/*
    file.* - file io classes
    Copyright (C) 1999-2004  Matthew Mueller <donut AT dakotacom.net>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "file.h"
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "sockstuff.h"
#include "strreps.h"
#include "path.h"


void xxrename(const char *oldpath, const char *newpath) {
#ifdef WIN32
	//On windows rename will not replace an existing file, so check first and remove it.
	if (fexists(newpath)) {
		if (unlink(newpath))
			throw FileEx(Ex_INIT, "rename: unlink %s: %s(%i)\n",newpath,strerror(errno),errno);
	}
#endif
	if (rename(oldpath, newpath))
		throw FileEx(Ex_INIT, "rename %s > %s: %s(%i)\n",oldpath,newpath,strerror(errno),errno);
}

void copyfile(c_file *of, c_file *nf) {
	char buf[4096];
	int len;
	while ((len=of->read(buf, 4096)) != 0)
		nf->write(buf, len);
}

int c_file_buffy::bfill(uchar *b,int l){
	return fileptr->read(b,l);
}

c_file::c_file(const char *fname): m_name(fname){
	rbuffer=NULL;
}

c_file::~c_file(){
//	close();//cant call it here, since doclose is already gone
	delete rbuffer;
}

ssize_t c_file::vputf(const char *buf, va_list ap){
	char *fpbuf;
	int i,l;
	l=vasprintf(&fpbuf,buf,ap);
	try {
		i=write(fpbuf,l);
	} catch (...) {
		free(fpbuf);
		throw;
	}
	free(fpbuf);
	return i;
}
ssize_t c_file::putf(const char *data,...){
	va_list ap;
	va_start(ap,data);
	ssize_t r=vputf(data, ap);
	va_end(ap);
	return r;
}
ssize_t c_file::read(void *data,size_t len){
	ssize_t i=doread(data,len);
	if (i<0) throw FileEx(Ex_INIT,"read %s (%s)", name(), dostrerror());
	return i;
}
void c_file::readfull(void *data,size_t len){
	size_t cur=0;
	ssize_t i;
	while (cur < len) {
		i=doread((char*)data+cur, len-cur);
		if (i<0) throw FileEx(Ex_INIT,"readfull %s (%s)", name(), dostrerror());
		else if (i==0) throw FileEx(Ex_INIT,"readfull %s: unexpected EOF", name());
		cur += i;
	}
	assert(cur == len);
}
ssize_t c_file::write(const void *data,size_t len){
	size_t sent=0;
	ssize_t i;
	while (sent < len) {
		i=dowrite((char*)data+sent, len-sent);
		if (i <= 0) 
			throw FileEx(Ex_INIT,"write %s: %i!=%i (%s)", name(), sent, len, dostrerror());
		sent += i;
	}
	assert(sent == len);
	return sent;
}

void c_file::flush(int local){
//	int i=0;
//###########3 dowrite(buffers..)
	if (!local) {
		int r=doflush();
		if (r != 0) throw FileEx(Ex_INIT,"flush %s: %i (%s)", name(), r, dostrerror());
	}
}
void c_file::close(void){
	if (isopen()){
		flush(1);
		if (doclose() != 0)
			throw FileEx(Ex_INIT,"close %s (%s)", name(), dostrerror());
	}
	if (rbuffer)rbuffer->clearbuf();
}
int c_file::close_noEx(void){
	try {
		close();
		return 0;
	} catch (FileEx &e) {
		return -1;
	}
}
void c_file::initrbuf(void){
	if (!rbuffer){
		rbuffer=new c_file_buffy(this);
	}
};


c_file_fd::c_file_fd(int dfd, const char *name):c_file(name){
	fd=::dup(dfd);
	if (fd<0)
		throw FileEx(Ex_INIT,"dup %s(%i) (%s)", name, dfd, strerror(errno));
}

c_file_fd::c_file_fd(const char *name,int flags, int mode):c_file(name){
	fd=::open(name,flags,mode);
	if (fd<0)
		THROW_OPEN_ERROR("open %s (%s)", name, strerror(errno));
		//throw FileEx(Ex_INIT,"open %s (%s)", name, strerror(errno));
}
int fopen2open(const char *mode){
	int m = 0;
	switch(mode[0]) {
		case 'r': m = strchr(mode,'+')?O_RDWR:O_RDONLY; break;
		case 'w': m = (strchr(mode,'+')?O_RDWR:O_WRONLY) | O_CREAT | O_TRUNC; break;
		case 'a': m = (strchr(mode,'+')?O_RDWR:O_WRONLY) | O_CREAT | O_APPEND; break;
		default:assert(0);
	}
	if (strchr(mode,'b')) m |= O_BINARY;
	return m;
}
c_file_fd::c_file_fd(const char *name,const char *mode):c_file(name){
	int flags=fopen2open(mode);
	fd=::open(name,flags,PUBMODE);
	if (fd<0)
		THROW_OPEN_ERROR("open %s (%s)", name, strerror(errno));
}
const char *c_file_fd::dostrerror(void) {
	return strerror(errno);
}
int c_file_fd::doflush(void){
#ifdef HAVE_FSYNC
	if (fd>=0)
		return fsync(fd);
#endif
	return 0;
}
int c_file_fd::doclose(void){
	int i=0;
	i=::close(fd);
	fd=-1;
	return i;
}
int c_file_fd::isopen(void)const{
	return (fd>=0);
}
ssize_t c_file_fd::dowrite(const void *data,size_t len){
	return ::write(fd,(char*)data,len);
}
ssize_t c_file_fd::doread(void *data,size_t len){
	return ::read(fd,data,len);
}
int c_file_fd::seek(int offset, int whence){
	int r = lseek(fd, offset, whence);
	if (r<0)
		throw FileEx(Ex_INIT,"seek %s: %s", name(), dostrerror());
	return r;
}


#ifdef USE_FILE_STREAM
int c_file_stream::c_file_stream(const char *name,const char * mode):c_file(name){
	if (!(fs=fopen(name,mode)))
		THROW_OPEN_ERROR("fopen %s (%s)", name, strerror(errno));
}
const char c_file_stream::*dostrerror(void) {
	return strerror(errno);
}
int c_file_stream::doflush(void){
	if (fs)
		return fflush(fs);
	return 0;
}
int c_file_stream::doclose(void){
	int i=0;
	i=fclose(fs);
	fs=NULL;
	return i;
}
int c_file_stream::isopen(void)const{
	return (fs!=0);
}
ssize_t c_file_stream::dowrite(const void *data,size_t len){
	return fwrite(data,1,len,fs);
}
ssize_t c_file_stream::doread(void *data,size_t len){
	return fread(data,1,len,fs);
}
#endif


c_file_tcp::c_file_tcp(const char *host,const char * port):c_file(host){
	if (m_name.find(':')<0){//this isn't quite right with ipv6 addrs, but its only for error messages so who cares ;)
		m_name+=':';
		m_name+=port;
	}
	try {
		sock=make_connection(host,port);
	} catch (FileEx &e) {
		throw FileEx(Ex_INIT,"open %s (%s)", name(), e.getExStr());
	}
}
const char *c_file_tcp::dostrerror(void) {
	return sock_strerror(sock_errno);
}
int c_file_tcp::doflush(void){
#ifdef HAVE_FSYNC
	if (sock_isvalid(sock))
		return fsync(sock);
#endif
	return 0;
}
int c_file_tcp::doclose(void){
	int i=0;
	i=sock_close(sock);
	sock=SOCK_INVALID;
	return i;
}
int c_file_tcp::isopen(void)const{
	return sock_isvalid(sock);
}
ssize_t c_file_tcp::dowrite(const void *data,size_t len){
	//don't need to use sock_write_ensured since c_file::write handles the looping.
	return sock_write(sock,(char*)data, len);
}
ssize_t c_file_tcp::doread(void *data,size_t len){
	return sock_read(sock,data,len);
}
bool c_file_tcp::datawaiting(void) const {
	return sock_datawaiting(sock);
}
