#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
#include "dmalloc.h"
#undef malloc
#undef free
#undef calloc
#undef realloc
#undef strdup


typedef struct MBLOCK {void **data; struct MBLOCK *next; struct MBLOCK *last; int size; char *file; int line; char k; char id; char isbig; } Block;
typedef struct BLOCKLIST {Block *used; Block *free; int nused; int nfree; int sz;  } Blocklist;

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

void nofrag_insert(Block **first, Block *b);
void nofrag_remove(Block **first, Block *b);
static void (*abort_fn)(int x);
static int free_check=FALSE; // SEt to true to check free'd memory isn't modified.

#ifdef DO_THREADS
//Mutex *th_mutex_new(char *name);
static Mutex *m;
#endif


#ifdef TESTING
int main(int argc, char *argv[]) 
{
	// Do some tests on my malloc function.
	char *m[1000];
	int sz[1000];
	int i;
	int b;
	char *s;
	for (i=0; i<1000; i++) {
		int want = rand()%3000;
		if (want<0) want = -want;
		m[i] = d_malloc(want);
		sz[i] = want;
		memset(m[i],i%100,sz[i]);
	}
	for (i=0; i<1000000; i++) {
		int k;
		int want = rand()%8000;
		if (want<0) want = -want;
		k = rand() % 1000;

		s=m[k];
		if (s!=NULL) {
			for (b=0; b<sz[k]; b++) {
				if (s[b]!=k%100) lib_abort();
			}
	
			d_free(m[k]); m[k] = NULL;
		}
		if (k%3==1) {
			m[k] = d_malloc(want);
			sz[k] = want;
			memset(m[k],k%100,sz[k]);
		}
	}
	for (i=1; i<1000; i++) {
		if (m[i]) d_free(m[i]);
	}
	nofrag_stats(stdout);


}
#endif


static int (*emsg)(const char * arg_list, ...);
static int (*imsg)(const char * arg_list, ...);
void d_malloc_init(int (*vemsg)(const char * arg_list, ...))
{
	static int done;
	if (done) return ;
	done = TRUE;
	emsg = vemsg;
	imsg = vemsg;
#ifdef DO_THREADS
	if (m==NULL) m = th_mutex_new("dmalloc");
#endif
}
static int mutex_line;
#ifndef MUTEX_TIME
//#define MUTEX_TIME 600000 chrisp changed so it will timeout BEFORE other mutexes so we will know if it might be a deadlock or really a memory mutex issue. 
#define MUTEX_TIME 100000
#endif
void d_mutex_wait(int line)
{
#ifdef DO_THREADS
	if (m==NULL) return;
	if (!th_mutex_wait(m,MUTEX_TIME)) { /* 10 minutes */
		if (!th_mutex_wait(m,10000)) { /* another 10 seconds, incase it came back to life*/
				lib_abort();
		}
	}
	mutex_line = line;
#endif
}
void d_mutex_release(void)
{
#ifdef DO_THREADS
	if (m==NULL) return;
	th_mutex_release(m);
#endif
}

static Blocklist blocks[32];
static int nb,max;
int nofrag_init(void)
{
	int sz=8;
	int i;
	for (i=0; i<30; i++) {
		if (sz>900000) break; 
		blocks[i].sz = sz;
		max = sz;
		sz *= 2;

	}
	nb = i;
	return TRUE;
}
int nofrag_stats(FILE *f)
{
	int i;
	int nu=0;
	int nf=0;
	Block *b;
	int totused=0;
	int totfree=0;
	for (i=0; i<nb; i++) {
		Blocklist *bl = &blocks[i];
		fprintf(f,"Block[%d] size=%d, nused=%d nfree=%d %dk\n",i,bl->sz,bl->nused,bl->nfree,bl->sz*(bl->nused+bl->nfree)/1024);
		totfree += bl->sz * bl->nfree;
		totused += bl->sz * bl->nused;
		nu = 0; nf = 0;
		for (b=bl->used;b!=NULL;b=b->next) nu++;
		for (b=bl->free;b!=NULL;b=b->next) nf++;
		if (nu!=bl->nused) fprintf(f,"Block mismatch1 %d %d\n",nu,bl->nused);
		if (nf!=bl->nfree) fprintf(f,"Block mismatch2 %d %d\n",nf,bl->nfree);
	}
	fprintf(f,"Total memory usage %dmb of %dmb\n",(totused)/(1024*1024),(totfree+totused)/(1024*1024));
	return TRUE;
}

static int nsimple, simple_bytes;

char * d_info(char *info)
{
	int i;
	int nu=0;
	int nf=0;
	int totused=0;
	int totfree=0;
	for (i=0; i<nb; i++) {
		Blocklist *bl = &blocks[i];
		totfree += bl->sz * bl->nfree;
		totused += bl->sz * bl->nused;
		nu += bl->nused;
	}
	sprintf(info,"Memory Blocks (%d) %dmb/%dmb  simple %d %dk",nu,totused/(1024*1024),(totused+totfree)/(1024*1024),nsimple,simple_bytes/1000);
	return info;
}


int nofrag_bucket(int sz)
{
	int lo=0;
	int hi=nb-1;
	int k;
	k = nb/2;
	for (;TRUE;) {
		if (blocks[k].sz>=sz) {
			hi = k;
			k = (hi-lo)/2 + lo;	
		} else {
			lo = k+1;
			k = (hi-lo)/2 + lo;	
		}
		if (hi-lo<1) {
			k = lo;
			break;
		}
	}		 
	if (blocks[k].sz<sz) {
		k++;
		if (blocks[k].sz<sz) lib_abort();
	}
	return k;	
}
void *d_realloc(void *x, int sz, char *file, int line)
{
	void **px = x;
	void *n;
	int szcopy;
	Block *b;
	//if (strstr(file,"gd")!=NULL) printf("realloc %d %p %s:%d\n",sz,x,file,line);
	if (x==NULL) return d_malloc(sz,file,line);
	d_check_pointer(x,file,line);
	n = d_malloc(sz,file,line);
	if (n==NULL) return NULL;
	b = px[-2];
	szcopy = b->size;
	if (sz<szcopy) szcopy = sz;
	//if (strstr(file,"gd")!=NULL) printf("realloc %d %p %s:%d copying %d  bytes\n",sz,x,file,line,szcopy);
	memcpy(n,x,szcopy);
	d_free(x,file,line);
	return n;
}
void *d_calloc(size_t num, size_t size, char *fname, int line)
{
	void *x;
//printf("calloc %d %d %s:%d\n",num,size,fname,line);
	//if (strstr(fname,"gd")!=NULL) printf("calloc %d %p %s:%d\n",num,size,fname,line);
	x = d_malloc(num*size,fname,line);
	if (x==NULL) {
		emsg("In %s:%d, calloc(%d) failed\n", fname, line, size*num);
		lib_abort();
	}
	memset(x,0,num*size);
	return x;
}
void dmalloc_ignore_leak_do(char *file, int line)
{
}
void d_dump(void)
{
}
char *d_strdup(const char *s, char *file, int line)
{
	char *m;
	int len;
	len = strlen(s)+1;
	m = d_malloc(len,file,line);
	memcpy(m,s,len);
	return m;
}
static int (*die_fn)(char *reason);
void d_set_die(int (*my_die_fn)(char *reason))
{
	die_fn = my_die_fn;
}

void dmalloc_set_dump(int xdump_to_stdout, int xdump_to_emsg)
{
//	dump_to_stdout=xdump_to_stdout;
//	dump_to_emsg=xdump_to_emsg;
}
int d_log_big(char *fname)
{
		return TRUE;
}
int d_check_pointer(char *x, char *src, int line)
{
	void **px = (void **) x;
	Block *b;
	int m;
	int end;

	if (x==NULL) return TRUE;
	b = px[-2];
	m = (int) px[-1];
	if (b->data != px-2) lib_abort(); // another double check	
	if (m!=0x11223344) { // Check the magic number.
		lib_abort();	
	}
	end = (b->size+(sizeof(char *)-1))/sizeof(char *) ;
	if (px[end]!=(void *) 0x11223344) { // Check the magic number at the end of the block too.
		lib_abort();
	}
	return TRUE;
}
int d_free_idle(void)
{
	return TRUE;
}
void d_check(void)
{

}
int d_free_check(void)
{
	return TRUE;
}
void dmalloc_set_emsg(int (*vemsg)(const char * arg_list, ...))
{
	emsg=vemsg;
}
int d_free_set_big(int x)
{
	return TRUE;
}

void *d_malloc_checked(int x, char *file, int line)
{
	return d_malloc(x,file,line);
}
void *d_malloc(int x, char *file, int line)
{
	int k;
	Blocklist *bl=NULL;
	Block *b;
	int end=0;
	int len=0;

//	x+=8; // testing...

//printf("malloc %d %s:%d\n",x,file,line);
	d_mutex_wait(__LINE__);
	if (nb==0) nofrag_init();
	if (x>max) {
		b = calloc(1,sizeof(Block));
		if (b==NULL) return NULL;
		len = (x/sizeof(void *)) + 4;
		b->data = calloc(sizeof(void *),len);
		if (b->data==NULL) return NULL;
		b->k = 0;
		b->size = x;
		b->id = 33;
		b->isbig = TRUE; // stand alone block, no linked list....
		goto ret;
	}
	k = nofrag_bucket(x);
	bl = &blocks[k];
	b = bl->free;

	if (b==NULL) { // create new one
		b = calloc(1,sizeof(Block));
		if (b==NULL) return NULL;
		len = bl->sz/sizeof(void *) + 3;
		b->data = calloc(sizeof(void *),len);
		if (b->data==NULL) return NULL;
		b->k = k;
		b->size = x;
		b->id = 33;
	} else { // remove from head of free list
		if (b->id != 22) lib_abort(); // Shoudl be set to this while in freelist.
		nofrag_remove(&bl->free,b);
		if (free_check) { // Check contents of free'd data has not been changed 
			int i;
			int l = b->size/sizeof(char *);
			for (i=0; i<l; i++) {
				if (b->data[i]!=(void *) b->size ) {  // 0x4455667788
					char *s = NULL;
					printf("Crashing corrupted %s:%d\n",b->file,b->line);
					*s = 0;
				}
			}
		}
		b->id = 33;
		b->size = x; // Current allocated size
		bl->nfree--;
		len = bl->sz/sizeof(void *) + 2;
	}	

	// Insert onto dbl link, 'used' linked list.
	nofrag_insert(&bl->used,b);
	bl->nused++;

ret:
	b->file = file;
	b->line = line;
	// Return the block
//	memset(b->data,2,len);
	b->data[0] = b; // The block for this object.
	b->data[1] = (void *) 0x11223344; // Magic number.
	end = (b->size+(sizeof(char *)-1))/sizeof(char *) + 2;
	b->data[end] = (void *) 0x11223344; // Magic number.
	d_mutex_release();
	//if (strstr(file,"gd")!=NULL) printf("malloc %d %p %s:%d\n",x,(b->data+2),file,line);
	return b->data+2;
}
void nofrag_remove(Block **first, Block *b)
{
	if (b->last!=NULL) {
		b->last->next = b->next;		
	} else {
		if (*first!=b) lib_abort(); // This should be true....
		b->last = NULL;
		*first = b->next;
	}
	if (b->next!=NULL) b->next->last = b->last;

	b->last = NULL;
	b->next = NULL;
}
void nofrag_insert(Block **first, Block *b)
{
	b->next = *first;
	if (*first !=NULL) (*first)->last = b;
	b->last = NULL;
	*first = b;
}
void nofrag_debug(Blocklist *bl, Block *th)
{
	Block *b;
	int max=0;
	for (b=bl->used;b!=NULL;b=b->next,max++) {
		if (max>10000) break;
		if (th==b) continue;
		if (b->data==th->data) lib_abort();
	}
	max = 0;
	for (b=bl->free;b!=NULL;b=b->next,max++) {
		if (max>10000) break;
		if (th==b) continue;
		if (b->data==th->data) lib_abort();
	}
}

void d_free(void *x, char *file, int line)
{
	void **px = x;
	Block *b;
	int k;
	Blocklist *bl;

	if (x==NULL) return;
	//if (strstr(file,"gd")!=NULL) printf("free %p %s:%d\n",x,file,line);
	d_check_pointer(x,file,line);
	px[-1] = (void *) 0x12121212; // Clear magic number to prevent double free.
	b = px[-2];
	k  = b->k;

	if (b->isbig) {
		free(b->data);
		free(b);
		return;
	}
	bl = &blocks[k];

// Scan the 'used' linked list for overlapping memory....
//	nofrag_debug(bl,b);


	if (x==NULL) return;
	if (b->id!=33) lib_abort();
	b->id = 22;
	// Remove from 'used' list....
	d_mutex_wait(__LINE__);
	bl = &blocks[k];
	nofrag_remove(&bl->used,b);
	bl->nused--;
	
	// And insert into 'free' list
	nofrag_insert(&bl->free,b);
	bl->nfree++;

	if (free_check) { // Check contents of free'd data has not been changed 
		int l = b->size/sizeof(char *);
		int i;
		for (i=0; i<l; i++) {
			b->data[i] = (void *) b->size; //0x4455667788;
		}
	}

	d_mutex_release();
}

void d_set_abort(void (*fn)(int x))
{
	abort_fn = fn;
#ifdef DO_LOW
	low_set_abort(fn);
#endif
}
void d_lib_abort(int n)
{
	if (abort_fn!=NULL) abort_fn(n);
	abort();
}





/*
header
	
code
length
DATA...

*/
#define SIMPLE_CODE 0x12345678
int simple_abort(int *xi, char *reason)
{
	emsg("Crashing because malloc fault (%s) %x %d \n",reason,xi[0],xi[1]);
	d_lib_abort(74);
	return TRUE;
}
void simple_free(void *x, char *src, int line)
{
	int *xi = (int *)x;
	if (x==NULL) return;
	xi-=2;
	nsimple--;
	simple_bytes -= xi[1];
	if (xi[0] != SIMPLE_CODE) {
		emsg("Bad simple free %s:%d\n",src,line);
		simple_abort(xi,"simple_free");
	}
	free(xi);
}

void *simple_malloc(int n)
{
	int *xi;
	nsimple++;
	xi = malloc(n +  sizeof(int)*2);
	xi[0] = SIMPLE_CODE;
	xi[1] = n;
	simple_bytes += xi[1];
	return (void *) (xi+2);
}
void *simple_realloc(void *x, int sz)
{
	int *xi = (int *)x;
	if (x==NULL) return simple_malloc(sz);
	xi-=2;
	if (xi[0] != SIMPLE_CODE) simple_abort(xi,"simple_realloc");
	simple_bytes -= xi[1];
	xi = realloc(xi,sz +  sizeof(int)*2);
	xi[0] = SIMPLE_CODE;
	xi[1] = sz;
	simple_bytes += xi[1];
	return (void *) (xi+2);
}
void *simple_calloc(int num, int size)
{
	int *xi;
	int n = num * size;
	nsimple++;
	xi = malloc(n +  sizeof(int)*2);
	memset(xi,0,n +  sizeof(int)*2);
	xi[0] = SIMPLE_CODE;
	xi[1] = n;
	simple_bytes += xi[1];
	return (void *) (xi+2);
}
char *simple_strdup(const char *s)
{
	char *n = simple_malloc(strlen(s)+1);
	strcpy(n,s);
	return n;
}



static int nreal;
void real_free(void *x){
	nreal--;
	free(x);
}

void *real_malloc(int n){
	nreal++;
	return malloc(n);
}
void *real_realloc(void *x, int sz)
{
	if (x==NULL) nreal++;
	return realloc(x,sz);
}
void *real_calloc(int num, int size)
{
	nreal++;
	return calloc(num,size);
}
char *real_strdup(const char *s)
{
	nreal++;
	return strdup(s);
}























