#ifdef __XCODE__
#include "xcode_quiet.h"
#endif

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#define DMID 8
#include "debug.h"
#include "qpair.h"
#include "lib.h"

#ifndef true
#define true 1
#endif
#ifndef false
#define false 0
#endif
/*
Like pair, but much faster if you are dealing with large pair lists.

qpair uses a hash-table and a single malloc (or possibly a few more if you
add lots of pairs) for the entire structure
that is freed when you free the entire pair list
*/

#ifndef iseol
#	define iseol(x) (x == '\r' || x == '\n' || x == '\0')
#endif

QPairList *qpair_new_do_x(int hsize, int initial_qmalloc_chunk_size, Qmalloc *qm, char *file, int line)
{
	int msize;
	QPairList *qp;
	Qmalloc *free_qmalloc=NULL;
	if (!qm) {
		qm=qmalloc_new_do(initial_qmalloc_chunk_size,file,line);
		free_qmalloc=qm;
	}
	qp=qmalloc(qm,sizeof(QPairList));
	qp->qmalloc=qm;
	qp->free_qmalloc=free_qmalloc;
	qp->hash_size=hsize;
	qp->hash_fn=qpair_str_hash;
	qp->start=NULL;

	msize=hsize*sizeof(QPair *);
	qp->htable=(QPair**)qmalloc(qp->qmalloc,msize);
	memset(qp->htable,0,msize);
	return qp;
}


QPairList *qpair_new_x(int hsize, char *file, int line)
{
	return qpair_new_do_x(hsize,hsize*10,NULL,file,line);
}

int qpair_str_hash(char *s, int max)
{
	unsigned int total=0;
	int r;
	int i;
	for (i=0; *s!=0; ) {
		total += *s++;
		if (*s==0) break;
		total += *s++ << 8;
		if (*s==0) break;
		total += *s++ << 16;
		if (*s==0) break;
		total += *s++ << 24;
		if (*s==0) break;
	}
    r = total % max;
    if (r<0) return -r;
    return r;
}

QPairList *qpair_copy(QPairList *plist)
{
	QPair *p;
	QPairList *ret=NULL;

	for(p=plist->start; p!=NULL; p = p->next) 	qpair_add(&ret,p->var,p->val);
	return ret;	
}
char *qpair_find(QPairList *qp, char *name)
{
	QPair *p=qpair_find_element(qp,name);
	if (p && p->val) return p->val;
	return "";
}

char *qpair_find_null(QPairList *qp, char *name)
{
	QPair *p=qpair_find_element(qp,name);
	if (p && p->val) return p->val;
	return NULL;
}

int qpair_find_int(QPairList *qp, char *name)
{
	return atoi(qpair_find(qp,name));
}
time_t qpair_find_time(QPairList *qp, char *name)
{
	return str_to_time(qpair_find(qp,name));
}

QPairList *qpair_dup_x(QPairList *qp,  char *file, int line)
/*
Only works if duplicate entries are not allowed.
*/
{
	QPairList *ret=NULL;
	QPair *p;
	if (!qp) return NULL;
	for(p=qp->start;p;p=p->next) {
		if (p->val)
			qpair_set_do(&ret,p->var,p->val,false,file,line);
	}
	
	return ret;
}

int qpair_eliminate_leaks(QPairList **qpp)
/*
If too much memory has been leaked recreate the entire qpairlist
to eliminate the leaks.
Too much memory is leaked if more than 100 bytes leaked and more than 75% the total used has been leaked.
*/
{
	Qmalloc *q;
	int max_leaked_bytes=100;
	if (*qpp==NULL) return false;
	if (!(*qpp)->free_qmalloc) return false; // If qpair creator maintains own qmalloc, can't do it.
	q=(*qpp)->qmalloc;
	if (q && q->total_leeked>100 && q->total_leeked>q->total_allocated*3/4) {
		QPairList *q2=qpair_dup(*qpp);
		//printf("qpair_eliminate_leaks DOING NOW as leaked %d/%d\n",q->total_leeked,q->total_allocated);
		qpair_free(qpp);
		*qpp=q2;
		return true;
	}
	return false;
}

void qpair_remove_from_lists_and_free(QPairList *qp, QPair *p)
{
	int h;
	if (p->next) p->next->prev=p->prev;
	if (p->prev) p->prev->next=p->next;
	else qp->start=p->next;

	h=qp->hash_fn(p->var,qp->hash_size);
	if (p->hash_next) p->hash_next->hash_prev=p->hash_prev;
	if (p->hash_prev) p->hash_prev->hash_next=p->hash_next;
	else qp->htable[h]=p->hash_next;

	qfreestr(qp->qmalloc,p->var);
	qfree(qp->qmalloc,sizeof(QPair));
}

int qpair_set_do(QPairList **qpp, char *name, char *val, int allow_dups, char *file, int line)
{
	int h;
	QPair *p;
	QPairList *qp;

	if (*qpp==NULL) *qpp=qpair_new_x(100,file,line);
	qp=*qpp;
	h=qp->hash_fn(name,qp->hash_size);
	if (!allow_dups) {
		for(p=qp->htable[h];p!=NULL;p=p->hash_next) {
			if (strcmp(p->var,name)==0) {
				if (p->val==NULL && val==NULL) return true;
				if (p->val!=NULL && val!=NULL && strcmp(p->val,val)==0) return true;
				qfreestr(qp->qmalloc,p->val);
				if (val) {
					p->val=qstrdup(qp->qmalloc,val);
					if(qp->auto_eliminate_leaks){
						qpair_eliminate_leaks(qpp);
					}
					return true;
				}
				else {
					qpair_remove_from_lists_and_free(qp,p);
					if(qp->auto_eliminate_leaks){
						qpair_eliminate_leaks(qpp);
					}
					return true;
				}
			}
		}
	}
	if (val==NULL) return true;

	p=qmalloc(qp->qmalloc,sizeof(QPair));
	p->var=qstrdup(qp->qmalloc,name);
	p->val=qstrdup(qp->qmalloc,val);
	p->hash_next=qp->htable[h];
	p->hash_prev=NULL;
	p->next=qp->start;
	p->prev=NULL;
	if (qp->htable[h])
		qp->htable[h]->hash_prev=p;
	qp->htable[h]=p;
	if (qp->start)
		qp->start->prev=p;
	qp->start=p;
	return true;
}

int	qpair_free_except_with_prefix(QPairList **qpp, char *prefix, char *prefix2)
{
	QPair *p;
	QPair *pnext;
	int len=(int)strlen(prefix);
	int len2=0;
	
	if (qpp==NULL) return true;
	if (*qpp==NULL) return true;
	if (prefix2) len2=(int)strlen(prefix2);
	p=qpair_start(*qpp);
	for (;p!=NULL;p=pnext) {
		pnext = p->next;
		if (strncmp(p->var,prefix,len)==0 || (len2>0 && strncmp(p->var,prefix2,len2)==0)) {
		}
		else {
			qpair_remove_from_lists_and_free(*qpp,p);
		}
	}
	qpair_eliminate_leaks(qpp);
	return true;
}

void qpair_remove_empty_values(QPairList **qpp)
{
	QPair *p;
	QPair *pnext;
	p=qpair_start(*qpp);
	for (;p!=NULL;p=pnext) {
		pnext = p->next;
		if (p->val==NULL || p->val[0]==0) {
			qpair_remove_from_lists_and_free(*qpp,p);
		}
	}
	qpair_eliminate_leaks(qpp);
}



int qpair_set_x(QPairList **qp, char *name, char *val, char *file, int line)
{
	return qpair_set_do(qp,name,val,0,file,line);
}

int qpair_set_int_x(QPairList **qp, char *name, int val, char *file, int line)
{
	char bf[100];
	sprintf(bf,"%d",val);
	return qpair_set_do(qp,name,bf,0,file,line);
}

int qpair_set_time(QPairList **qp, char *name, time_t val)
{
	char xtmp[BFSZ];
	return qpair_set_do(qp,name,time_to_str(val,xtmp),0,__FILE__,0);
}

int qpair_del_x(QPairList **qp, char *name, char *value, char *file, int line)
/*
If a value is specified, only remove it if existing value is equal.
*/
{
	if (value) {
		QPair *old_val=qpair_find_element(*qp,name);
		if (!old_val) return true;
		if (!old_val->val) return true;
		if (strcmp(old_val->val,value)!=0) return true;
	}
	return qpair_set_do(qp,name,NULL,0,file,line);
}

int qpair_set_allow_dup_x(QPairList **qp, char *name, char *val, char *file, int line)
{
	return qpair_set_do(qp,name,val,1,file,line);
}

int qpair_free_x(QPairList **qp, char *file, int line)
{
	if (*qp && (*qp)->free_qmalloc) {
		Qmalloc *q=(*qp)->free_qmalloc;
		qmalloc_free_all_do(q,file,line);
	}
	*qp=NULL;
	return true;
}

QPair *qpair_start(QPairList *pl)
{
	if (!pl) return NULL;
	return pl->start;
}

int qpair_equal(QPairList *q1, QPairList *q2)
{
	QPair *q;
	for(q=q1->start;q!=NULL;q=q->next) {
		if (q->val) {
			if (strcmp(q->val,qpair_find(q2,q->var))!=0) return 0;
		}
	}
	for(q=q2->start;q!=NULL;q=q->next) {
		if (q->val) {
			if (strcmp(q->val,qpair_find(q1,q->var))!=0) return 0;
		}
	}
	return 1;
}

char *qpair_token_encode_one(char *var, char *val, char *tmp, int tmpsz)
{
	char *pp;
	char *xtmp=tmp;
	int len=(int)strlen(var)+((int)strlen(val))*2+5;
	*tmp=0;
	if (len<tmpsz) {
		len=(int)strlen(var);
		strcpy(tmp,var);
		tmp+=len;
		tmpsz-=len;
		strcpy(tmp,"=\"");
		tmp+=2;
		tmpsz-=2;
		for(pp=val;*pp;pp++) {
			if (*pp=='\\' || *pp=='\"') {
				*tmp='\\';
				tmp++;
				tmpsz--;
			}
			*tmp=*pp;
			tmp++;
			tmpsz--;
		}
		strcpy(tmp,"\" ");
		tmp+=2;
		tmpsz-=2;
	}
	return xtmp;
}

char *qpair_token_encode(QPairList *list, char *tmp, int tmpsz, int ignore_empty_values)
{
	QPair *p;
	char *ret=tmp;
	char tt[5000];
	tmp[0]=0;
	for(p=list->start;p!=NULL;p=p->next) {
		int len;
		if (ignore_empty_values && p->val==NULL || p->val[0]==0) continue;
		qpair_token_encode_one(p->var,p->val,tt,5000);
		if (*tt) {
			len=(int)strlen(tt);
			if (len<tmpsz) {
				strcpy(tmp,tt);
				tmp+=len;
				tmpsz-=len;
			}
		}
	}
	return ret;
}

int qpair_token_split(char *s, QPairList **pairs,  char *tmp, int tmpsz)
{
	/* a="test" b="j  pb" c="end" d="a quoted \"word\" with a slash\\ " */
        int n=0;
		char *var;
		char *o=tmp;

        for (;*s!=0;) {
                s += strspn(s," ");  /* Skip spaces */
                if (*s==0) break;
                var = s;
                s += strcspn(s,"=");  /* Find end of word */
				if (*s==0) break;
				*s = 0; s++;
				if (*s=='"') s++;
				o=tmp;
				for (;*s!=0;s++) {
					if (o-tmp>tmpsz-10) break;
					if (*s=='"') break;
					if (*s=='\\') {
						s++;
						*o++ = *s;
					} else {
						*o++ = *s;
					}
				}
				*o++ = 0;
				qpair_add(pairs,var,tmp);
				n++;
				if (*s) s++;
        }
        return n;
}

int qpair_token_split2(char *s, QPairList **pairs,  char *tmp, int tmpsz)
{
	// like qpair_token_split but allow unquoted strings eg. a, c
	/* a=test b="j  pb" c=end d="a quoted \"word\" with a slash\\ " */
        int n=0,quoted;
		char *var;
		char *o=tmp;

        for (;*s!=0;) {
                s += strspn(s," ");  /* Skip spaces */
                if (*s==0) break;
                var = s;
                s += strcspn(s,"=");  /* Find end of word */
				if (*s==0) break;
				*s = 0; s++;
				if (*s=='"') {
					s++; 
					quoted=1;
				}else{
					quoted=0;
				}
				o=tmp;
				for (;*s!=0;s++) {
					if (o-tmp>tmpsz-10) break;
					if (*s=='"') break;
					if (*s==' ' && !quoted) break;
					if (*s=='\\') {
						s++;
						*o++ = *s;
					} else {
						*o++ = *s;
					}
				}
				*o++ = 0;
				qpair_add(pairs,var,tmp);
				n++;
				if (*s) s++;
        }
        return n;
}


int qpair_loadfile(QPairList **pp, char *file)
{
	char bf[5000],*p,*n,*s;
	FILE *f;

	f = fopen(file,"r");

	if (!f) return 0;
	while(fgets(bf,4999,f)) {
		for(p = bf; sisspace(*p); p++);
		for(n = p; *n && !sisspace(*n); n++);
		if (*n) {
			*n++ = '\0';
			for(; sisspace(*n); n++);
			for(s = n; *s; s++);
			for(; sisspace(*s) || iseol(*s); s--) *s = '\0';
		}
		qpair_add(pp,p,n);
	}
	fclose(f);
	return 1;
}

int qpair_savefile(QPairList *plist, char *file)
{
	FILE *f;
	QPair *p;

	f = fopen(file,"w");
	if (!f) return 0;
	for(p=plist->start; p!=NULL; p = p->next) fprintf(f,"%s %s\n",p->var,p->val);
	fclose(f);
	return 1;
}


#ifdef QPAIR_TEST
#include <stdio.h>

void rmalloc(int num)
{
	int i;
	char **m;
	m=malloc(sizeof(char *)*num);
	printf("malloc %d times...\n",num);
	for(i=0;i<num;i++) {
		m[i]=malloc(num%100);
		memset(m[i],0,num%100);
	}
	printf("free %d times...\n",num/2);
	for(i=0;i<num;i++) {
		if (i%2==0)
		free(m[i]);
	}
	printf("done\n");
}
#include "lib.h"
#define TRUE 1
void pair_test(int num)
{
	Pair *plist=NULL;
	int i;

	char bf[100];
	char bf2[100];
	//printf("setting pairs...\n");
	for(i=0;i<num;i++) {
		sprintf(bf,"var%d",i+1);
		sprintf(bf2,"val%d",i+1);
		pair_add(&plist,bf,bf2);
	}
	//printf("checking pairs...\n");
	for(i=0;i<num;i++) {
		sprintf(bf,"var%d",i+1);
		sprintf(bf2,"val%d",i+1);
		if (strcmp(pair_find(plist,bf),bf2)!=0) printf("failed\n");
	}
	pair_free(&plist);
	//printf("done\n");

}

void qpair_test(int num)
{
	QPairList *plist=NULL;
	int i;

	char bf[100];
	char bf2[100];
	//printf("setting pairs...\n");
	plist=qpair_new(1000);
	for(i=0;i<num;i++) {
		sprintf(bf,"var%d",i+1);
		sprintf(bf2,"val%d",i+1);
		qpair_set(plist,bf,bf2);
	}
	//printf("checking pairs...\n");
	for(i=0;i<num;i++) {
		sprintf(bf,"var%d",i+1);
		sprintf(bf2,"val%d",i+1);
		if (strcmp(qpair_find(plist,bf),bf2)!=0) printf("failed\n");
	}
	qpair_free(plist);
	//printf("done\n");

}

int main(int argc, char *argv[])
{
	int i;
	printf("qtest...\n");
	for(i=0;i<100;i++) {
		qpair_test(1000);
	}

	printf("ptest...\n");
	for(i=0;i<100;i++) {
		pair_test(1000);
	}
	printf("done\n");
	
	/*rmalloc(1000000);
	rmalloc(1000000);*/
}
#endif

//#define FIND_USE_NO_HASH 1

#ifdef FIND_USE_NO_HASH
#define qpair_find_element_no_hash qpair_find_element
#else
#define qpair_find_element_with_hash qpair_find_element
#endif

QPair *qpair_find_element_with_hash(QPairList *qp, char *name)
{
	QPair *p;
	int h;
	if (qp==NULL) return NULL;
	h=qp->hash_fn(name,qp->hash_size);
	for(p=qp->htable[h];p!=NULL;p=p->hash_next) {
		if (strcmp(p->var,name)==0) return p;
	}
	return NULL;
}
QPair *qpair_find_element_no_hash(QPairList *qp, char *name)
{
	QPair *p;
	if (qp==NULL) return NULL;

	for(p=qp->start;p!=NULL;p=p->next) {
		if (strcmp(p->var,name)==0) return p;
	}
	return NULL;
}
