// MW: Addition for visual studio 2008
#include <stdio.h>
#ifdef WIN32
#	include <errno.h>
#	include <fcntl.h>
#	include <io.h>
#	include <share.h>
#else
#	include <unistd.h>
#	include <fcntl.h>
#endif
#include <stdarg.h>
#include "vsnprintf.h"

#include <sys/stat.h>
#include <string.h>
#include <errno.h>
//#include "../common/all_common.h"
//#include "mail.h"
#include "lib.h"
#include "lprintf.h"
#include "rwlock.h"
#include "lfile.h"
#include "thread.h"
#include "solfile.h"
#define DMID 14
#include "debug.h"

extern int imsg(const char *arg_list, ...);

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

//Because the open is not dfopen so the close mustn't be, but on unix
// the open 'is' dfopen so the close must also be. 
#ifdef WIN32
#ifdef fclose 
#undef fclose
#endif
#endif

#define MAXHANDLE 2000
static int handle_line[MAXHANDLE];
static int handle_isopen[MAXHANDLE];

int lfile_handle_open(int fd, int line)
{
	if (fd<0 || fd >=MAXHANDLE) return FALSE;
	handle_line[fd] = line;
	handle_isopen[fd] = TRUE;
	return TRUE;
}
int lfile_handle_close(int fd)
{
	if (fd<0 || fd >=MAXHANDLE) return FALSE;
	handle_isopen[fd] = FALSE;
	return TRUE;
}
int lfile_handle_dump(void)
{
	int i;
	FILE *f;
	f = fopen("lfile.log","w");
	if (f==NULL) return FALSE;
	for (i=0; i<MAXHANDLE; i++) {
		if (handle_isopen[i]) fprintf(f,"Handle %d, line %d\n",i,handle_line[i]);
	}
	fclose(f);
	return TRUE;
}
int lfile_osize(LFILE *f)
{
	return lib_file_osizeh(lfile_fileno(f));
}
int lfile_fileno(LFILE *f)
{
	return solfileno(f->f);
}
int lfile_unlock_handle(int fd);
int lfile_lock_handle(int fd, int exclusive);

// also change to use 'solfopen...' calls on unix so it works on solaris nicely
// if 'writelock' then we need exclusive access to the file
// if 'readlock' we don't mind other people with 'read' access ,but no one else
// is allowed a writelock until we are done. 
#ifdef WIN32
FILE* lfile_fopen_shared(const char *filename, const char *mode, int nolock)
{
	int h;
	int commodeset = 0;
	int scanset    = 0;
	int whileflag, pmode=0;
	int flags = 0;
	int sharemode = 0;

	const char *xmode=mode;
	switch (*mode) {
	case ('r'):
		flags = O_RDONLY;
		if (nolock) sharemode = _SH_DENYNO;
		else sharemode = _SH_DENYWR;
		break;
	case ('w'):
		flags = O_WRONLY | O_CREAT | O_TRUNC;
		sharemode = _SH_DENYRW;
		break;
	case ('a'):
		flags = O_WRONLY | O_CREAT | O_APPEND;
		sharemode = _SH_DENYRW; 
		// Chrisp, we don't need to lock in this situation.... I don't think. 
		// Was getting corruption, trying it with this locking....what  goes wrong.
//		sharemode = _SH_DENYNO;
		break;
	default:
		return(NULL);
	}

	/* There can be up to three more optional mode characters:
	(1) A single '+' character,
	(2) One of 't' and 'b' and
	(3) One of 'c' and 'n'.
	*/

	whileflag=1;

	while(*++mode && whileflag)
	switch(*mode) {

	case ('+'):
		sharemode = _SH_DENYRW;
		if (flags & O_RDWR)
			whileflag=0;
		else {
			flags |= O_RDWR;
			flags &= ~(O_RDONLY | O_WRONLY);
		}
		break;

	case ('b'):
		if (flags & (O_TEXT | O_BINARY))
			whileflag=0;
		else
			flags |= O_BINARY;
		break;

	case ('t'):
		if (flags & (O_TEXT | O_BINARY))
			whileflag=0;
		else
			flags |= O_TEXT;
		break;

	case ('c'):
		if (commodeset)
			whileflag=0;
		else {
			commodeset = 1;
		}
		break;

	case ('n'):
		if (commodeset)
			whileflag=0;
		else {
			commodeset = 1;
		}
		break;
	case ('S'):
		if (scanset)
			whileflag=0;
		else {
		scanset = 1;
			flags |= _O_SEQUENTIAL;
		}
		break;

	case ('R'):
		if (scanset)
			whileflag=0;
		else {
			scanset = 1;
			flags |= _O_RANDOM;
		}
		break;

	case ('T'):
		if (flags & _O_SHORT_LIVED)
			whileflag=0;
		else
			flags |= _O_SHORT_LIVED;
		break;

	case ('D'):
		if (flags & _O_TEMPORARY)
			whileflag=0;
		else
			flags |= _O_TEMPORARY;
		break;
	default:
		whileflag=0;
		break;
	}

	if ( (flags & O_CREAT) && (flags & O_RDONLY) ) pmode = S_IREAD;  // logic error on windows as rdonly is zero.... so cannot occur.
	else pmode = S_IREAD | S_IWRITE;

	flags |= O_NOINHERIT;
	h = _sopen(filename, flags, sharemode,pmode);

	
	if (h<0) {
		char xfilename[BFSZ];
		int exists;
		ncpy(xfilename,(char *) filename,BFSZ-1);
		exists = lib_exists(xfilename);
		imsg("lfile: sopen_bad exists=%d (%s) (%s) m=%x sharemode=%d pmode=%d %s %s\n"
			,exists,filename,xmode
			,flags,sharemode,pmode
			,nolock ? "NoLock" : "Locked",strerror(errno));

		lib_to_unix_slash(xfilename);
		lib_cleanup_slash(xfilename);
		imsg("lfile: retry with (%s)\n",xfilename);
		h = _sopen(xfilename, flags, sharemode,pmode);
		if (!exists) errno=ENOENT; 
		if (h<0) return NULL;
		imsg("lfile: sopen_bad retry worked (%s)\n",xfilename);
	}
	imsg("lfile: openok (%s) (%s) %s\n",filename,xmode,nolock ? "NoLock" : "Locked");
	return _fdopen(h, xmode);
}
#endif

static int dolocks=TRUE;
LFILE *lfile_fopen(char *fname,char *mode, int wait, char *reason, int line)
{
	return lfile_fopen_ext(fname,mode,wait,reason,line,FALSE);
}
LFILE *lfile_fopen_ext(char *fname,char *mode, int wait, char *reason, int line, int nolock)
{
	// nolock means 'don't keep' the lock. still get it to cause nfs flush.
	LFILE *l;
	time_t start = time(NULL);
	int iswrite=FALSE;
	int held=0;
	o_thread hid;
	int age;
	int first=TRUE;
	if (strchr(mode,'w')!=NULL) iswrite = TRUE;
	if (strchr(mode,'+')!=NULL) iswrite = TRUE;
	if (strchr(mode,'a')!=NULL) iswrite = TRUE;
	l = calloc(1,sizeof(LFILE));

	//imsg("lfile_fopen: start (%s) %p\n",fname,l->lock);
	l->lock = rwlock_wait(fname,wait,iswrite,line,&held,&hid,&age);
	if (l->lock==NULL) {
		errno = 0;
		lprintf(reason,BFSZ-1,"Unable to lock file sorry rwlock_busy (%s), line=%d wait=%d/%d hid=%d age=%d",fname,held,(int) (time(NULL)-start),wait,(int) hid,age);
		if (th_equal(th_oid(),hid)) lprintf(reason,BFSZ-1,"DEADLOCK, lock file sorry rwlock_busy (%s), line=%d wait=%d/%d hid=%d age=%d",fname,held,(int) (time(NULL)-start),wait,(int) hid,age);
		return NULL;
	}
	for (;first || (time(NULL)-start < wait);) {
		first = FALSE;
#ifdef WIN32 
		l->f = lfile_fopen_shared(fname,mode,nolock);
		if (l->f!=NULL) lfile_handle_open(fileno(l->f),line);
#else
#ifdef is_linux
		l->f = dfopen(fname,mode,"ndb.c",line,"none");
#else
		l->f = solfopen(fname,mode);
#endif
		if (l->f!=NULL) {
			lib_close_on_exec(solfileno(l->f));
			if (dolocks) lfile_lock_handle(solfileno(l->f),iswrite);
			lfile_handle_open(solfileno(l->f),line);
		}
#endif
		if (l->f==NULL) if (errno!=ENOENT) {
			lib_msleep(100);
			continue;
		}
		break;
	}
	
	if (l->f==NULL) {
		lprintf(reason,BFSZ-1,"Lock failed %d sec (%s) %s",(int) (time(NULL)-start),fname,strerror(errno));
		rwlock_release(l->lock); l->lock = NULL;
		free(l);
// CHRISP REMOVED NEXT LINE, IT SHOULD NOT BE CLEARED AS SOME THINGS USE IT....
//		errno = 0;
		if (time(NULL)-start>1) imsg("lfile: fopen failed in %d seconds, called by line %d %s\n",(int) (time(NULL)-start),line,fname);
		return NULL;
	}
	if (nolock) { // release the lock, it has served it's purpose we hope...
#ifndef WIN32
		if (dolocks) lfile_unlock_handle(solfileno(l->f));
#endif
		rwlock_release(l->lock); l->lock = NULL;
	}
	// if (is_nfs) lock handle
	ncpy(l->fname,fname,BFSZ-1);
	l->isopen = TRUE;
	if (time(NULL)-start>1) imsg("lfile: fopen completed in %d seconds, called by line %d %s\n",(int) (time(NULL)-start),line,fname);
	return l;
}
LFILE *lfile_fopen_create(char *fname,char *mode, int wait, char *reason)
{
	LFILE *f = lfile_fopen(fname,mode,wait,reason,__LINE__);
	if (f==NULL) {
		if (errno==ENOENT) {
			f = lfile_fopen(fname,"ab",wait,reason,__LINE__);
			if (f==NULL) return NULL;
			lfile_fclose(&f,__LINE__);
			f = lfile_fopen(fname,mode,wait,reason,__LINE__);
		}
	}
	return f;
}
int lfile_fclose(LFILE **pl,int line)
{
	LFILE *l = *pl;
	int ret;
	if (!l->isopen) lib_abort();
	l->isopen = FALSE;
	lfile_handle_close(solfileno(l->f));
	//imsg("lfile: unLock file (%s) line=%d\n",l->fname,line);
	rwlock_release(l->lock); l->lock = NULL;
#ifdef WIN32
	ret = fclose(l->f);
#else
	if (dolocks) lfile_unlock_handle(solfileno(l->f));
	ret = solfclose(l->f);
#endif

	free(l); *pl = NULL;
	// if (is_nfs) unlock handle
	return ret;
}
char *lfile_fgets(char *str, int n, LFILE *f)
{
	return solfgets(str,n,f->f);
}


/*int ndb_lock(int fd)
{
	// On windows _locking() might be appropriate....
}
int ndb_unlock(int fd)
{

}
*/
#ifndef WIN32
int lfile_lock_handle(int fd, int exclusive)
{
	int op, ret;
	struct flock arg;

	op = F_SETLK;	
	arg.l_type = F_RDLCK;
	if (exclusive) arg.l_type = F_WRLCK;
	arg.l_whence = 0;
	arg.l_start = 0;
	arg.l_len = 10000000;
	arg.l_pid = 0;
	
	if ((ret = fcntl(fd, op, &arg)) == -1) {
		if (errno == EACCES || errno == EAGAIN)	errno = EWOULDBLOCK;	
	}
	if (ret==0) return TRUE;
	return FALSE;
}

int lfile_unlock_handle(int fd)
{
	int op, ret;
	struct flock arg;

	op = F_SETLK;	
	arg.l_type = F_UNLCK;
	arg.l_whence = 0;
	arg.l_start = 0;
	arg.l_len = 10000000;
	arg.l_pid = 0;
	
	if ((ret = fcntl(fd, op, &arg)) == -1) {
		if (errno == EACCES || errno == EAGAIN)	errno = EWOULDBLOCK;	
	}
	if (ret==0) return TRUE;
	return FALSE;
}


#endif

int lfile_feof(LFILE *pl)
{
	return solfeof(pl->f);	
}
int lfile_fread(void *bf, size_t size, size_t count, LFILE *f)
{
	return (int) solfread(bf,size,count,f->f);
}
int lfile_fwrite(void *bf, size_t size, size_t count, LFILE *f)
{
	return (int) solfwrite(bf,size,count,f->f);
}
int lfile_fseek(LFILE *f, long to, int mode)
{
	return (int) solfseek(f->f,to,mode);
}
int lfile_ftell(LFILE *f)
{
	return (int) solftell(f->f);
}
int lfile_fprintf(LFILE *f, char *format, ...)
{	
	char bf[BFSZ];
	va_list args;
	int len;

	va_start(args,format);
	len = vsnprintf(bf,BFSZ,format,args);
	if (len == -1 || len == BFSZ) bf[len = BFSZ-1] = 0;
	va_end(args);

	return solfprintf(f->f,"%s",bf);
}
