/*
Add code to 'dmalloc' so I can choose 'smalloc'



*/

#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <time.h>


#include "lib.h"
#include "thread.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>

// So we don't get malloc/free over-ridden
#define IS_DMALLOC_C 1
#define DMID 13
#include "dmalloc.h"

#undef malloc
#undef free
#undef calloc
#undef realloc
#undef strdup


#include "smalloc.h"

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

int  emsg(const char * arg_list, ...);

#define MAXBUCKET 32
typedef struct SMEMBLOCK {struct SLINK *lnk; int pad; } Smemblock;
typedef struct SLINK {Smemblock *memblock; short inuse; short bkt; short mid; int used;  struct SLINK *next; struct SLINK *last; char *src; int line;} Slink;
typedef struct BUCKET {int sz; int nfree; int nused; int npending; struct SLINK *listused; struct SLINK *listfree; struct SLINK *listpending;} Bucket;
typedef struct SBLOCK {Mutex *mutex; int mid; Bucket *bucket[MAXBUCKET];} Sblock;
#define MEMHEAD (sizeof(char *) +sizeof(int))
#define MAGIC1 0X12345678
#define MAXID 90
static Sblock *sblock[MAXID];

void s_abort(Slink *slink, void *x, char *reason, char *src, int line);
void s_bucket_free(char *x, char *src, int line);
void *s_bucket_malloc(Sblock *s, int len, char *src, int line);
void s_alink_free(Sblock *s, Slink *a, char *src, int line);
Slink *s_link_remove(Slink **pplist, Slink *add);
void s_link_add(Slink **pplist, Slink *add);
Bucket *s_bucket_get(Sblock *s, int bkt, int len);
void s_bucket_grow(Sblock *s, Bucket *b, int bkt);
Bucket *s_bucket_new(Sblock *s, int sz, int bkt);
static int isparanoid=FALSE;

extern void *(*psimple_realloc)(void *x, int n,char *src, int line);
extern void (*psimple_free)(void *x, char *src, int line);
extern void *(*psimple_mallocid)(int dmid, int n,char *src, int line);
extern void *(*psimple_callocid)(int dmid,int num, int size,char *src, int line);

static int safemode=TRUE; // brings it up to about the same speed as dmalloc... 

int s_malloc_set_smalloc(void)
{
	psimple_mallocid = s_malloc;
	psimple_realloc = s_realloc;
	psimple_callocid = s_calloc;
	psimple_free = s_free;
	return TRUE;
}

Sblock *sblock_lock(int mid)
{
	// if needed allocate it, creat the mutex
	// lock the mutex.
	Sblock *s;
	s = sblock[mid];
	if (s==NULL) {
		d_mutex_wait(__LINE__);
		if (s==NULL) {
			s =  calloc(1,sizeof(Sblock));
			if (s == NULL) return NULL;
			s->mid = mid;
			s->mutex = 	th_mutex_new("smalloc");		 // important that th_Mutex_new doesn't call malloc/calloc
			sblock[mid] = s;
		}
		d_mutex_release();

	}
	if (!th_mutex_get(s->mutex)) {
		s_abort(NULL,NULL,"Mutext_lock failed","none",0);
	} 
	return s;
}
void s_abort(Slink *slink, void *x, char *reason, char *src, int line)
{
	emsg("S_CHECK: %s %s:%d %p - SHOULD FIND NEAR MEMORY AND DUMP DATA..\n",reason,src,line,x);
	if (slink!=NULL) {
		emsg("S_CHECK: Memory block details %s:%d used=%d inused=%d mid=%d bkt=%d\n",slink->src,slink->line,slink->used,slink->inuse,slink->mid,slink->bkt);
	}
	lib_abort();
}
void sblock_unlock(Sblock *s)
{
	th_mutex_release(s->mutex);
}
void *s_malloc(int mid, int len, char *src, int line)
{
	Sblock *s = sblock_lock(mid);
	char *ret;
	if (len==0) len = 1; // reject requests for null bytes...
	if ((len<0) || (len>300000000)) { // negative or more than 300mb, then crash hard... 
		emsg("MALLOC of silly amount of memory %s:%d %d bytes\n",src,line,len);
		lib_abort();
	}
	ret = s_bucket_malloc(s,len,src,line);
	sblock_unlock(s);
	return (void *) ret;
}
void s_free(void *x, char *src, int line)
{
	if (x==NULL) return;
	s_bucket_free(x,src,line);
}
int s_mid(char *x, char *src, int line)
{
	Smemblock *m;
	Slink *l;
	x -= MEMHEAD;
	m = (Smemblock *)x;
	l = m->lnk;
	if (m!=l->memblock) s_abort(l,m,"s_mid",src,line); // it should point to itslef, if not it's corrupted...
	return l->mid;
}
int s_test(char *data, int len, char val, char *reason)
{
	int was=len;
	if (!safemode) return TRUE;
	for (;len>0;len--,data++) if (*data!=val) {
		emsg("S_CHECK: Failed %d != %d at [%d] %s",(int) *data,(int) val, was-len,reason); 
		return FALSE;
	}
	return TRUE;
}
void s_check(void *xx, char *whence, char set1, char set2, char *src, int line)
{
	char *x = (char *) xx;
	char *data = x;
	Smemblock *m;
	Slink *l;
	Bucket *b;
	x -= MEMHEAD;
	m = (Smemblock *)x;
	l = m->lnk;
	if (m->pad!=MAGIC1) {
		emsg("S_CHECK: memory underwrite %s:%d\n",src,line);
		emsg("S_CHECK: Original block %s:%d\n",l->src,l->line);
		s_abort(l,m,whence,src,line);
	}
	if (l==NULL) s_abort(l,m,whence,src,line); // it should point to itslef, if not it's corrupted...
	if (l == NULL) return;
	if (m!=l->memblock) s_abort(NULL,m,whence,src,line); // it should point to itslef, if not it's corrupted...
	b = sblock[l->mid]->bucket[l->bkt];
	// One of these next two lines gives an issue:
	// S_CHECK: Failed 'a' != 'b' at [0]
	if (set1!=0) if (!s_test(data,l->used,set1,"actualdata")) s_abort(l,m,whence,src,line); // it should point to itslef, if not it's corrupted...
	// failed here... So the block has data set to 'a' when it should be 'b'...
	if (!s_test(data+l->used,b->sz-MEMHEAD-l->used,set2,"afterblock")) s_abort(l,m,whence,src,line); // it should point to itslef, if not it's corrupted...
}
Slink *s_link(void *xx, char *src, int line)
{
	char *x = (char *) xx;
	Smemblock *m;
	Slink *l;
	x -= MEMHEAD;
	m = (Smemblock *)x;
	l = m->lnk;
	if (m!=l->memblock) s_abort(l,m,"s_lnk",src,line); // it should point to itslef, if not it's corrupted...
	return l;
}
void *s_realloc(void *old, int n, char *src, int line)
{
	int mid=0;
	void *m;
	if (old!=NULL) {
		mid = s_mid(old,src,line);
	}
	m = s_malloc(mid,n,src,line);
	if (old!=NULL) {
		int max = n;
		Slink *slink = s_link(old,src,line);
		if (max>slink->used) max = slink->used; // don't copy more than the size of the new buffer...
		memcpy(m,old,max);
		s_free(old,src,line);
	}
	return m;
}
void *s_calloc(int mid, int num, int size, char *src, int line)
{
	void *x = s_malloc(mid,num*size,src,line);
	memset(x,0,num*size);
	return x;
}

int clz(int v) 
{
    int n=32,c=32;
    while (n) {
        n>>=1;
        if (v>>n) c-=n,v>>=n;
    }
    return c-v;
}
int s_choose(int sz)
{
	int pos = 32 - clz(sz);
	pos -= 4;
	if (pos<0) pos = 0;
	return pos;
}
void s_bucket_free(char *orig, char *src, int line)
{
	Bucket *b=NULL;
	Smemblock *m;
	Slink *l;
	char *x=orig;
	Sblock *s=NULL;
	if (x==NULL) return;
	s_check(x,"s_bucket_free",0,'b',src,line); // test  it HERE first...
	x -= MEMHEAD;
	m = (Smemblock *)x;

	l = m->lnk;
	if (l==NULL) {
		emsg("S_CHECK: s_bucket_Free link is null\n");
		s_abort(l,m,"bucket_free",src,line); // it should point to itslef, if not it's corrupted...
		return;
	}
	if (m!=l->memblock) {
		emsg("S_CHECK: s_bucket_Free m=%p l=%p\n",m,l);
		emsg("S_CHECK: s_bucket_Free memblock = %p \n",l->memblock);
		s_abort(NULL,m,"bucket_free",src,line); // it should point to itslef, if not it's corrupted...
	}

	s = sblock_lock(l->mid);
	s_alink_free(s,l,src,line);
	if (safemode) {
		// Now set the block to 'c' so we know it's in the pending list.
		b = s->bucket[l->bkt];
		l->used = 0; l->inuse = FALSE;
		memset(orig,'c',b->sz-MEMHEAD);
		if (isparanoid) s_check(orig,"pendingtesting",'c','c',src,line);
	}
	sblock_unlock(s);
}
void s_alink_free(Sblock *s, Slink *a, char *src, int line)
{
	Bucket *b = s->bucket[a->bkt];
	s_link_remove(&b->listused,a);
	s_link_add(&b->listpending,a); // add to used list...
	b->npending++;
	b->nused--;
	a->inuse = FALSE;
}
void s_free_pending(Sblock *s, Bucket *b, char *src, int line)
{
	if (b==NULL) return;
	for (;b->npending>0;) {
		Slink *slink = b->listpending;
		char *data = ((char *)slink->memblock)+MEMHEAD;
		if (safemode) {
			s_check(data,"freepending",'c','c',src,line);
			memset(data,'d',b->sz-MEMHEAD);
		}
		b->npending--;
		b->nfree++;
		s_link_remove(&b->listpending,slink);
		s_link_add(&b->listfree,slink);
	}
}
void s_test_one(Sblock *s, Bucket *b, char *src, int line)
{
	Slink *link;
	if (b==NULL) return;
	if (!safemode) return; 
	for (link = b->listfree;link!=NULL;link=link->next) {
		char *data = ((char *)link->memblock)+MEMHEAD;
		s_check(data,"test_one_free",'d','d',src,line);
	}
	for (link = b->listpending;link!=NULL;link=link->next) {
		char *data = ((char *)link->memblock)+MEMHEAD;
		s_check(data,"test_one_pending",'c','c',src,line);
	}
	for (link = b->listused;link!=NULL;link=link->next) {
		char *data = ((char *)link->memblock)+MEMHEAD;
		s_check(data,"test_one_used",0,'b',src,line);
	}
}
void *s_bucket_malloc(Sblock *s, int len, char *src, int line)
{
	void *ret;
	int rest;
	int bkt = s_choose((int)(len+MEMHEAD+len*1.2)); // 20% padding
	Bucket *b = s_bucket_get(s,bkt,len); // create if necessary, and return bucket structuer.
	Slink *a;
	// Now allocate from 'free' list and return pointer...
	if (b->nfree==0) if (b->npending*b->sz > 100000) s_free_pending(s,b,src,line);
	if (b->listfree==NULL) s_bucket_grow(s,b,bkt);
	a = s_link_remove(&b->listfree,NULL);
	b->nfree--;
	b->nused++;
	a->used = len;
	a->src = src;
	a->line = line;
	s_link_add(&b->listused,a); // add to used list...
	a->inuse = TRUE;
	if (a->memblock->lnk==NULL) s_abort(a,a->memblock,"null point back","null",0);
	ret = (char *) a->memblock + MEMHEAD;		
	if (safemode) {
		s_check(ret,"bucket_malloc1",'d','d',src,line); // starts as 'd' when it's free. 
		memset(ret,'a',len);
	}
	rest = b->sz - len - MEMHEAD;
	if (safemode) {
		memset((char * )ret+len,'b',rest); // setting it to 'b' 
	}
	if (isparanoid) s_check(ret,"s_bucket_malloc",'a','b',src,line); // verify it's setup right... (remove)
	return ret;
}
Bucket *s_bucket_get(Sblock *s, int bkt, int len)
{
	int size;	
	size = 1<<(bkt+4);
	if (s->bucket[bkt]==NULL) s->bucket[bkt] = s_bucket_new(s,size,bkt);
	return s->bucket[bkt];
}
Bucket *s_bucket_new(Sblock *s, int sz, int bkt)
{
	Bucket *b = calloc(1,sizeof(Bucket));
	if (b == NULL) return NULL;
	b->sz = sz;
	return b;
}
void s_bucket_grow(Sblock *s, Bucket *b, int bkt)
{
	// Allocate some more of these...
	int want = 128;
	char *data;
	int i;
	if (b->sz>1000) want = 8;
	if (b->sz>100000) want = 2;
	if (b->sz>1000000) want = 1;
	data = calloc(64,(want*b->sz)/64); if (data==NULL) lib_abort();
	for (i=0; i<want; i++) {
		char *pd;
		Slink *a = calloc(1,sizeof(Slink));
		if (a == NULL) return;
		a->memblock = (Smemblock *) (data + i * b->sz);
		a->used = 0;
		a->bkt = bkt;
		a->mid = s->mid;
		a->memblock->lnk = a; // Link back the index of blocks so we can do stuff when freeing it...
		a->memblock->pad = MAGIC1; // Link back the index of blocks so we can do stuff when freeing it...
		a->src = "unused";
		pd = ((char *)a->memblock)+MEMHEAD;
		if (safemode) memset(pd,'d',b->sz-MEMHEAD);
		if (a->memblock->lnk==NULL) s_abort(a,a->memblock,"null point back2","null",0);
		if (isparanoid) s_check(pd,"grow",'d','d',__FILE__,__LINE__);
		s_link_add(&b->listfree,a);
		b->nfree++;
	}
	//s_abort(0,"test bucket grow","x",0);

}
//typedef struct SLINK {Smemblock *m; int used; int bkt; int mid; struct SLINK *next; struct SLINK *last; char *src; int line;} Slink;
void s_link_add(Slink **pplist, Slink *add)
{
	Slink *plist = *pplist;
	add->next = plist;
	add->last = NULL;
	if (plist!=NULL) plist->last = add;
	*pplist = add;
}
Slink* s_link_remove(Slink** pplist, Slink* add)
{
	Slink* plist = *pplist;
	Slink* last;
	Slink* next;
	if (add == NULL) add = plist;
	if (add == NULL) { s_abort(NULL, NULL, "s_link_remove and nothing left", "none", 0); return NULL; }
	last = add->last;
	next = add->next;
	if (next!=NULL) next->last = last;
	if (last!=NULL) {
		last->next = next;
	} else {
		*pplist = next; // adjust head.
	}
	add->last = add->next = NULL;
	return add;
}
void s_trim()
{
	int i;
	time_t start = time(NULL);
	int elap;
	for (i=0; i<MAXID; i++) {
		Sblock *s = sblock[i];
		if (s!=NULL) {
			int b;
			s = sblock_lock(i);
			for (b=0; b<MAXBUCKET;b++) {
				Bucket *bb = s->bucket[b];
				if (bb) s_free_pending(s,bb,__FILE__,__LINE__);
			}
			sblock_unlock(s);
		}
	}
	elap = (int)(time(NULL)-start);
	if (elap>2) emsg("s_trim: took %d seconds\n",elap);
}
void s_testall()
{
	int i;
	time_t start = time(NULL);
	for (i=0; i<MAXID; i++) {
		Sblock *s = sblock[i];
		if (s!=NULL) {
			int b;
			s = sblock_lock(i);
			for (b=0; b<MAXBUCKET;b++) {
				Bucket *bb = s->bucket[b];
				if (bb) s_test_one(s,bb,__FILE__,__LINE__);
			}
			sblock_unlock(s);
		}
	}
//	emsg("smalloc: testall finished in %d seconds\n",time(NULL)-start);
}
int s_dump_sblock(Sblock *sb, FILE *fout);
int s_dump(char *fname)
{
	int mid;
	FILE *fout = fopen(fname,"w");
	if (fout==NULL) return FALSE;

	fprintf(fout,"TOTAL    |--- NOT FREED -----| |--- USED & FREED ---|\n");
	fprintf(fout,"bytes       bytes     blocks      bytes     blocks\n");

	for (mid=0; mid<MAXID; mid++) {
		Sblock *s = sblock[mid];
		if (s!=NULL) {
			s = sblock_lock(mid);
			s_dump_sblock(s,fout);
			sblock_unlock(s);
		}
	}
	fclose(fout);
	return TRUE;
}
int s_dump_sblock(Sblock *sb, FILE *fout)
{
	// Go through all block lists and record totals for memory usage ....source:line...
	// For each 'source' file not done
	//	Count mallocs and total for each line.
	//	  Display...
	int totused=0;
	int *lines,*totals;
	int *lines_f,*totals_f;
	int i;
	int line;
	int totfree=0;
	int h;
	int b;
	Wentry *w;
	Hwordlist *wl=NULL;

	hword_new(&wl);
	lines = calloc(10000,sizeof(int));
	totals = calloc(10000,sizeof(int));
	lines_f = calloc(10000,sizeof(int));
	totals_f = calloc(10000,sizeof(int));

	// get list of filenames used in this sblock...
	for (b=0; b<MAXBUCKET;b++) {
		Bucket *bb = sb->bucket[b];
		if (bb) {
			Slink *sl;
			for (sl=bb->listused;sl!=NULL;sl=sl->next) {
				if (!hword_find(wl,sl->src)) {
					hword_add(wl,sl->src);
				}
			}
			for (sl=bb->listfree;sl!=NULL;sl=sl->next) {
				if (!hword_find(wl,sl->src)) {
					hword_add(wl,sl->src);
				}
			}
			for (sl=bb->listpending;sl!=NULL;sl=sl->next) {
				if (!hword_find(wl,sl->src)) {
					hword_add(wl,sl->src);
				}
			}
		}
	}


	for (h=0; h<HWORDHASH; h++) {
		for (w=wl->table[h];w!=NULL;w=w->next) {
			memset(lines,0,sizeof(int)*10000);
			memset(totals,0,sizeof(int)*10000);
			memset(lines_f,0,sizeof(int)*10000);
			memset(totals_f,0,sizeof(int)*10000);
			for (b=0; b<MAXBUCKET;b++) {
				Bucket *bb = sb->bucket[b];
				if (bb) {
					Slink *sl;
					for (sl=bb->listused;sl!=NULL;sl=sl->next) {
						if (strcmp(sl->src,w->word)==0) {
							line = sl->line;
							line = line % 10000;
							lines[line]++;
							totals[line] += sl->used;
						}
					}
					for (sl=bb->listfree;sl!=NULL;sl=sl->next) {
						if (strcmp(sl->src,w->word)==0) {
							line = sl->line;
							line = line % 10000;
							lines_f[line]++;
							totals_f[line] += bb->sz;
						}
					}
				}
			}
			for (i=0; i<10000; i++) {
			 //if (totals[i]==0) continue;
			 if (lines[i]>0 ||lines_f[i]>0) fprintf(fout,"%8d %8d=u %8d=n %8d=f %8d=nf %s:%d\n"
					,totals_f[i]+totals[i],totals[i],lines[i],totals_f[i],lines_f[i],w->word,i);
			}
		}
	}
	free(lines); free(totals);
	free(lines_f); free(totals_f);
	hword_free(&wl);
	return TRUE;
}

void sm_status()
{
	// show all of them...
	int i;
	for (i=0; i<MAXID; i++) {
		Sblock *s = sblock[i];
		if (s!=NULL) {
			int b;
			emsg("Sblock %d\n",s->mid);
			printf("Sblock %d\n",s->mid);
			for (b=0; b<MAXBUCKET;b++) {
				Bucket *bb = s->bucket[b];
				if (bb!=NULL) {
					emsg("Bucket [%d] sz=%d nused=%d nfree=%d npending=%d total=%dk\n",b,bb->sz,bb->nused,bb->nfree,bb->npending,bb->nused+bb->nfree+bb->npending*bb->sz/1000);
					printf("Bucket [%d] sz=%d nused=%d nfree=%d npending=%d total=%dk\n",b,bb->sz,bb->nused,bb->nfree,bb->npending,bb->nused+bb->nfree+bb->npending*bb->sz/1000);
				}
			}
		}
	}
}
void s_malloc_test()
{
	int i;
	int ntest=1000;
	char *mem[1000];
	int len[1000];
	// Allocate some stuff
	printf("s_malloc: test allocations\n");
	for (i=0;i<ntest;i++) {
		len[i] = i;
		mem[i] = s_malloc(1,len[i],__FILE__,__LINE__);
		if (mem[i]==NULL) printf("NULL RETURNED FROM MALLLOC %d %p\n",i,mem[i]);
	}
	printf("s_malloc: test modify\n");
//	*(mem[4]+10) = 'z';
	for (i=0;i<ntest;i++) {
		if (mem[i]) memset(mem[i],i,len[i]);
	}
	s_dump("dump.log");
	// free it
	printf("s_malloc: test free\n");
	for (i=0;i<ntest;i++) {
		s_free(mem[i],__FILE__,__LINE__);
	}
//	*(mem[4]+10) = 'z';
	s_trim();
	s_testall();
	printf("s_malloc: finished\n");
	sm_status();
}
