#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include <stdarg.h>
#include "lib.h"
#include "lprintf.h"

#define DMID 14
#include "debug.h"
#include "vsnprintf.h"


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

#define emsg EMSG_NOT_IN_LIB_C_uSE_VEMSG
#define imsg EMSG_NOT_IN_LIB_C_uSE_VEMSG

/*--------------------------------------------------------------------------------*/
char *lib_token_encode_quoted(char **names, char **values, int n, int ignore_empty_values, char *bf, int bfsz)
{
	char tmp[5000];
	char *obf=bf;
	int i;
	for(i=0;i<n;i++) {
		char *v=values[i];
		int len;
		if (v[0]==0) continue;
		if (strchr(v,' ')) {
			lib_token_encode_one(names[i],v,tmp,4999);
		}
		else {
			lprintf(tmp,bfsz-1,"%s=%s",names[i],v);
		}
		len=lib_strlen(tmp);
		if (len<bfsz) {
			strcpy(bf,tmp);
			bf+=len;
			bfsz-=len;
		}
	}
	*bf=0;
	return obf;
}


char *lib_token_encode_one(char *var, char *val, char *tmp, int tmpsz)
{
	return pair_token_encode_one(var,val,tmp,tmpsz);
}

void pair_setval(Pair *p, char *val)
{
	if (p->val!=NULL) free(p->val);
	p->val = strdup(val);
	p->val_len = (int) strlen(val); // not sure if this is needed...
}
Pair *lib_email_getheaders_real(char *fname, char *file, int line)
{
	char bf[BFSZ],*p,*n;
	Pair *list = NULL;
	FILE *f;
	int len;
	char *firstcolon;
	char *firstspace;
	char *r;

	f = fopen(fname,"r");
	if (f==NULL) return list;

	while(fgets(bf,BFSZ-1,f)) {
		if (iseol(bf[0])) break;
		if (sisspace(bf[0])) {
			for(p = bf; sisspace(*p); p++);
			if (!list) break; //if it starts with a line containing spaces, it has no headers.
			len = lib_strlen(list->val) + lib_strlen(p) + 4;
			list->val = realloc(list->val,len);
			ncat(list->val," ",len-1);
			ncat(list->val,p,len-1);
			continue;
		}
		firstspace = strchr(bf,' ');
		firstcolon = strchr(bf,':');
		if (firstcolon==NULL) break;
		if (firstspace!=NULL) if (firstspace<firstcolon) break;
		p = mystrtok(bf,":",&n);
		if (p==NULL) {
			if (!list) break; //not a header, and none found so far, must be no headers in this file/message
			continue; //malformed header? ignore..
		}
		r = mystrtok(NULL,"\r\n",&n);
		if (r==NULL) continue;
		for(; sisspace(*r); r++);
		pair_add_real(&list,p,r,file,line,TRUE,FALSE);
	}
	fclose(f);

	return list;
}
Pair *pair_split(char *pin, char *sep)
{
	Pair *list=NULL;
	char in[BFSZ];
	char *upto;
	char *s;
	ncpy(in,pin,BFSZ-1);
	s = mystrtok(in,sep,&upto);
	for (;s!=NULL;s=mystrtok(NULL,sep,&upto)) {
		char *eq;
		eq = strchr(s,'=');
		if (eq==NULL) continue;
		*eq = 0; eq++;
		pair_add(&list,s,eq);
	}
	return list;
}
Pair *pair_token_decode(char *string)
{
	Pair *list = NULL;
	char var[BFSZ];
	char val[BFSZ];

	while(pair_token_decode_one(&string,var,val,BFSZ))
		pair_add(&list,var,val);

	return list;
}

int pair_token_decode_one(char **string, char *var, char *val, int bfsz)
{
	char *src = *string;
	int i;

	while(sisspace(*src)) src++;

	lib_copy_upto_str(&src,"=\"",var,bfsz);
	if (strncmp(src,"=\"",2)!=0) return FALSE;
	src += 2;

	for(i = 0; i < bfsz-1 && *src; i++)
	{
		if (*src == '\"') break;
		if (*src == '\\') { src++; if (!*src) break; }
		val[i] = *src++;
	}
	val[i] = 0;

	if (*src != '\"') return FALSE;
	src++;

	*string = src;

	return TRUE;
}

char *pair_token_encode_one(char *var, char *val, char *tmp, int tmpsz)
{
	char *pp;
	char *xtmp=tmp;
	int len=lib_strlen(var)+(lib_strlen(val))*2+5;
	*tmp=0;
	if (len<tmpsz) {
		len=lib_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 *pair_token_encode(Pair *list, char *tmp, int tmpsz)
{
	Pair *p;
	char *ret=tmp;
	char tt[BFSZ];
	tmp[0]=0;
	for(p=list;p!=NULL;p=p->next) {
		int len;
		pair_token_encode_one(p->var,p->val,tt,BFSZ);
		if (*tt) {
			len=lib_strlen(tt);
			if (len<tmpsz) {
				strcpy(tmp,tt);
				tmp+=len;
				tmpsz-=len;
			}
		}
	}
	return ret;
}

int pair_token_split(char *s, Pair **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;
				pair_add(pairs,var,tmp);
				n++;
				if (*s) s++;
        }
        return n;
}

int pair_token_split_keep(char *s, Pair **pairs,  char *tmp, int tmpsz)
{
	/* a="test" b="j  pb" c="end" d="a quoted \"word\" with a slash\\ " */
	/*
	pair_token_split would give:
	a=test
	b=j  pb
	c=end
	d=a quoted "word" with a slash \

	pair_token_split_keep would give:
	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=='\\') {
						*o++ = *s;
						s++;
						*o++ = *s;
					} else {
						*o++ = *s;
					}
				}
				*o++ = 0;
				pair_add(pairs,var,tmp);
				n++;
				if (*s) s++;
        }
        return n;
}
int pair_loadfile_x(Pair **pp, char *file, char *src, int line)
{
	char bf[BFSZ],*p,*n,*s;
	FILE *f;
	int nlines=0;
//	if (vemsg!=NULL) vemsg("pair_loadfile (%s) %s %d\n",file,src,line);
//	f = fopen(file,"r");
#ifdef USE_DFOPEN
	f = dfopen(file,"r",src,line,"fn");
#else
	f = fopen(file,"r");
#endif

	if (!f) return FALSE;
	while(fgets(bf,BFSZ,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';
		}
		pair_add_real(pp,p,n,src,line,TRUE,FALSE);
		nlines++;
	}
	fclose(f);
	return TRUE;
}

int pair_loadfile_sep(Pair **pp, char *file, char *sep)
{
	char bf[BFSZ];
	FILE *f;
	int np;
	char *p[100];

	f = fopen(file,"r");
	if (!f) return FALSE;
	while(fgets(bf,BFSZ,f)) {
		np = token_split(bf,p,sep,10);
		if (np<2) continue;
		pair_add(pp,p[0],p[1]);
	}
	fclose(f);
	return TRUE;
}

int pair_loadfile_concat(Pair **pp, char *file, char *concat)
{
	char bf[BFSZ];
	FILE *f;
	int np,i,np2;
	char *p[100],*p2[100];

	f = fopen(file,"r");
	if (!f) return FALSE;
	while(fgets(bf,BFSZ,f)) {
		np = token_split(bf,p,concat,10);
		if (np<1) continue;
		for(i=0;i<np;i++){
			np2 = token_split(p[i],p2,"=",10);
			if (np2<2) continue;
			pair_add(pp,p2[0],p2[1]);
		}
	}
	fclose(f);
	return TRUE;
}


int pair_savefile(Pair *p, char *file)
{
	FILE *f;

	f = fopen(file,"w");
	if (!f) return FALSE;
	for(; p; p = p->next) fprintf(f,"%s %s\n",p->var,p->val);
	fclose(f);
	return TRUE;
}
int pair_savefile_safe(Pair *p, char *file)
{
	FILE *f;
	char xname[BFSZ];
	int failed=FALSE;
	lprintf(xname,BFSZ-1,"%s_new_tmp",file);

	f = fopen(xname,"w");
	if (!f) return FALSE;
	for(; p; p = p->next) {
		if (fprintf(f,"%s %s\n",p->var,p->val)<=0) failed=TRUE;
	}
	if (fclose(f)!=0) failed = TRUE;
	if (!failed) {
		remove(file);
		rename(xname,file);
	}
	return TRUE;
}

void pair_copy_real(Pair **dst, Pair *src, char *file, int line)
{
	for(; src; src = src->next) {
		pair_add_real(dst,src->var,src->val,file,line,TRUE,FALSE);
	}
}

Pair *pair_dup_real(Pair *src, char *file, int line)
{
	Pair *ret=NULL;
	for(; src; src = src->next) {
		pair_add_real(&ret,src->var,src->val,file,line,TRUE,FALSE);
	}
	return ret;
}

int pair_add_int_real(Pair **pp, char *var, int val,char *file, int line)
{
	char bf[BFSZ];
	int_to_str(val,bf);
	return pair_add_real(pp,var,bf,file,line,TRUE,FALSE);
}
int pair_add_time(Pair **pp, char *var, time_t val)
{
	char xtmp[BFSZ];
	return pair_add_real(pp,var,time_to_str(val,xtmp),__FILE__,__LINE__,TRUE,FALSE);
}
int pair_add_double(Pair **pp, char *var, double val)
{
	char bf[BFSZ];
	sprintf(bf,"%g",val);
	return pair_add(pp,var,bf);
}

int pair_add_bin(Pair **pp, char *var, char *val, int len)
{
	Pair *p;

	p = malloc(sizeof(Pair));
	p->var = strdup(var);
	p->val = malloc(len+1);
	memcpy(p->val,val,len);
	p->val[len] = 0;
	p->val_len = len;
//printf("pair_add_bin len=%d (%s) (%s)\n",len,var,val);

	// Insert into list.
	p->next = *pp;
	*pp = p;
	return TRUE;
}
int pair_add_real(Pair **pp, char *var, char *val,char *file, int line, int check_dups, int atend)
{
	Pair *p=*pp;
	Pair *end;

	if (var == NULL || val == NULL) {
		lib_abort();
	}

	end = NULL;
	if (check_dups) {
		for (;p!=NULL;p=p->next) {
			if (strcmp(var,p->var)==0) {
				if (val != p->val) {
					pair_setval(p,val);
				}
				return TRUE;
			}
			end = p;
		}
	}
	else if (atend) {
		for (;p!=NULL;p=p->next)
			end = p;
	}

#ifdef USE_DMALLOC
	p = d_mallocid(DMID,sizeof(Pair),file,line);
	p->var = d_strdupid(DMID,var,file,line);
	p->val = d_strdupid(DMID,val,file,line);
#else
	p = malloc(sizeof(Pair));
	p->var = strdup(var);
	p->val = strdup(val);
#endif

	if (atend && end) {
		p->next = NULL;
		end->next = p;
	}
	else {
		p->next = *pp;
		*pp = p;
	}

	return TRUE;
}
int pair_add_end(Pair **pp, Pair **next, char *var, char *val)
{
	Pair *p=*pp;
	int len;
	if (lib_strlen(var)==0) {
		/* For news headers, add it to end of previous header with crlf ? */
		p = *next;
		if (p==NULL) return FALSE;
		len = lib_strlen(p->val)+lib_strlen(val);
		p->val = realloc(p->val,len+10);
		strcpy(p->val+lib_strlen(p->val),"\r\n ");
		strcpy(p->val+lib_strlen(p->val),val);
		return TRUE;
	}
	p = malloc(sizeof(Pair));
	p->next = NULL;
	p->var = strdup(var);
	p->val = strdup(val);
	if (*pp==NULL) {
		*pp = p;
		*next = p;
	} else {
		(*next)->next = p;
		*next = p;
	}
	return TRUE;
}
int pair_add_toend(Pair **pp, Pair **next, char *var, char *val)
{
	Pair *p=*pp;
	p = malloc(sizeof(Pair));
	p->next = NULL;
	p->var = strdup(var);
	p->val = strdup(val);
	if (*pp==NULL) {
		*pp = p;
		*next = p;
	} else {
		(*next)->next = p;
		*next = p;
	}
	return TRUE;
}

int pair_free_real(Pair **pp, char *file, int line)
{
	Pair *p;
	Pair *pnext;
	if (pp==NULL) return TRUE;
	p=*pp;
	for (;p!=NULL;p=pnext) {
		pnext = p->next;
#ifdef USE_DMALLOC
		d_free(p->var,file,line);
		d_free(p->val,file,line);
		d_free(p,file,line);
#else
		free(p->var);
		free(p->val);
		free(p);
#endif
	}
	*pp = NULL;
	return TRUE;
}
char *pair_find(Pair *p, char *var)
{
	for (;p!=NULL;p=p->next) {
		if (strcmp(var,p->var)==0) return p->val;
	}
	return "";
}
char *pair_find_value(Pair *p, char *var)
{
	for (;p!=NULL;p=p->next) {
		if (strcmp(var,p->val)==0) return p->var;
	}
	return NULL;
}
char *pair_find_bin(Pair *p, char *var, int *len)
{
	*len = 0;
	for (;p!=NULL;p=p->next) {
		if (strcmp(var,p->var)==0) {
			*len = p->val_len;
			return p->val;
		}
	}
	return "";
}
int pair_find_int(Pair *p, char *var)
{
	return atoi(pair_find(p,var));
}
time_t pair_find_time(Pair *p, char *var)
{
	return str_to_time(pair_find(p,var));
}
char *pair_findn(Pair *p, char *var, int n)
{
	char bf[BFSZ];
	lprintf(bf,BFSZ-1,"%s%d",var,n);
	return pair_find(p,bf);
}
int pair_deln(Pair **p, char *var, int n)
{
	char bf[BFSZ];
	lprintf(bf,BFSZ-1,"%s%d",var,n);
	return pair_remove(p,bf,NULL);
}
int pair_addn(Pair **p, char *var, int n, char *val)
{
	char bf[BFSZ];
	lprintf(bf,BFSZ-1,"%s%d",var,n);
	return pair_add(p,bf,val);
}
char *pair_find_null(Pair *p, char *var)
{
	for (;p!=NULL;p=p->next) {
		if (strcmp(var,p->var)==0) return p->val;
	}
	return NULL;
}
Pair *pair_find_p(Pair *p, char *var)
{
	for (;p!=NULL;p=p->next) {
		if (strcmp(var,p->var)==0) return p;
	}
	return NULL;
}
Pair *pair_append(Pair *p, char *val)
{
	char *s = malloc(lib_strlen(val) + lib_strlen(p->val) + 1);
	strcpy(s,p->val);
	strcpy(s+lib_strlen(p->val),val);
	free(p->val);
	p->val = s;
	return p;
}
Pair *pair_add_p(Pair **pp, char *var, char *val)
{
	Pair *p;
	p = malloc(sizeof(Pair));
	p->var = strdup(var);
	p->val = strdup(val);
	p->next = *pp;
	*pp = p;
	return p;
}
char *pair_findnc(Pair *p, char *var)
{
	for (;p!=NULL;p=p->next) {
		if (strcmpnc(var,p->var)==0) return p->val;
	}
	return "";
}
char *pair_findnc_null(Pair *p, char *var)
{
	for (;p!=NULL;p=p->next) {
		if (strcmpnc(var,p->var)==0) return p->val;
	}
	return NULL;
}
int pair_remove(Pair **pp, char *var, char *val)
{
	Pair *p,*prev = NULL;

	for (p = *pp ;p != NULL; prev = p, p = p->next) {
		if (strcmp(var,p->var) != 0) continue;
		if (val && strcmp(val,p->val) != 0) continue;
		if (prev) prev->next = p->next;
		else *pp = p->next;
		free(p->var);
		free(p->val);
		free(p);
		return TRUE;
	}
	return FALSE;
}

int pair_remove_empty_values(Pair **pp)
{
	while(*pp) {
		Pair *p=*pp;

		if (p->val && p->val[0]) {
			pp=&(p->next);
			continue;
		}
		*pp=p->next;
		free(p->var);
		free(p->val);
		free(p);
	}
	return FALSE;
}

int pair_removenc(Pair **pp, char *var, char *val)
{
	Pair *p,*prev = NULL;

	for (p = *pp ;p != NULL; prev = p, p = p->next) {
		if (strcmpnc(var,p->var) != 0) continue;
		if (val && strcmpnc(val,p->val) != 0) continue;
		if (prev) prev->next = p->next;
		else *pp = p->next;
		free(p->var);
		free(p->val);
		free(p);
		return TRUE;
	}
	return FALSE;
}
void pair_reverse(Pair **list) {
	Pair *newlist = NULL,*p;

	if (!list || !*list) return;
	for(p = *list; p; p = p->next) {
		pair_add(&newlist,p->var,p->val);
	}
	pair_free(list);
	*list = newlist;
}
/* suitable for qsort */
int pair_cmp(const void *p1, const void *p2)
{

	Pair *pp1 =  * ((Pair **)p1);
	Pair *pp2 =  * ((Pair **)p2);

	return strcmp(pp1->var,pp2->var);
}
int pair_cmpnc(const void *p1, const void *p2)
{
	Pair *pp1 =  * ((Pair **)p1);
	Pair *pp2 =  * ((Pair **)p2);


	return strcmpnc(pp1->var,pp2->var);
}
/* suitable for qsort */
int pair_cmp_v(const void *p1, const void *p2) {
	Pair *pp1 =  * ((Pair **)p1);
	Pair *pp2 =  * ((Pair **)p2);
	return strcmp(pp1->val,pp2->val);
}


int pair_printf(Pair **pp, char *var, char *fmt,...)
{
	char tmp[BFSZ];

	va_list args;
	va_start(args,fmt);
	vsnprintf(tmp,BFSZ-1,fmt,args);
	tmp[BFSZ-1]=0;
	pair_add(pp,var,tmp);
	va_end(args);
	return TRUE;
}

int pair_sort(Pair **pp, int (*cmp_fn)(const void *p1, const void * pp2))
{
	Pair *p;
	int n=0;
	int i=0;
	Pair **array;
	for(p=*pp;p;p=p->next) {
		n++;
	}
	if (n<=1) return n;
	array=malloc(n*sizeof(Pair *));
	for(p=*pp;p;p=p->next) {
		array[i++]=p;
	}
	qsort(array,n,sizeof(Pair *),cmp_fn);

	for(i=0;i<n-1;i++) {
		array[i]->next=array[i+1];
	}
	array[n-1]->next=NULL;
	*pp=array[0];

	free(array);
	return n;
}

void pair_log(Pair *list,int (*printf_fn)(const char *fmt, ...))
{
	Pair *p;
	for(p=list;p!=NULL;p=p->next) {
		printf_fn("[%s]=[%s]",p->var,p->val);
	}
}

void pair_log_short(Pair *list,char *start_msg,int (*printf_fn)(const char *fmt, ...))
{
	char bf[BFSZ];
	char *pp;
	Pair *p;
	pp=bf;
	sprintf(pp,"%s",start_msg);
	pp+=lib_strlen(pp);
	for(p=list;p!=NULL;p=p->next) {
		int len=lib_strlen(p->var)+lib_strlen(p->val);
		if (len<BFSZ-10-(pp-bf)) {
			sprintf(pp,"[%s]=[%s] ",p->var,p->val);
			pp+=lib_strlen(pp);
		}
		else {
			sprintf(pp,"...");
			pp+=lib_strlen(pp);
			break;
		}
	}
	*pp=0;
	printf_fn("%s\n",bf);
}

int pair_len(Pair *p)
{
	int n=0;
	for (;p!=NULL;p=p->next) n++;
	return n;
}
