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

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "dfopen.h"
#include "lib.h"
#include "limit.h"
#include "lprintf.h"
#include "tcplib.h"
#include "emsg.h"
// removed to reduce memory usage.
// added back in by chrisp as it's essential for dfopen to be used or file gets locked.
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

//name=string ip=string groups=string con=int 
//speed=int daily=int monthly=int break=bool byip=bool

#define USERLEN 50
typedef struct FDATA {char userip[USERLEN]; Ulimit cur; int rday; int valid; int fill[3]; } Fdata;
#define HASHSZ 2000
static Flimit *utable[HASHSZ];
static FILE *lfile;
int  imsg_limit(const char * arg_list, ...);
#define imsg imsg_limit

int fl_free(Flimit *fl);
int limit_write_todisk_f(FILE *f, Flimit *fl);
int limit_hash(char *s)
{
	return lib_str_hash(s,HASHSZ);
}
#define FILE_ULIMIT "ulimit2.dat"
int limit_init(void)
{
	if (!limit_mutex_wait(__LINE__)) lib_abort();
	limit_init_one(FILE_ULIMIT);
	limit_mutex_release();
	return TRUE;
}
int limit_exit(void)
{
	if (!limit_mutex_wait(__LINE__)) return FALSE;
	if (lfile!=NULL) fclose(lfile); lfile = NULL;
	limit_mutex_release();
	return TRUE;
}
int limit_rewrite(void)
{
	int i;
	FILE *f;
	Flimit *fl=NULL;
	if (lfile!=NULL) fclose(lfile); 	lfile = NULL;
	f = fopen("ulimit.dat_new","wb");
	if (f==NULL) return FALSE;
	for (i=0; i<HASHSZ; i++) {
		for (fl=utable[i];fl!=NULL;fl=fl->next) {
			limit_write_todisk_f(f,fl);
		}
	}
	fclose(f);
	if (remove("ulimit.dat_old")!=0) emsg("remove of ulimit.dat_old failed %s\n",strerror(errno));
	if (rename(FILE_ULIMIT,"ulimit.dat_old")!=0) emsg("rename of ulimit.dat to  ulimit.dat_old failed %s\n",strerror(errno));
	if (rename("ulimit.dat_new",FILE_ULIMIT)!=0) emsg("rename ulimit failed %s\n",strerror(errno));
	if (lfile!=NULL) fclose(lfile); 	lfile = NULL;

	return TRUE;
}
int limit_init_one(char *fname)
{
	Fdata fd;
	FILE *f;
	int bad=FALSE;
	int anyvalid=FALSE;
	Flimit *p;
	int sz;
	char bf[BFSZ];
	/* Load in index of limits from file into hashed table */
	sz = lib_file_size(fname);
	if (sz>0) if (sz%sizeof(fd) !=0) {
		int fix = sizeof(fd) - (sz%sizeof(fd));
		memset(bf,0,90);
		f = fopen(fname,"ab");
		fwrite(bf,1,fix,f);
		fclose(f);
		emsg("limit: Corrected size by adding %d bytes\n",fix);
		printf("limit: Corrected size by adding %d bytes\n",fix);
	}
	f = fopen(fname,"rb");
	if (f==NULL) goto ret;
	for (;!feof(f);) {
		if (fread(&fd,sizeof(fd),1,f)!=1) break;
		if (fd.cur.monthly>1000000000000.0) continue;
		if (fd.cur.daily>1000000000000.0) continue;
		if (fd.valid==3) anyvalid = TRUE;
		if (anyvalid) if (fd.valid!=3) {
			printf("Ignoring invalid record %d (%s) %g\n",fd.valid,fd.userip,fd.cur.monthly);
			emsg("Ignoring invalid record %d (%s) %g\n",fd.valid,fd.userip,fd.cur.monthly);
			bad = TRUE;
			continue;
		}
		if (strlen(fd.userip)>48) continue;
		fd.cur.ncon = 0;
		fd.valid = 3;
		p = limit_find(fd.userip);
//printf("Raed in (%s) usage %f\n",fd.userip,fd.cur.monthly);
		if (p!=NULL) {
			p->cur = fd.cur;
			p->cur.ncon = 0;
			if (fd.rday!=0) p->rday = fd.rday;
			continue;
		}
		p = limit_create(fd.userip);
		p->cur = fd.cur;
		p->cur.ncon = 0; // Reset on startup
		if (fd.rday!=0) p->rday = fd.rday;
	}
	fclose(f);
	if (bad) limit_rewrite();
ret:	return	 TRUE;
}
Flimit *limit_find(char *xuser)
{
	Flimit *p;
	int h;
	char user[BFSZ];
	ncpy(user,xuser,BFSZ-1);
	strlwr(user);
	h = limit_hash(user);
	for (p=utable[h];p!=NULL;p=p->next) {
		if (strcmp(p->userip,user)==0) return p;
	}
	return NULL;
}
Flimit *limit_create(char *user)
{
	int h;
	Flimit *p;
	p = calloc(1,sizeof(Flimit));
	p->userip = strdup(user);
	strlwr(p->userip);
	h = limit_hash(p->userip);
	p->next = utable[h];
	p->nref = 1;
	utable[h] = p;
	return p;
}
Flimit *limit_login(char *xuser, Ulimit *u, int localonly, int rday)
{
	int h;
	Flimit *p;
	char user[BFSZ];
	ncpy(user,xuser,BFSZ-1);
	strlwr(user);
	h = limit_hash(user);
	if (rday>33) rday = 33;
	if (!limit_mutex_wait(__LINE__)) return NULL;
	p = limit_find(user);
	if (p==NULL) {
		p = limit_create(user);
		p->wused = 0;
	}
	if (rday!=0) p->rday = rday;
	p->max = *u;
	p->nref++;
	p->cur.ncon++;
	limit_mutex_release();
	if (!localonly) limit_wide_login(p);
	return p;
}
int limit_add(Flimit *fl, double bytes, char *reason)
{
	if (fl==NULL) return TRUE;
	fl->cur.daily += bytes;
	fl->cur.monthly += bytes;

	if (fl->max.daily>0 && fl->cur.daily > fl->max.daily) {
		lprintf(reason,BFSZ-1,"%s Daily limit exceeded %.0fk>%.0fk",fl->userip,fl->cur.daily/1000,fl->max.daily/1000);
		return FALSE;
	}
	if (fl->max.monthly>0 && fl->cur.monthly > fl->max.monthly) {
		lprintf(reason,BFSZ-1,"%s Monthly limit exceeded %.0fk>%.0fk",fl->userip,fl->cur.monthly/1000.0,fl->max.monthly/1000.0);
		return FALSE;
	}
	if (fl->max.ncon>0 && fl->cur.ncon > fl->max.ncon) {
		lprintf(reason,BFSZ-1,"%s Connection limit exceeded %d>%d",fl->userip, fl->cur.ncon,fl->max.ncon);
		return FALSE;
	}
	fl->wused += bytes;
	return TRUE;
}
int limit_logout(Flimit *fl, int localonly)
{
	if (!localonly) limit_wide_logout(fl);
	limit_mutex_wait(__LINE__);
	fl->cur.ncon--;
	if (fl->cur.ncon<0) fl->cur.ncon = 0; // Reset if negative
//printf("limit_logout (nref=%d) rday=%d\n",fl->nref,fl->rday);
	if (fl->nref<=2) {
		/* Write to disk */
		limit_write_todisk(fl);
	}
	fl_free(fl); // free ref and structure if it's ref is zero
	limit_mutex_release();
	return TRUE;
}
int limit_write_todisk(Flimit *fl)
{
	if (lfile==NULL) lfile = fopen(FILE_ULIMIT,"ab");
	if (lfile==NULL) return FALSE;
	return limit_write_todisk_f(lfile,fl);
}
int limit_write_todisk_f(FILE *f, Flimit *fl)
{
	Fdata fd;
	if (f==NULL) return FALSE;
	memset(&fd,0,sizeof(Fdata));
	fd.cur = fl->cur;
	fd.rday = fl->rday;
	fd.valid = 3;
	ncpy(fd.userip,fl->userip,USERLEN-1);
	fwrite(&fd,sizeof(fd),1,f);
	return TRUE;
}
int limit_newday(void)
{
	Flimit *fl;
	int i;

	limit_mutex_wait(__LINE__);
	for (i=0; i<HASHSZ; i++) {
		for (fl=utable[i];fl!=NULL;fl=fl->next) {
			fl->cur.daily = 0;			
			if (fl->cur.monthly<0) fl->cur.monthly = 0;
			fl->cur.ncon = 0; // Reset each night.
		}
	}
		
	limit_rewrite();	
	limit_mutex_release();
	return TRUE;
}
int limit_reset(char *uname)
{
	Flimit *fl;
	limit_mutex_wait(__LINE__);
	fl = limit_find(uname);
	if (fl!=NULL) {
		fl->cur.daily = 0;			
		fl->cur.monthly = 0;
		limit_write_todisk(fl);
	}		
	limit_mutex_release();
	return TRUE;
}
double limit_monthly(char *uname)
{
	Flimit *fl;
	double r=0;
	limit_mutex_wait(__LINE__);
	fl = limit_find(uname);
	if (fl!=NULL) {
		r = fl->cur.monthly;
	}		
	limit_mutex_release();
	return r;
}
int limit_monthly_report(void)
{
	struct tm atm;
	char fname[BFSZ];
	time_t t = time(NULL);
	struct tm *tm;
	tm = safe_localtime(&t,&atm);
	lprintf(fname,BFSZ-1,"limit/limit_%02d_%02d_%02d.txt",tm->tm_year-100,tm->tm_mon+1,tm->tm_mday);
	lib_mkdir("limit",0770);
	limit_report(fname,NULL,NULL,NULL);
	return TRUE;
}
int limit_showuser(char *user, double *val)
{
	Flimit *fl;
	int i;
	int ret=FALSE;

	*val = 0;
	limit_mutex_wait(__LINE__);
	for (i=0; i<HASHSZ; i++) {
		for (fl=utable[i];fl!=NULL;fl=fl->next) {
			if (strcmpnc(fl->userip,user)==0) {
				*val = fl->cur.monthly;	
				ret = TRUE;
				goto done;
			}
		}
	}
done:
	limit_mutex_release();
	return ret;
}
int limit_report(char *fname, void (*pfn)(void *p1, char *bf, int len), void *p1, Wordlist *wanted)
{
	Flimit *fl;
	int i;
	char bf[BFSZ];
	FILE *f;
	Wordlist *w;
	int nusers=0;
	f = fopen(fname,"a");
	if (f==NULL) {
		emsg("Cannot write to %s %s\n",fname,strerror(errno));
		return FALSE;
	}

	lprintf(bf,BFSZ-1,"User/IP  	        Month	        ToDay	NCon	Rday	Nref (tab delimited)\r\n");
	fprintf(f,"%s",bf);
	if (pfn!=NULL) pfn(p1,bf,strlen(bf));

	limit_mutex_wait(__LINE__);
	for (i=0; i<HASHSZ; i++) {
		for (fl=utable[i];fl!=NULL;fl=fl->next) {
			if (wanted) {
				for (w=wanted;w!=NULL;w=w->next) {
					if (strcmpnc(w->word,fl->userip)==0) goto gotit;
				}
				continue;
			}
gotit:
			lprintf(bf,BFSZ-1,"%s\t%12.0fk\t%12.0fk\t%d\t%d\t%d\r\n",fl->userip,fl->cur.monthly/1000.0,fl->cur.daily/1000.0,fl->cur.ncon,fl->rday,fl->nref);
			fprintf(f,"%s",bf);
			nusers++;
			if (pfn!=NULL) pfn(p1,bf,strlen(bf));
		}
	}
	limit_mutex_release();

	lprintf(bf,BFSZ-1,"Total Users %d\r\n",nusers);
	fprintf(f,"%s",bf);
	if (pfn!=NULL) pfn(p1,bf,strlen(bf));
	
	fclose(f);
	return TRUE;
}
int fl_free(Flimit *fl)
{
	fl->nref--;
	if (fl->nref==0) {
		free(fl->userip); fl->userip = NULL;
		free(fl);
	}
	return TRUE;
}
int limit_newmonth(int day)
{
	Flimit *fl,*next,**pnext;
	int i;
	FILE *f;
	char tmp[BFSZ];
	int kill;
	int isnorm = FALSE;
	if (day==1) isnorm = TRUE;
	if (day>28) day = 28;
	if (isnorm) limit_monthly_report();
	else limit_monthly_report();
	limit_mutex_wait(__LINE__);
	if (day==1) remove("limit_day.log");
	f = fopen("limit_day.log","a");
	for (i=0; i<HASHSZ; i++) {
		pnext = &utable[i];
		for (fl=utable[i];fl!=NULL;fl=next) {
			next = fl->next;
			*pnext = fl;
			kill = FALSE;
			if (fl->rday>28) fl->rday = 28;
			if (fl->rday==day) kill = TRUE;
			if (isnorm && fl->rday==0) kill = TRUE;
			if (kill) {
				if (f!=NULL) fprintf(f,"%s Kill %s %fk %fk rday=%d today=%d\n",lib_emsg_time(tmp),fl->userip,fl->cur.monthly/1000.0,fl->cur.daily/1000.0,fl->rday,day);
				fl_free(fl);
				*pnext = NULL;
			} else {
				if (f!=NULL) fprintf(f,"%s Keep %s %fk %fk rday=%d today=%d\n",lib_emsg_time(tmp),fl->userip,fl->cur.monthly/1000.0,fl->cur.daily/1000.0,fl->rday,day);
				pnext = &fl->next;
			}
		}
		//utable[i] = NULL;
	}
	if (f!=NULL) fclose(f);
	limit_rewrite();	
	limit_mutex_release();
	return TRUE;
}
/*

if quota_host is defined, then, sit it loop waiting for mutex
grab mutex
open link quota_host if not open
send request:
	xquota_login username|ipaddress
	xquota_logout username|ipaddress
	xquota_add username|ipaddress bytes

Response in all cases:
	200 username|ipaddress daily xxmb monthly nnmb ncon zz


*/
static Chan *chan;
static int busy;


int limit_wide_login(Flimit *fl)
{
	return limit_wide_cmd(fl->userip,&fl->cur,"login",0,fl->rday);
}
int limit_wide_logout(Flimit *fl)
{
	int r;
	double used;
	limit_mutex_wait(__LINE__);
		used = fl->wused;
		fl->wused = 0;
	limit_mutex_release();

	r = limit_wide_cmd(fl->userip,&fl->cur,"logout",used,fl->rday);
	return r;
}
static char limit_host[BFSZ];
static char limit_pass[BFSZ];
int limit_wide_setup(char *host, char *pass)
{
	ncpy(limit_host,host,BFSZ-1);
	ncpy(limit_pass,pass,BFSZ-1);
	return TRUE;
}
static int is_master;
int limit_wide_cmd(char *userip, Ulimit *used, char *cmd, double add, int rday)
{
	char *p[100];
	int np;
	char bf[BFSZ];
	if (strlen(limit_host)==0) return FALSE;
	if (strlen(limit_pass)==0) return FALSE;
	if (is_master) {
		imsg("is_master: so not sending to widearea host (%s)\n",limit_host);
		return FALSE;
	}

	if (!limit_wide_mutex_wait()) return FALSE;
	if (chan==NULL) tcp_new(&chan);
	while (TRUE) {
		if (!tcp_isopen(chan)) {
			imsg("limit: open connection (%s)\n",limit_host);
			if (!tcp_open(chan,limit_host,119,"")) {
				imsg("limit: could not open link (%s)\n",limit_host);
				break;
			}
			if (!tcp_readline(chan,bf,BFSZ-1)) goto failed;
			imsg("limit: connected (%s)\n",bf);
		}
		break;
	}
	imsg("limit: -send-> xwide %s %s %s %f %d\r\n",limit_pass,cmd,userip,add,rday);
	tcp_printf(chan,"xwide %s %s %s %f %d\r\n",limit_pass,cmd,userip,add,rday);
	if (!tcp_readline(chan,bf,BFSZ-1)) {
		char tmp[BFSZ];
		imsg("limit: Cannot read from channel %s\n",tcp_error(chan,tmp));
		goto failed;
	}
	imsg("limit: -read-> %s\n",bf);
	np = token_split(bf,p," ",10);
	// 200 username dailymb monthlymb ncon
	if (np ==5) {
		if (strcmpnc(p[1],userip)!=0) {
			emsg("limit: Invalid response (%s) (%s)\n",p[1],userip);
			imsg("limit: Invalid response (%s) (%s)\n",p[1],userip);
			tcp_close_nice(chan);
		} else {
			used->daily = atof(p[2]);	
			used->monthly = atof(p[3]);	
			imsg("Set limits %s %g %g\n",userip,used->daily,used->monthly);
		}
		// used->ncon = atof(p[4]);		
	}
failed:
	limit_mutex_wait(__LINE__);
	busy = FALSE;
	limit_mutex_release();
	return TRUE;
}
int limit_wide_mutex_wait(void)
{
	time_t start=time(NULL);
	limit_mutex_wait(__LINE__);
	for (;TRUE;) {
		if (!busy) {	
			busy = TRUE;
			break;			
		}
		limit_mutex_release();
		if (time(NULL)-start>5) {
			emsg("Gave up waiting for limit channel\n");
			return FALSE;
		}
		lib_msleep(100);
		limit_mutex_wait(__LINE__);
	}
	limit_mutex_release();
	return TRUE;
}
int limit_changes(Flimit *fl,char *amount,char *reason)
{
	FILE *f;
	char tmp[BFSZ];
	f = fopen("limit_change.log","a");
	if (f!=NULL) {
		fprintf(f,"%s %s %.0fmb %.0fmb %s\n",lib_emsg_time(tmp),fl->userip,fl->cur.monthly/1000000.0,atof(amount)/1000000.0,reason);
		fclose(f);
	}
	if (lib_file_size("limit_change.log")>50000000) {
		remove("limit_change2.log");
		rename("limit_change.log","limit_change2.log");
	}
	return TRUE;
}
int limit_wide_client(char *ip,char *cmd, char *response)
{
	Flimit *fl;
	Ulimit u;
	char *p[100];
	char reason[BFSZ];
	int np;
	int rday=0;
	memset(&u,0,sizeof(Ulimit));

	np = token_split(cmd,p," \r\n\t",20);
	if (np<5) {
		lprintf(response,BFSZ-1,"501 Incorrect parameter count (%d) wanted (%d)",np,5);
		return TRUE;
	}
	if (np>5) rday = atoi(p[5]);
	if (strcmp(p[1],limit_pass)!=0) {
		lprintf(response,BFSZ-1,"501 Incorrect limit password");
		emsg("limit: Incorrect password set (%s) (%s)\n",limit_pass,p[1]);
		return TRUE;
	}
	is_master = TRUE;
	if (strcmp(p[2],"login")==0) {
		fl = limit_login(p[3],&u,TRUE,rday);
		lprintf(response,BFSZ-1,"200 %s %f %f %d",fl->userip,fl->cur.daily,fl->cur.monthly,0);
		limit_logout(fl,TRUE);
	} else if (strcmp(p[2],"logout")==0) {
		int before,after;
		fl = limit_login(p[3],&u,TRUE,0);
		before = (int) (fl->cur.monthly / 10000000);
		after = (int) ((fl->cur.monthly+atof(p[4])) / 10000000);
		if (after>before) {
			limit_changes(fl,p[4],ip);
		}
		limit_add(fl,atof(p[4]),reason);
		lprintf(response,BFSZ-1,"200 %s %f %f %d",fl->userip,fl->cur.daily,fl->cur.monthly,0);
		limit_logout(fl,TRUE);
	} else  {
		lprintf(response,BFSZ-1,"501 Invalid command (%s)",p[2]);
		return TRUE;
	}
	return TRUE;
}
