#ifndef WIN32
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#endif

#include "debug.h"
#include "lib.h"
#include "md5.h"
#include "thread.h"
#include "lprintf.h"
#include "tlib.h"
#include "emsg.h"
int main_thread_free_one(Thread *th);
static Mutex *m;
void t_mutex_init(void)
{
	if (m==NULL) m = th_mutex_new("tlib.c");
}
static char *save_file;
static int save_line;
static int save_id;
extern int crash_line;
static int nheld;
#define MHASH 20000
static int mutex_msec[MHASH];
static int mutex_line[MHASH];
static char *mutex_filename[MHASH];
static int msec_start;
static int isexit;
char *tlib_tempfile(char *fname)
{
	static int c;
	lprintf(fname,BFSZ-1,"tmp/tlib%d_%d.tmp",th_id(),c++);
	return fname; 
}
int t_mutex_wait_real(char *file, int line)
{
	if (m==NULL) return TRUE;
	if (save_id==th_id()) {
		nheld++;
		return TRUE;
	}
	if (!th_mutex_wait(m,10000)) { /* 30 seconds */
		if (!th_mutex_wait(m,5000)) { /* another 10 seconds, incase it came back to life*/
//			log_startstop("Mutex failed held at %s %d, wanted by %s %d crash_line=%d\n",save_file,save_line,file,line,crash_line);
			emsg("Mutex failed held at %s %d, wanted by %s %d\n",save_file,save_line,file,line);
			t_mutex_dump();
			lib_abort();  // IT IS CRASHING HERE ??? 
		}
	}
	nheld++;
	save_file = file;
	save_line = line;
	save_id = th_id();
	msec_start = lib_msec_now();
	return TRUE;
}
void t_mutex_release(void)
{
	int h;
	if (m==NULL) return;
	nheld--;
	if (nheld>0) {
		//imsg("t_mutex_release: fake release nheld=%d\n",nheld);
		return; // not all free yet. 
	}
	save_id = -1;
//	imsg("t_mutex_release: real release nheld=%d\n",nheld);
	crash_line=-1;

	h = save_line % MHASH;
	mutex_msec[h] = lib_msec_now() - msec_start;
	mutex_line[h] = save_line;
	mutex_filename[h] = save_file;

	th_mutex_release(m);
}


void t_mutex_dump(void)
{
	FILE *f;
	int i;
	f = fopen("mutex.log","w");
	if (f==NULL) return;
	for (i=0; i<MHASH; i++) {
		if (mutex_msec[i]!=0) fprintf(f,"%d %s:%d\n",mutex_msec[i],mutex_filename[i],mutex_line[i]);
	}
	fclose(f);
}

ThreadData *td_new(void)
{
	ThreadData *d;
	d = calloc(1,sizeof(ThreadData));
	return d;
}
void td_free(ThreadData *td)
{
	t_mutex_wait();
	t_mutex_release();
	
	if (td->th!=NULL) {
		main_thread_free_one(td->th);
	}
	td->th = NULL;
	free(td);
}

int main_setexit(void)
{
	isexit = TRUE;
	tcp_set_exit();
	imsg("main: setexit called, shutdown now in progress\n");
	return TRUE;
}
int is_exit(void)
{
	return isexit;
}
char *work_dir(void)
{
	return "./work";
}
char *log_file(char *file, char *tmp)
{
	lprintf(tmp,BFSZ-1,"%s/%s",log_dir(),file);
	return tmp;
}
char *log_dir(void)
{
	return "./log";
}
static Threadlist *tlist;
int main_th_free(Thread **th)
{
	th_free(th);
	return TRUE;
}
int main_thread_free_one(Thread *th)
{
	Threadlist *t,**next;
	t_mutex_wait();
	next = &tlist;
	if (th==NULL) {
		t_mutex_release();
		return TRUE;
	}
	for (t=tlist;t!=NULL;t=t->next) {
		if (t->th==th) {
			main_th_free(&t->th);
			*next = t->next;
			main_thread_free(t);
			t_mutex_release();
			return TRUE;
		}
		next = &(t->next);
	}
	t_mutex_release();
	imsg("Unable to find thread to kill %p\n",th); lib_abort();
	return TRUE;
}
int main_thread_free_me(void)
{
	Threadlist *t,**next;
	t_mutex_wait();
	next = &tlist;
	for (t=tlist;t!=NULL;t=t->next) {
		if (t->thid==th_id()) {
			main_th_free(&t->th);
			*next = t->next;
			main_thread_free(t);
			t_mutex_release();
			return TRUE;
		}
		next = &(t->next);
	}
	t_mutex_release();
	imsg("Unable to find thread to kill %d\n",th_id()); lib_abort();
	return TRUE;
}


Threadlist *main_thread_new(char *threadname,char *src, int line)
{
	Threadlist *t;
	t = d_calloc(1,sizeof(Threadlist),src,line);
	t->threadname = strdup(threadname);
	return t;
}
int main_thread_free(Threadlist *tlist)
{
	free(tlist->threadname);
	free(tlist);
	return TRUE;
}
int main_thread_dump(void)
{
	Threadlist *t;
	t_mutex_wait();
	for (t=tlist;t!=NULL;t=t->next) {
		printf("Thread still in list (%s)\n",t->threadname,t->state);
	}
	t_mutex_release();
	return TRUE;
}
Wordlist *main_thread_info(void)
{
	Wordlist *top=NULL;
	char bf[BFSZ];
	Threadlist *t;
	t_mutex_wait();
	for (t=tlist;t!=NULL;t=t->next) {
		lprintf(bf,BFSZ-1,"%s|%s\n",t->threadname,t->state);
		word_add(&top,bf);
	}
	t_mutex_release();
	return top;

}

int main_thread_store(Threadlist *tl, Thread *th)
{
	// this is always called with mutex...
	if (th==NULL) {
		main_thread_free(tl);
	} else {
		if (tl!=NULL) {
			tl->next = tlist;
			tl->th = th;
			tl->thid = th_id();
			tl->last = time(NULL);
			tlist = tl;
		}
	}
	return TRUE;
}
int main_new_thread(Chan *newchan, int (*v_fn)(void *x), char *threadname)
{
	int ntry=0;
	static int nthread_start;
	int reuse=FALSE;
	Thread *h=NULL;
	time_t start;
	int stack=8192*8;
	Threadlist *tl=NULL;
	static int nfails=0;

//	imsg("New Thread (%s) stack %d  nthread_start=%d\n",threadname,stack,nthread_start);
	// Chrisp added so 'logging in' things will show an ip address.

	t_mutex_wait();
	tl = main_thread_new(threadname,__FILE__,__LINE__);
	nthread_start++;
	tl->data = (void *) newchan;
	
	start = time(NULL);
	h = th_new_stack(v_fn,tl,stack);
	//imsg("main_new_thread: thread_create %d seconds %d total\n",time(NULL)-start,nthread_start);

	main_thread_store(tl,h);
	if (h==NULL) {
		t_mutex_release();
		nfails++;
		emsg("Thread creation failed, possibly fatal problem - trying to continue nfails=%d %s\n",nfails,strerror(errno));
		if (nfails>100) lib_abort();
	} else {
		t_mutex_release();
		nfails = 0;
	}
	return TRUE;
}


int ini_reload(void)
{
	//limit_peer[P_IN] = ini_geti_nolimit(I_SPEED_CLIENT_IN);
	return TRUE;
}
#define BIGBF 5000
int clib_listen_list(Chan *clist[], int max, char *list, int dflt_port)
{
	/* List = port,ip.ip.ip:port,port */
	char *s;
	char *upto;
	char host[BFSZ];
	char bf[BIGBF];
	char tmp3[BFSZ];
	int port;
	int n=0;
	int i;
	int ret=0;

	strncpy(bf, list, BIGBF-1);
	for (i=0; i<max; i++) clist[i] = NULL;
	s = mystrtok(bf,", \t",&upto);
	for (;s!=NULL;s=mystrtok(NULL,", \t",&upto)) {
		if (strcmpnc(s,"disabled")==0) {ret=1; goto done;}
		if (strcmpnc(s,"disable")==0) {ret=1; goto done;}
		if (strcmpnc(s,"none")==0) {ret=1; goto done;}
		if (strcmpnc(s,"-1")==0) {ret=1; goto done;}
		ncpy(host,"all interfaces",14);
		port = dflt_port;
		ncpy(host,s,BFSZ-1);
		s = strchr(host,':');
		if (s!=NULL) {
			*s = 0;
			port = atoi(s+1);
		} else {
			if (strchr(host,'.')!=NULL) {
				port = dflt_port;
			} else {
				port = atoi(host);
				ncpy(host,"all interfaces",14);
			}
		}
		imsg("Listening on (%s:%d)\n",host,port);
		if (strcmp(host,"all interfaces")==0) strcpy(host,"");
		if (!tcp_listen(&(clist[n]),port,host,TRUE)) {
			imsg("Listen failed for %s:%d %s\n",host,port,tcp_error(clist[n],tmp3) );	
			tcp_free(&clist[n]);
		} else {
			tcp_close_on_exec(clist[n]); // cp added for freebsd
			n++;
		}
		if (n+3>max) break;
	}
	if (n==0 && dflt_port>=0) {
		imsg("Listening on (all interfaces:%d)\n",dflt_port);
		if (!tcp_listen(&(clist[n]),dflt_port,"",TRUE)) {
			imsg("Listen failed for %s:%d %s\n","",dflt_port,tcp_error(clist[n],tmp3) );	
			tcp_free(&clist[n]);
		} else {
			tcp_close_on_exec(clist[n]); // cp added for freebsd
			n++;
		}

	}
done:
	clist[n] = NULL;
	clist[n+1] = NULL;
	return n+ret;
}
int clib_accept_list(Chan *vlisten[], Chan **newchan)
{
	int i;
	int r;

	for (i=0; vlisten[i]!=NULL; i++) {
		if (tcp_waitfor(vlisten[i],0)) {
			r = tcp_accept(vlisten[i],newchan,"clibxxc");
			if (r) {
				char ip[BFSZ];
				char me[BFSZ];
				tcp_getip(*newchan,ip);
				tcp_getmyip(*newchan,me);

			}
			return r;
		}
	}
	return FALSE;
}
int ulimit_stack_get(void)
{	
#ifndef WIN32
	struct rlimit r;
	getrlimit(RLIMIT_STACK, &r);
	return r.rlim_cur;
#endif
	return 0;
}




typedef struct OLOCK {char *name; int nref; time_t when;  int thid; char *file; int line; struct OLOCK *next; char *whence;} Objectlock;
#define OHASH 1000
static Objectlock *objects[OHASH];
// Ok lets make the object locking efficient...
int slib_lock_object_real(char *obj, int timeout, char *file, int line, char *whence)
{
		// WARNING, ONLY UNLOCK AN OBJECT IF THE LOCK SUCCEEDS.
	Objectlock *o,*was=NULL;
	char *wfile="none";
	int wline=0;
	int h;
	int tid=th_id();

	time_t start = time(NULL);
	h = lib_str_hash(obj,OHASH);
again:
	t_mutex_wait();
	was = NULL;
	for (o=objects[h]; o!=NULL;o=o->next) {
		if (strcmp(o->name,obj)==0) {
			if (o->thid == tid) {
				o->nref++;
				t_mutex_release();
				return TRUE;
			} else was=o;
			if (was!=NULL) {
				wfile = was->file;
				wline = was->line;
			}
			t_mutex_release();
			lib_msleep(20);
			if (time(NULL)-start > timeout) {
				if (time(NULL)-start > 2) {
					emsg("object: Could not get lock for (%s) held by %s:%d\n",obj,wfile,wline);
				} else {
					imsg("object: Could not get lock for (%s) held by %s:%d\n",obj,wfile,wline);
				}
				return FALSE;
			}
			goto again;
		}
	}
	// Ok we got the lock, add to linked list

	o = calloc(1,sizeof(Objectlock));
	o->name = strdup(obj);
	o->thid = tid;
	o->next = objects[h];
	o->when = time(NULL);
	o->whence = whence; 
	if (whence==NULL) o->whence = "none";
	o->nref = 1;
	o->line = line;
	o->file = file;
	objects[h] = o;
	t_mutex_release();
	return TRUE;	
}
int quota_add(char *obj, double x)
{
	return 0;
}
int quota_get_used(char *obj)
{
	return 0;
}
char *mini_logpath(char *fname, char *tmp)
{
	lprintf(tmp,BFSZ-1,"log/%s",fname);
	return tmp;
}
char* get_userdom(char *userdom, char *user, char *dom)
{
	// If it proves necessary maybe handle g_domain_separator too
	if (strchr(user,'@')==NULL) {
		lprintf(userdom,BFSZ-1,"%s@%s",user,dom);
	}else{
		ncpy(userdom,user,BFSZ-1);
	}
	return userdom;
}

#define LBFSZ 32000
int file_md5(FILE *f,int maxitem,char *md5)
{
	char *bf;
	int want;
	unsigned char buffer[64];
	unsigned int ctx[6];
    unsigned char md5sum[BFSZ];

    md5_init( ctx );
	md5_update( ctx, "Secret code to confuse hackersxxxxx", 30 ,buffer);
	bf = malloc(LBFSZ);
	  for (;!feof(f);) {
		int r;
		want = LBFSZ-1;
		if (want>maxitem) want = maxitem;
		r = fread(bf,1,want,f);
		if (r<=0) break;
		md5_update( ctx, bf, r ,buffer);

		maxitem -= r;
		if (maxitem<=0) break;
	  }
	free(bf);	
    md5_finish( ctx, buffer,md5sum);
	lib_bin_to_hex(md5sum,16,md5,BFSZ-1);
	return TRUE;
}


int slib_unlock_object_real(char *obj, char *file, int line)
{
	// WARNING I WILL CRASH IF YOU UNLOCK AN OBJECT THAT IS NOT LOCKED :-)	
	int tid=th_id();
	int h;
	Objectlock *o,**pnext;
	t_mutex_wait();
	h = lib_str_hash(obj,OHASH);
	pnext = &objects[h];
	for (o=objects[h]; o!=NULL;o=o->next) {
		if (strcmp(o->name,obj)==0) {
			if (o->thid!=tid) {
				t_mutex_release();
				emsg("FATAL ATTEMPT TO RELEASE AN OBJECT OWNED BY ANOTHER THREAD (%s) (%s:%d) %d!=%d\n",obj,file,line,tid,th_id());
				abort();
			}
			if (o->nref<=1) {
				*pnext = o->next;
				free(o->name);
				free(o);
				t_mutex_release();
				return TRUE;
			} else {
				o->nref--;
				t_mutex_release();
				return TRUE;
			}
		}
		pnext = &o->next;
	}
	t_mutex_release();
	emsg("slib: could not find object to release (%s) so crashing (%s:%d)\n",obj,file,line);
	abort();
	return FALSE;
}

