#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include "nofrag.h"
#ifdef UDMALLOC
#include <dmalloc.h>
#endif

#define lib_abort() lib_abort_real(__FILE__,__LINE__)
void lib_abort_real(char *src, int line);
// malloc that doesn't fragment,
// mutexes must be around all calls to these functions....

typedef struct MBLOCK {void **data; struct MBLOCK *next; struct MBLOCK *last; int size; 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);

#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] = nofrag_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();
			}
	
			nofrag_free(m[k]); m[k] = NULL;
		}
		if (k%3==1) {
			m[k] = nofrag_malloc(want);
			sz[k] = want;
			memset(m[k],k%100,sz[k]);
		}
	}
	for (i=1; i<1000; i++) {
		if (m[i]) nofrag_free(m[i]);
	}
	nofrag_stats(stdout);


}
#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;
}
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 *nofrag_realloc(void *x, int sz)
{
	void **px = x;
	Block *b = px[-2];
	void *n;
	if (x==NULL) return nofrag_malloc(sz);
	n = nofrag_malloc(sz);
	if (n==NULL) return NULL;
	memcpy(n,x,b->size);
	nofrag_free(x);
	return n;
}
void *nofrag_malloc(int x)
{
	int k;
	Blocklist *bl=NULL;
	Block *b;
	int len=0;
	if (nb==0) nofrag_init();
	if (x>max) {
		b = calloc(1,sizeof(Block));
		if (b==NULL) return NULL;
		len = (x/sizeof(void *)) + 3;
		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 *) + 2;
		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);
		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:
	// Return the block
//	memset(b->data,2,len);
	b->data[0] = b; // The block for this object.
	b->data[1] = (void *) 0x11223344; // Magic number.
	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 nofrag_free(void *x)
{
	void **px = x;
	Block *b;
	int m;
	int k;
	Blocklist *bl;

	if (x==NULL) return;
	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();	
	}
	px[-1] = 0; // Clear magic number to prevent double free.
	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....
	bl = &blocks[k];
	nofrag_remove(&bl->used,b);
	bl->nused--;
	
	// And insert into 'free' list
	nofrag_insert(&bl->free,b);
	bl->nfree++;
}

