/*
Todo:
	Make expires work check -patching' code in expires.
	test it.
	test corrupting a file or mixing up order.

		............

	check bigger than xover_max #define INBFSZ 64000

	write some test commands
		forwards, backwards, random, chunks of 100
		(remove logging)
		(increase bucketsize)
	
Change so output is anothe file handle
make main dnews call the close function more often.
Change reader so it uses a buffer and only seeks and reads if not in buffer.


*/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include "lib.h"
#include "sary.h"
#include "string.h"
#include "emsg.h"
#include "xhash.h"
#include "db.h"

static FILE *xf; 
static int xgid;
static int xbucket;
static int xhdr_changed;
#define BUCKETSIZE 100 /* Increase to 500 after testing - CAN'T CHANGE IT NOW, BUGGER :-)  */
typedef struct HDR {int pnt; int len;} Hdr;
static Hdr xhdr[BUCKETSIZE];
#define INBFSZ 64000

static char inbf[INBFSZ];
static int inbf_from,inbf_len;
static int xatend;
int fread_fast(char *bf, int from, int sz, int n, FILE *f)
{
	int loop=0;
	int len = sz*n;
	int offset;

	if (inbf_from>=0) {
again:	
		if (from<inbf_from) goto norm;
		if (from+len > inbf_from+inbf_len) goto norm;
		offset = from - inbf_from;
		memcpy(bf,inbf+ (offset),len);

		return len;
	}
norm:
	inbf_from = from - 2000;
	if (inbf_from<0) inbf_from = 0;
	if (fseek(xf,from,SEEK_SET)!=0) imsg("xhash: fread_fast seek failed %d %s\n",inbf_from,strerror(errno));
	n = fread(inbf,1,INBFSZ-2,xf);
	xatend = FALSE;
	inbf_len = n;
	inbf_from = from;
	loop++;
	if (loop>2) {
		imsg("xhash: logic fault from=%d inbf_from=%d\n",from,inbf_from);
		return -1;
	}
	goto again;
}


/*
New idea, format of file is:

  x100.xov  100-199

  hdr = int pnt, int len
  hdr[100]

  So index is 200*4 1k bytes.

  And is updated on every write to the file.  	


*/

static char path[BFSZ];
int xhash_init(char *xpath)
{
	ncpy(path,xpath,BFSZ-1);
	return TRUE;
}
char *xhash_fname(int gid, int bkt)
{
	static char bf[BFSZ];
	lprintf(bf,BFSZ-1,"%s/x%d.xov",xhash_path(gid),bkt);
	return bf;
}
char *xhash_path(int gid)
{
	static char bf[BFSZ];
	lprintf(bf,BFSZ-1,"%s/hash/%d/%d",path,gid%100,gid);
	return bf;
}
int xhash_bucket(int n, int *bucket, int *h)
{
	*h = n % BUCKETSIZE;
	*bucket = n / BUCKETSIZE;
	return TRUE;
}
int xhash_close(void)
{
	inbf_from = inbf_len = 0;;
	xatend = FALSE;
	if (xf!=NULL) if (xhdr_changed) {
		if (fseek(xf,0,SEEK_SET)!=0) imsg("xhash_close, write failed %s\n",strerror(errno));
		if (fwrite(xhdr,sizeof(Hdr),BUCKETSIZE,xf)!=BUCKETSIZE) imsg("xhash_closehdr, write failed %s\n",strerror(errno));
		dmsg("xhash: close-header write %d\n",fileno(xf));
		xhdr_changed = FALSE;
	}
	if (xf!=NULL) dmsg("xhash: close-read  %d\n",fileno(xf));
	if (xf!=NULL) fclose(xf);
	xf = NULL;
	xatend = FALSE;
	return TRUE;
}
static int xiswrite;
int xhash_open(int gid, int bucket, int iswrite, char *reason)
{
	char *mode;
	int done=FALSE;
	static char finbf[65000];
	static char foutbf[65000];

	if (gid!=xgid) xhash_close();
	if (xbucket!=bucket) xhash_close();
	if (iswrite) if (iswrite!=xiswrite) xhash_close();

	if (iswrite) if (xf!=NULL) return TRUE;
	if (!iswrite) if (xf!=NULL) return TRUE;
	mode = "rb";
	if (iswrite) mode = "rb+";

	xhdr_changed = FALSE;
	xatend = FALSE;

	if (iswrite) xf = fopen(xhash_fname(gid,bucket),"rb+");
	else xf = fopen(xhash_fname(gid,bucket),"rb");

	if (xf!=NULL) dmsg("xhash: open (%s) %s h=%d\n",xhash_fname(gid,bucket),mode,fileno(xf));
	else dmsg("xhash: notfound (%s) %s\n",xhash_fname(gid,bucket),mode);
	if (iswrite) if (xf==NULL) if (errno==ENOENT) {
		mode = "wb+";
		lib_createdir(xhash_fname(gid,bucket));
		xf = fopen(xhash_fname(gid,bucket),mode);
		if (xf==NULL) imsg("xhash: CANNOT OPEN IN CREATE MODE %s\n",strerror(errno));
		memset(xhdr,0,sizeof(Hdr)*BUCKETSIZE);
		xhdr_changed = TRUE;
		if (xf!=NULL) if (fwrite(xhdr,sizeof(Hdr),BUCKETSIZE,xf)!=BUCKETSIZE) {
			imsg("xhash: Unable to write index part of xover data %s\n",xhash_fname(gid,bucket));
			lprintf(reason,100,"Unable to write index part of xover data %s",xhash_fname(gid,bucket));
			fclose(xf); xf = NULL;
			return FALSE;
		}
	}
	if (xf==NULL) return FALSE;
	setvbuf(xf,foutbf,_IOFBF,sizeof(finbf));
	xbucket = bucket;
	xgid = gid;
	xiswrite = iswrite;
	if (!xhdr_changed) {
		int r;
		r = fread(xhdr,sizeof(Hdr),BUCKETSIZE,xf); 
		if (r!=BUCKETSIZE) {
		 if (feof(xf)) {
			imsg("Why couldn't we read the entire header?\n");
			memset(xhdr,0,sizeof(Hdr)*BUCKETSIZE);
			return TRUE;			
		 }
		 fclose(xf);
		 if (iswrite ) xf = NULL; else xf = NULL; 
		 lprintf(reason,100,"Could not read index %s",strerror(errno));
		 return FALSE;
		}
	}
	return TRUE;
}
int xhash_corrupt(int gid, int n)
{
	int bucket,h;
	xhash_bucket(n,&bucket,&h);
	xhash_close();
	remove(xhash_fname(gid,bucket));
	return TRUE;
}
int xhash_get(int gid, int n, char *xline, int bfsz, int *xlen, char *reason)
{
	int bucket,h;
	int r;
	int sz;
	strcpy(reason,"");
	xhash_bucket(n,&bucket,&h);
	if (!xhash_open(gid,bucket,FALSE,reason)) {
		/*dmsg("xhash: could not open gid %d bucket %d\n",gid,bucket);*/
		return FALSE;
	}
	sz = xhdr[h].len;
	if (sz<=0) {
		/*dmsg("xhash: Not in index\n");*/
		return FALSE; /* Not in index */
	}
	if (sz>100000) {
		imsg("xhash: Invalid size \n");
		lprintf(reason,100,"xhash: fread failed, deleting corrupt xover index2 invalid sz %d",sz);
		xhash_corrupt(gid,n);
		return FALSE;
	}
	if (sz>=bfsz) {
		lprintf(reason,100,"Cannot return xover data as buffer is too small %d >%d",sz,bfsz);
		return FALSE; 
	}
	r = fread_fast(xline,xhdr[h].pnt,1,sz,xf);
	if (r>0) xline[r] = 0;
	if (r!=sz) {
		/* Corrupt indexx */
		imsg("xhash: Index must be corrupt, deleting to rebuild it now %d/%d %s\n",r,sz,strerror(errno));
		lprintf(reason,100,"fread failed, deleting corrupt xover index");
		xhash_corrupt(gid,n);
		return FALSE;
	}
	if (xline[sz-1]!='\n') {
		imsg("xhash: Index must be corrupt, deleting to rebuild it now3\n");
		lprintf(reason,100,"fread failed, deleting corrupt xover index3");
		xhash_corrupt(gid,n);
		return FALSE;	
	}
	// Count the tabs in the line, if wrong number then corrupt so return null
	{
	    int nt=0;
	    int k;
    	    for (k=0; k<sz; k++) {
    	     if (xline[k]=='\t') nt++;
	    }
	    if (nt<7) {
		imsg("xhash: Index must be corrupt only found %d tabs %d:%d\n",nt,gid,n);
		lprintf(reason,100,"Invalid tab count");
		xhash_corrupt(gid,n);
		return FALSE;	
	    }
	}
	if (xline[sz-1]!='\n') {
		imsg("xhash: Index must be corrupt, deleting to rebuild it now3\n");
		lprintf(reason,100,"fread failed, deleting corrupt xover index3");
		xhash_corrupt(gid,n);
		return FALSE;	
	}

	*xlen = sz;
	xline[sz] = 0;
	return TRUE;
}
int xhash_add(int gid, int n, char *xline, int len,  char *reason)
{
	int bucket,h;
	strcpy(reason,"");
	xhash_bucket(n,&bucket,&h);
	if (!xhash_open(gid,bucket,TRUE,reason)) {
		imsg("xhash: open failed for %d:%d %s\n",gid,bucket,reason);
		return FALSE;
	}
	if (!xatend) {
    	    if (fseek(xf,0,SEEK_END)!=0) {
    	    	lprintf(reason,100,"SEEK TO end failed");
	    	return FALSE;
	    }   
	    xatend = TRUE;
	}
	xhdr[h].len = len;
	xhdr[h].pnt = ftell(xf);
	xhdr_changed = TRUE;
	if ((int) fwrite(xline,1,len,xf)!=len) {
		lprintf(reason,100,"Fwrite failed in xover %s",strerror(errno));
		return FALSE;
	}
	return TRUE;
}
int xhash_expire(int grp, int gid, int min, int max, int force)
{
	/* 
	  Scan folder, then for each 'bucket' check if it exists in index,
	  if not delete it 

	*/
	int bucket;
	char *fname=NULL;	
        static Sary *ls;       
	int ndel=0;
	int i,j;
	int used[60000];
	int n;
        if (ls==NULL) ls = s_alloc();
	min = min / BUCKETSIZE;
	max = max / BUCKETSIZE;
	s_wild(ls,xhash_path(gid),"*.xov");
	
	
	if (!force) {
		for (i=0; i <=(max-min); i++) {used[i] = FALSE; if (i>=60000) break;}
		n = g_list_start(grp);
		for (i=0; (i<10000000) && (n>=0); i++)  {
			n = n / BUCKETSIZE;
			n = n - min;
			if (n<0) continue;
			if (n>=60000) continue;
			used[n] = TRUE;
		 	n = g_list_next();
		}
	}
        for (j=0; j<s_len(ls); j++) {
              fname = s_line(ls,j);
	      bucket = atoi(fname+1);
	      if (force) {
			xhash_remove(gid,fname);
			continue;
	      }
	      if (bucket<min) {xhash_remove(gid,fname); ndel++;}
	      else if (bucket>max) {xhash_remove(gid,fname); ndel++;}
	      else {
		n = bucket - min;
		if (n<0) continue;
		if (n>=60000) continue;
		ndel++;
		if (!used[n]) xhash_remove(gid,fname);
	      }
	}
	s_clear(ls);
	dmsg("xhash: Deleted %d xover buckets\n");
	return TRUE;
}
int xhash_remove(int gid, char *fname)
{
	char bf[BFSZ];
	lprintf(bf,BFSZ-1,"%s/%s",xhash_path(gid),fname);
	dmsg("xhash: Removing (%s)\n",bf);
	if (remove(bf)!=0) {
		imsg("xhash: Remove of (%s) failed %s\n",fname,strerror(errno));	
		return FALSE;
	}
	return TRUE;
}
int xhash_delete(int gid)
{
	xhash_expire(0,gid,0,0,TRUE);
	return TRUE;		
}
/*
Nope, it's not fragementation, it's xover indexes that don't exist .... 
Should trigger a build maybe ? 

int xhash_defrag(int gid)
{
	int bucket,h;
	int r;
	int sz;
	strcpy(reason,"");
	xhash_bucket(n,&bucket,&h);
	if (!xhash_open(gid,bucket,TRUE,reason)) {
		return FALSE;
	}
	sz = xhdr[h].len;

	
	
	xhdr_changed = TRUE; 
	xhash_close();
}
*/