/*
If string is > 16000 then write to file. (test make it 100 bytes)
remove dstr_s() so old code cannot break.

*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>

#ifdef SURGEMAIL
//void b_free(void *p);
//void *b_realloc(void *cur, int sz);
#include "../dftp/common/clib.h"
#else
#define b_free free
#define b_realloc realloc
#endif



#include "vsnprintf.h"
void lib_abort_real(char *src, int line);
#define lib_abort() lib_abort_real(__FILE__,__LINE__)

#include "thread.h"
#include "lib.h"
#define DSTR_LIB 1
#include "dstr.h"
#define DMID 14
#include "debug.h"
#ifndef FALSE
#define FALSE  0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

#ifdef DO_THREADS
static Mutex *m;
#endif
static int max_mem=200; // Increase to 10k 
#define STRINGID 11
static int strtotal;
static void (*emsg_do)(char *s);
void dstr_lib_abort(void);
#define emsg dstr_myemsg

#ifndef MUTEX_TIME
#define MUTEX_TIME 600000
#endif

void emsg(char * arg_list, ...)
{
	va_list arg_ptr;
	char *format;
	int len;
	char output[2000];
	
	va_start(arg_ptr, arg_list);
	format = arg_list;
	len = vsprintf(output, format, arg_ptr);
	if (emsg_do!=NULL) emsg_do(output);
}

static int mutex_i;
static int (*mutex_cb)(int vm, int isget, int seconds);
int dstr_set_mutex(int (*cb)(int vm, int isget, int seconds), int m)
{
		mutex_i = m;
		mutex_cb = cb;
		return TRUE;
}

void dstr_wait(void)
{
	if (mutex_cb) { mutex_cb(mutex_i,TRUE,600); return;}
#ifdef DO_THREADS
	if (m==NULL) return;
	if (!th_mutex_wait(m,MUTEX_TIME)) { /* 10 minutes */
		//		printf("Cannot get mutex, fatal problem, dieing dstr\n");
		emsg("Cannot get mutex, fatal problem, dieing dstr\n");
		dstr_lib_abort();
	}
#endif
}
void dstr_release(void)
{
	if (mutex_cb) { mutex_cb(mutex_i,FALSE,600); return; }
#ifdef DO_THREADS
	if (m==NULL) return;
	th_mutex_release(m);
#endif
}

static int max_str= 10000000;
void dstr_startup(int maxlen, void (*fn)(char *s))
{
	max_str = maxlen;
	emsg_do = fn;
#ifdef DO_THREADS
	if (m==NULL) m = th_mutex_new("dstr");
#endif
}
void dstr_nodisk(Dstr *s, int x)
{
	s->nodisk = x;
}
void dstr_newsz_d(Dstr **ss, int sz, char *source, int line)
{
	dstr_new_d(ss,source,line);
	dstr_expand(*ss,sz);
}
void dstr_new_d(Dstr **ss,char *source, int line)
{
	Dstr *s;
	
	if (*ss!=NULL) {
		emsg("Cannot create string as existing pointer is not null %s:%d",source,line);
		dstr_lib_abort();
	}
	s = malloc(sizeof(*s));
	if (s==NULL) {
		emsg("Unable to allocate dstr object, insufficient memory %s:%d ",source,line);
		dstr_lib_abort();
	}
	dstr_init(s);
	s->source = source;
	s->line = line;
	s->id = STRINGID;
	*ss = s;
}
int dstr_read(Dstr *s, char *fname)
{
	FILE *f;
	char inbuff[2001];
	int i;
	char *e;
	
	dstr_clear(s);
	if (lib_strlen(fname)==0) return FALSE;
	f = fopen(fname,"r");
	if (f==NULL) return FALSE;
	
	for (;!feof(f);) {
		if (fgets(inbuff,2000,f)!=NULL) {
			i = lib_strlen(inbuff);
			if (i>0) inbuff[i-1] = 0; /*Delete newline character */
			e = strchr(inbuff,'\015');
			if (e!=NULL) *e = 0;
			e = strchr(inbuff,'\012');
			if (e!=NULL) *e = 0;
			strcat(inbuff,"\015\012");
			dstr_adds(s,inbuff,lib_strlen(inbuff));                      
		}
	}
	fclose(f);
	return TRUE;
}
int dstr_addfile(Dstr *s, char *fname)
{
	FILE *f;
	char inbuff[2001];
	int i;
	char *e;
	
	f = fopen(fname,"r");
	
	if (f==NULL) return FALSE;
	
	for (;!feof(f);) {
		if (fgets(inbuff,2000,f)!=NULL) {
			i = lib_strlen(inbuff);
			if (i>0) inbuff[i-1] = 0; /*Delete newline character */
			e = strchr(inbuff,'\015'); if (e!=NULL) *e = 0;
			e = strchr(inbuff,'\012'); if (e!=NULL) *e = 0;
			strcat(inbuff,"\015\012");
			dstr_adds(s,inbuff,lib_strlen(inbuff));                      
		}
	}
	fclose(f);
	return TRUE;
}
static Dstr *list;
void dstr_init(Dstr *s)
{
#ifdef DO_THREADS
	if (m==NULL) m = th_mutex_new("dstr");
#endif
	
	dstr_wait();
	memset(s,0,sizeof(Dstr));
	s->len = 0; s->total = 0; s->s = NULL;
	s->f = NULL;
	s->dnext = list;
	s->last = NULL;
	s->isfile = FALSE;
	if (s->dnext!=NULL) s->dnext->last = s;
	list = s;
	dstr_release();
}
#define ifexpand(str,len) if (str->total<=(len)) dstr_expand(str,len);
#define iffree(x) if ((x)!=NULL) {free(x); (x) = NULL;}
#define iffree2(x) if ((x)!=NULL) {b_free(x); (x) = NULL;}
void dstr_adds_file(Dstr *dst, char *s, int len)
{
	dst->len += len;
	if ((int) fwrite(s,1,len,dst->f)!=len) dst->failed = TRUE;
}
char *dstr_ss(Dstr *s)
{
	if (!s->nodisk) lib_abort();
	return s->s;
}
int dstr_len(Dstr *s)
{
	return s->len;	
}
void dstr_copysn(Dstr *dst, char *src)
{
	dstr_clear(dst);
	dstr_addsn(dst,src);
}
void dstr_adds(Dstr *dst, char *s, int len)
{
	/* if (dst->s!=NULL) dmsg("dstr_add {%s} \n",dst->s);*/
	ifexpand(dst,dst->len+len+1);
	if (dst->isfile) { dstr_adds_file(dst,s,len); return;}
	safe_memcpy((dst->s) +dst->len,s,len,dst->s,dst->total);
	dst->len += len;
	(dst->s)[dst->len] = 0; /* Add a terminating 0 */
	/* if (dst->s!=NULL) dmsg("dstr_now {%s} \n",dst->s); */
}
static int unique;
void dstr_expand(Dstr *s, int len)
{
	if (len==0) return;
	len+=len; 
	if (s->isfile) return;	
	if (len<100) len = 100;
	if (len<0) {
		emsg("str: dstr_expand, NEGATIVE value %d \n",len);
		dstr_lib_abort();
	}
	if (len>max_str) {
		emsg("str: dstr_expand, malloc is too big %d>%d\n",len,max_str);
		dstr_lib_abort();
	}

	if (!s->nodisk && len>max_mem) {
		int u;
		s->isfile = TRUE;
		dstr_wait();
		u = unique++;
		dstr_release();
		sprintf(s->fname,"dstrtmp/tmp%d.tmp",u);
		s->f = fopen(s->fname,"wb");
		if (s->f==NULL) {
			lib_create_dir(s->fname,0770);
			s->f = fopen(s->fname,"wb");
		}
		if (s->f==NULL) {
			s->failed = TRUE;
			emsg("dstr_file open failed (%s)\n",s->fname,strerror(errno));
		}
		if ((int) fwrite(s->s,1,s->len,s->f)!=s->len) {
			s->failed = TRUE;
			emsg("dstr_file write failed (%s)\n",s->fname,strerror(errno));
		}
		return;
	}
	strtotal += len;
	strtotal -= s->total;
	
	if (len<=0) {
		emsg("str: Cannot create zero length string");
		dstr_lib_abort();
	}
//#ifdef USE_DMALLOC
//	s->s = d_realloc(s->s,len,s->source,s->line);
//#else
	s->s = b_realloc(s->s,len);
//#endif

	s->total = len;
}
void dstr_clear(Dstr *dst) 
{
	dst->len = 0;
	strtotal -= dst->total;
	iffree2(dst->s);
	dst->s = NULL;
	dst->total = 0;
	if (dst->isfile) {
		if (dst->f!=NULL) fclose(dst->f); dst->f = NULL;
		dst->isfile = FALSE;
		remove(dst->fname); strcpy(dst->fname,"");
	}
}
void dstr_free(Dstr **ss) 
{
	FILE *f=NULL;
	int isfile=FALSE;
	char fname[BFSZ];
	Dstr *s=*ss;

	strcpy(fname,"");
	if (s==NULL) {
		emsg("Attempting to free already free string");
		dstr_lib_abort();
	}
	if (s->id!=STRINGID) {
		emsg("Attempting to free invalid string pointer");
		dstr_lib_abort();
	}
	
	/* Remove from doubly linked list */
	dstr_wait();
	if (s->last==NULL) list = s->dnext;
	else	s->last->dnext = s->dnext;
	if (s->dnext!=NULL) s->dnext->last = s->last;
	
	s->dnext = NULL;
	s->last = NULL;
	s->id = 0;

	// CHRISP moving these two outside mutex...
	if (s->f!=NULL) {f = s->f; s->f = NULL;}
	if (s->isfile) {isfile = s->isfile; ncpy(fname,s->fname,BFSZ-1);}

	strtotal -= s->total;
	iffree2(s->s);
	iffree(s);
	*ss = 0;
	dstr_release();

	// Now close the file etc that the mutex is released...
	if (f!=NULL) fclose(f); f = NULL;
	if (isfile) remove(fname);
}
void dstr_lock(Dstr *s)
{
}
void dstr_unlock(Dstr *s)
{
}
int dstr_total(void)
{
	return strtotal;
}
void dstr_addsn(Dstr *a, char *s)
{
	dstr_adds(a,s,lib_strlen(s));
}
void dstr_add(Dstr *a, Dstr *s)
{
	dstr_adds(a,dstr_ss(s),dstr_len(s));
}
size_t dstr_fwrite(char *s, size_t size, size_t count, Dstr *a)
{
	dstr_adds(a,s,(int)(size*count));
	return count;
}
void dstr_exit(void)
{
	Dstr *s;
	int nbad = 0;
	/* Report all unallocated strings */
	for (s=list;s!=NULL;s=s->dnext) {
		//dmsg("Unfree'd string %.90s:%d %.80s",s->source,s->line,(s->s!=NULL) ? s->s : "(NULL-string)");
		nbad++;
	}
	if (nbad>0) emsg("dstr_exit: nbad %d\n",nbad);
}

void dstr_lib_abort(void)
{
#ifdef WIN32
	char *s=NULL;
	*s = 0;
#else
	lib_abort();
#endif
}


int dstr_fopen(Dstr *str)
{
	if (str->isfile) {
		if (str->f!=NULL) fclose(str->f);
		str->f = fopen(str->fname,"rb");
		if (str->f==NULL) return FALSE;
	} else {
		str->read_at = 0;
	}
	return TRUE;
} 
char *dstr_fgets(Dstr *str, char *bf, int bfsz)
{
	char *xbf=bf;
	char *s;
	int rest;
	int ngot=0;
	bfsz--;
	if (str->isfile) {
		if (str->f) return fgets(bf,bfsz,str->f);
		return NULL;
	} else {
		rest = str->len - str->read_at;
		for (s=str->s + str->read_at; bfsz>0 && rest>0;rest--,bfsz--) {
			*bf++ = *s++;
			ngot++;
			str->read_at++;
			if (bf[-1]=='\n') break;
		}
		*bf++ = 0; // append null
		if (ngot<=0) return NULL;
		return xbf;
	}
}
int dstr_fread_exact(void *bf, size_t size, size_t count, Dstr *str)
{
	int r = dstr_fread(str,bf,(int)(size*count));
	return (int)(r/size);
}
int dstr_fread(Dstr *str, char *bf, int bfsz)
{
	int ngot=0;
	char *s;
	int rest;
	bfsz--;
	if (str->isfile) {
		if (str->f) return (int) fread(bf,1,bfsz,str->f);
		return 0;
	} else {
		rest = str->len - str->read_at;
		for (s=str->s + str->read_at; bfsz>0 && rest>0;rest--,bfsz--) {
			*bf++ = *s++;
			ngot++;
			str->read_at++;
		}
		return ngot;
	}
	return 0;
}
int dstr_ferror(Dstr *str)
{
	if (str->isfile && str->f)
		return ferror(str->f);
	return 0;
}
int dstr_feof(Dstr *str)
{
	if (str->isfile) {
		if (str->f) return feof(str->f);
		return TRUE;
	}
	return ((str->len - str->read_at) == 0);
}
int dstr_fclose(Dstr *str)
{
	if (str->isfile) if (str->f) {
		fclose(str->f);
		str->f = NULL;
	}
	return TRUE;
}
int dstr_printf(Dstr *dst, const char * arg_list, ...)
{
    va_list arg_ptr;
    char *format;
    int len;
	char output[2000];

    va_start(arg_ptr, arg_list);
    format = (char *) arg_list;

    len = vsnprintf(output,1000-1, format, arg_ptr);
	if (len < 0 || len >= 1000) {
		output[(len = 900)] = '\0';
	}
	dstr_adds(dst,output,lib_strlen(output));
	return TRUE;
}
int dstr_iprintf(Dstr *dst, int indent, const char * arg_list, ...)
{
    va_list arg_ptr;
    char *format;
    int len,i;
	char output[2000];

    va_start(arg_ptr, arg_list);
    format = (char *) arg_list;
	
	if(indent>10) indent=10;
	for(i=0;i<indent;i++)
		output[i]=' ';

    len = vsnprintf(output+indent,1000-indent-1, format, arg_ptr);
	if (len < 0 || len >= 1000) {
		output[(len = 900)] = '\0';
	}
	dstr_adds(dst,output,lib_strlen(output));
	return TRUE;
}

//#define TESTINGDSTR 1
#ifdef  TESTINGDSTR
void logit(char *s)
{
	printf("%s",s);
}
int main(int argc, char *argv[]) 
{
	int got;
	Dstr *a=NULL;
	char bf[BFSZ];
	dstr_startup(10000000,logit);
	dstr_newsz_d(&a,30000,"src",1);
	dstr_addsn(a,"testing here1\n");
	dstr_addsn(a,"testing here2\n");
	dstr_addsn(a,"testing here3\n");
	dstr_addsn(a,"testing here4\n");
	dstr_addsn(a,"testing here4b\n");
	dstr_addsn(a,"testing here5\n");
	dstr_addsn(a,"testing here6\n");
	dstr_addsn(a,"testing here7\n");

	dstr_fopen(a);
	for (;TRUE;) {
		got  = dstr_fread(a,bf,100);
		if (got<=0) break;
		bf[got] = 0;
		printf("got line (%s)\n",bf);
	}
	dstr_fclose(a);
	dstr_free(&a);
}
#endif

// Create a dynamic length string using compact syntax (primarily for surgeweb & imaplib use)
Dstr *dstr_plain_d(char *source, int line)
{
	Dstr *dstr=NULL;
	dstr_new_d(&dstr,source,line);
	dstr_nodisk(dstr,TRUE);
	dstr_big_enough(dstr,BFSZ);	
	return dstr;
}

int dstr_bfsz(Dstr *dst)
{
	return dst->total;
}
void dstr_ffree(Dstr **s)
{
	if(*s==NULL) return;
	dstr_free(s);
}
void dstr_big_enough(Dstr *s, int len)
{
	int ask_length;
	if(len>1000)
	{
		int i=100;
	}
	if (s->total>=len) return;

	// NOTE: expand automatically doubles ask_length
	ask_length = len%2==0 ? len/2 : len/2+1; 
	dstr_expand(s,ask_length);
}
void dstr_ncat(Dstr *s, char *txt)
{
	if(!s) return;
	dstr_addsn(s, txt);
}
void dstr_ncat_sep(Dstr *s, char*sep, char*txt)
{
	if(!s) return;
	if(dstr_len(s)>0) dstr_addsn(s, sep);
	dstr_addsn(s, txt);
}
void dstr_reset(Dstr *s)
{
	s->len = 0;
	s->s[0] = 0;
}
int dstr_filewrite(Dstr *s, char *filename)
{
	FILE *f;
	f=fopen(filename,"wb");
	if(!f) return FALSE;
	fwrite(dstr_ss(s),dstr_len(s),1,f);
	fclose(f);

	return TRUE;
}
