#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#ifdef WIN32
#include "windows.h"
#endif
#include "gstat.h"

#ifdef NETWARE
#include "memdebug.h"
#define malloc mymalloc
#define free myfree
#define strdup mystrdup
#endif

#define BFSZ 1000
#ifndef FALSE
#define FALSE 0 
#define TRUE (!FALSE)
#endif

/* //#define TESTING TRUE*/
#ifdef TESTING
void testshow(char *s)
{
	printf("--> %s\n",s);
}
static int xxtime;
static int tlast;
void main(int argc, char *argv[])
{
	int i,j;
	int x=0;
	gstat_init("d:\\test\\gstat",printf);

	xxtime = 0;
	if (argc>2) {
		gstat_send_names(testshow) ;	
		gstat_send(&x,testshow);
	} else {
		j=0;
		for (i=0; i<100; i++) {
			gstat_add("Incoming",j++);
			if (i==0) gstat_send_names(testshow) ;
			gstat_send(&x,testshow);
			xxtime += 10;
		}
		gstat_rewrite();
		xxtime += 500*10;
		for (i=0; i<100; i++) {
			gstat_add("Incoming",j++);
			if (i==0) gstat_send_names(testshow) ;
			gstat_send(&x,testshow);
			xxtime += 10;
		}
		gstat_rewrite();
		xxtime += 500*10;
		for (i=0; i<100; i++) {
			gstat_add("Incoming",j++);
			if (i==0) gstat_send_names(testshow) ;
			gstat_send(&x,testshow);
			xxtime += 10;
		}
		gstat_rewrite();
		xxtime += 500*10;
		for (i=0; i<100; i++) {
			gstat_add("Incoming",j++);
			if (i==0) gstat_send_names(testshow) ;
			gstat_send(&x,testshow);
			xxtime += 10;
		}
		gstat_rewrite();
		printf("sorting again+++++\n");
	
		gstat_rewrite();
	}
}
#endif
/*-------------------gstats---------------------------*/
static char file_nam[BFSZ];
static char file_dat[BFSZ];
static char file_tmp[BFSZ];

static char *gstat_name[MAXID];
static int ngstat;
static Gstat *gstat[MAXID];
static int gstat_id[MAXID];
static int gstat_n[MAXID];
static int gstat_used[MAXID];
void gstat_free(Gstat *g);

Gstat *gstat_get(int id)
{
 	return gstat[id];
}
int vtime(void *x)
{
#ifdef TESTING
	return xxtime;
#else
	return (int) time(NULL);
#endif
}
char *str_nocolon(char *src)
{
	static char bf[BFSZ],*s;
	strcpy(bf,src);
	for (s=bf;*s!=0;s++) {
		if (*s==':') *s = ' ';
	}
	return bf;
}
int lprintf( char *buffer, int bfsize, char *format, ... );
void gstat_send_names(void (*sendfn)(char *s))
{
	int i;
	char bf[BFSZ];
	gstat_load();
	for (i=0; i<ngstat; i++) {
		if (!gstat_used[i]) continue;
		lprintf(bf,BFSZ-1,":gn:%d:%s:",gstat_id[i],str_nocolon(gstat_name[i]));
		sendfn(bf);
	}

}

int gstat_nextid(void)
{
	int i,j;
	for (i=0; i<MAXID; i++) {
		for (j=0; j<ngstat; j++) {
			if (gstat_id[j]==i) goto next;	
		}
		return i;
next:		;
	}
	return MAXID-1;
}
static int (*emsg)(const char *fmt,...) ;
void gstat_init(char *name, int (*xemsg)(const char *fmt,...) )
{
	emsg = xemsg;
	emsg("Gstat initialized\n");
	strcpy(file_nam,name);
	strcpy(file_dat,name);
	strcpy(file_tmp,name);
	strcat(file_nam,".nam");
	strcat(file_dat,".dat");
	strcat(file_tmp,".tmp");
}
int gstat_add(char *name, int value)
{
	int i;
	FILE *f;
	Grec rec;
	time_t start;

	start = time(NULL);
	i = gstat_findadd(name);
	if (i<0) return FALSE;
	rec.t = vtime(NULL);
	rec.id = gstat_id[i];
	rec.value = value;
	rec.filler = -1;
	f = fopen(file_dat,"ab");
	if (f==NULL) {emsg("Can't append to %s %s\n",file_dat,strerror(errno)); return FALSE;}
	fwrite(&rec,sizeof(rec),1,f);
	fclose(f);
	if ((time(NULL)-start)>2) {
		emsg("gstat_add: took %d seconds, disable with gstat false in dnews.conf\n",time(NULL)-start);
	}
	return TRUE;
}
int gstat_add_f(char *name, int value, FILE *f)
{
	int i;
	Grec rec;
	time_t start;

	start = time(NULL);
	i = gstat_findadd(name);
	if (i<0) return FALSE;
	rec.t = vtime(NULL);
	rec.id = gstat_id[i];
	rec.value = value;
	rec.filler = -1;
	fwrite(&rec,sizeof(rec),1,f);
	if ((time(NULL)-start)>2) {
		emsg("gstat_add: took %d seconds, disable with gstat false in dnews.conf\n",time(NULL)-start);
	}
	return TRUE;
}

void gstat_rewrite(void)
{
	Grec rec;
	int i;
	Gstat *g,*gnext;
	FILE *f;

	
	for (i=0; i<MAXID; i++) {
		if (gstat[i]!=NULL) gstat_free(gstat[i]);
		gstat[i] = NULL;
	}
	
	f = fopen(file_dat,"rb");
	if (f==NULL) {emsg("Can't append to %s %s\n",file_dat,strerror(errno)); return ;}
	for (;!feof(f);) {
		if (fread(&rec,sizeof(rec),1,f)!=1) break;
		if ((rec.id>=0) && (rec.id<MAXID)) {
			gaddon(&gstat[rec.id],rec.t,rec.value);
		}
	}
	fclose(f); f = NULL;
	/* Now add up duplicate times*/

	for (i=0; i<MAXID; i++) {
		g = gstat[i];
		for (;g!=NULL;g=g->next) {
			if (g->next==NULL) break;
			if (g->next->t==g->t) {
				g->v += g->next->v;
				gnext = g->next;
				g->next = g->next->next;
				free(gnext);
			}
		}
	}
	for (i=0; i<MAXID; i++) {
		if (gstat[i]==NULL) continue;
		gstat[i] = gstat_compress(gstat[i]);		
		gstat[i] = grev(gstat[i]);
	}
	f = fopen(file_tmp,"wb");
	if (f==NULL) {emsg("Can't write to %s\n",file_tmp); return;}
	for (i=0; i<MAXID; i++) {
		if (gstat[i]==NULL) continue;
		for (g=gstat[i];g!=NULL;g=g->next) {
			rec.id = i;
			rec.t = g->t;
			rec.value = g->v;
			fwrite(&rec,sizeof(rec),1,f);
		}
	}
	fclose(f); f = NULL;
	remove(file_dat);
	rename(file_tmp,file_dat);

	for (i=0; i<MAXID; i++) {
		if (gstat[i]!=NULL) gstat_free(gstat[i]);
		gstat[i] = NULL;
	}


}
static int total[MAXID];
static int totalgot[MAXID];
int gfile_len(FILE *f)
{
        struct stat bf;
        if (fstat(fileno(f),&bf)!=0) return 0;
        return bf.st_size;
}
void gstat_send(int *sendat, void (*sendfn)(char *s))
{
	FILE *f;
	int i;
	int xlen;
	int lastt = 0;
	Grec rec;

	if ((*sendat)==0) gstat_send_names(sendfn);

	f = fopen(file_dat,"rb");
	if (f==NULL) return;
/*	if (f==NULL) {emsg("Can't read %s %s\n",file_dat,strerror(errno)); return ;}*/
	xlen = gfile_len(f);
	if (xlen<= *sendat)  {
		*sendat = xlen;
		fclose(f);
		return;
	}
	fseek(f,*sendat,SEEK_SET);
	for (i=0; i<MAXID; i++) totalgot[i] = FALSE;
	for (;!feof(f);) {
		if (fread(&rec,sizeof(rec),1,f)!=1) break;
		*sendat += sizeof(rec);
		if (lastt>0) if (rec.t!=lastt) gstat_sendall(lastt,sendfn);
		lastt = rec.t;
		if ((rec.id>=0) && (rec.id<MAXID)) {
			totalgot[rec.id] = TRUE;
			total[rec.id] += rec.value;
		}
	}
	if (lastt>0) gstat_sendall(lastt,sendfn);
	fclose(f);
}
void gstat_sendall(int t, void (*sendfn)(char *s))
{
	int i;
	int nfound=0;
	char bf[BFSZ],out[BFSZ];
	lprintf(out,BFSZ-1,":gs: %d ",t);
	for (i=0; i<MAXID; i++) {
		if (totalgot[i]) { 
			if (!gstat_used[i]) if (gstat_name[i]!=NULL) {
				gstat_used[i] = TRUE;
				lprintf(bf,BFSZ-1,":gn:%d:%s:",gstat_id[i],str_nocolon(gstat_name[i]));
				sendfn(bf);
			}
			nfound++;
			lprintf(bf,BFSZ-1," %d:%d",i,total[i]); strcat(out,bf);
		}
		totalgot[i] = FALSE;
		total[i] = 0;
	}
	if (nfound>0) sendfn(out);
}


/*

5 seconds * 100 = 10 minutes
if age < 300 seconds leave alone
if age < 3000 seconds add up until time = 50 seconds then add one
if age < 30000 seconds add up until time = 500 seconds then add one

*/


Gstat *gstat_compress(Gstat *gin)
{
	int now=0;
	/* Go thru and make averages */
	Gstat *g,*g2;
	int age;
	int t= -1;
    int last= -1;
	int want=0;
	int savet=0;
	int v=0;
	int n;
	int lastwant=9;
	int nv=0;
	int tot;
	g2 = NULL;

	for (g=gin;g!=NULL;g=g->next) {
    		if (now<g->t) now = g->t;
	}
	


	gin = grev(gin);
	for (g=gin;g!=NULL;g=g->next) {
		age = now - g->t;
		if (age>=200000) continue;
		if (age<1000) want = 10;
		else if (age<10000) want = 100;
		else if (age<200000) want = 1000;
#ifdef TESTING
printf("age=%d t=%d v=%d want=%d lastwant=%d savet=%d\n"
       ,age,g->t,g->v,want,lastwant,savet);
#endif
		if (want!=lastwant) savet = g->t;
		else savet += want;

		/* Find the right cell to put this one in */
		for (;(savet+want)<g->t;) {
			savet += want;
		}

		lastwant = want;

		tot = g->v;
		n=1;
		for (;(g->next!=NULL) && (g->next->t < savet+want); g=g->next) {
#ifdef TESTING
			printf("Eating point %d < %d+%d (%d) \n",g->next->t , savet,want,savet+want);
#endif
			
			tot += g->next->v;
			n++;
		}
#ifdef TESTING
		printf("Compressed %d %d (n=%d)\n",savet,tot/n,n);
#endif
		gaddon(&g2,savet,tot/n);
	}

	/* SHOULD FREE IT HERE*/
	
#ifdef TESTING
	printf("Now we show you what is in g2\n");
	for (g=g2;g!=NULL;g=g->next) {
		printf("t=%d v=%d \n",g->t,g->v);
	}
#endif
	gstat_free(gin);
	return g2;
}
Gstat *grev(Gstat *g)
{
	Gstat *g2,*gnext;
	g2 = NULL;
	for (;g!=NULL;g=gnext) {
		gnext = g->next;
		gaddon(&g2,g->t,g->v);
		free(g);
	}
	return g2;
}
void gstat_free(Gstat *g)
{
	Gstat *gnext;
	for (;g!=NULL;g=gnext) {
		gnext = g->next;
		free(g);
	}
}
int gaddon(Gstat **a, int t, int v)
{
	Gstat *g;
	g = (Gstat *) malloc(sizeof(Gstat));
	if (g==NULL) {
		emsg("Ran out of memory in gstat\n");
		abort();
	}
	g->next = *a;
	g->t = t;
	g->v = v;
	*a = g;
	return TRUE;
}
#define GSTAT_NAM file_work("gstat.nam")
int gstat_load(void)
{
	FILE *f;
	char *s;
	char bf[BFSZ];
	char name[BFSZ];
	int id;
	f = fopen(file_nam,"r");
	if (f==NULL) return TRUE; 
	/*{emsg("Can't READ %s",file_nam); return TRUE;}*/
	for (;!feof(f);) {
		if (fgets(bf,BFSZ-10,f)==NULL) break;
		s = strtok(bf,":\n"); if (s==NULL) continue;
		id  = atoi(s);
		s = strtok(NULL,":\n"); if (s==NULL) continue;
		strcpy(name,s);
		if (gstat_find(name)<0) {
			gstat_add_name(name,id,TRUE);
		}
	}
	fclose(f);
    return TRUE;
}
int gstat_find(char *s)
{
	int i;

	for (i=0; i<ngstat; i++) {
		if (strcmp(s,gstat_name[i])==0) return i;
	}
	return -1;
}
int gstat_add_name(char *s, int id, int isloading)
{
	FILE *f;

	if ((ngstat+5)>=MAXID) {
		emsg("Too many names in gstat.nam\n");
		return FALSE;
	}

	if (!isloading) {
		f = fopen(file_nam,"a");
		if (f!=NULL) {
			fprintf(f,"%d:%s\n",id,s);
			fclose(f);
		}
	}
	gstat_name[ngstat] = strdup(s);
	gstat_id[ngstat] = id;
	ngstat++;
	return TRUE;
}
int gstat_findadd(char *s)
{
	int id;
	int i;
	i = gstat_find(s);	if (i>=0) return i;
	gstat_load();
	i = gstat_find(s);	if (i>=0) return i;
	id = gstat_nextid();
	gstat_add_name(s,id,FALSE);
	i = gstat_find(s);
	return i;	
}
/* DNEWS interface, */
static char *xnames[100];
static int nx;
static int xval[100];
static int xtime[100];
void gstat_xadd(char *name, int val)
{
	int i;
	for (i=0; i<nx; i++) {
		if (strcmp(xnames[i],name)==0) break;
	}
	if (i>50) return;
	if (i>=nx) {
		i = nx++;
		xnames[i] = strdup(name);
	}
	xval[i] += val;
	if (xtime[i] == 0) xtime[i] = vtime(NULL);
}
void gstat_xsend(void)
{
	int i;
	int now = vtime(NULL);
	int age;
	FILE *f;

	f = fopen(file_dat,"ab");
	if (f==NULL) {emsg("Can't append to %s %s\n",file_dat,strerror(errno)); return ;}
	for (i=0; i<nx; i++) {
		age = now - xtime[i];
		if (age<=0) age = 1;
		if (xval[i]>=0) {
			gstat_add_f(xnames[i],xval[i]/age,f);
		}
		xval[i] = 0;
		xtime[i] = now;
	}
	fclose(f);
}

/*-------------------gstats---------------------------*/
