#define DMID 7
#include "debug.h"
#include "qmalloc.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*

Qmalloc - used for memory allocation that is fast (since malloc and free are very slow)
Most of the time mallocs are done in blocks, and we want to free them all at the end
, so we do just one real malloc, and all qmallocs take a chunk of the big malloc data.


e.g.

int i;
char *data[2000];
QMalloc *q=qmalloc_new(5000);  // Allocate a 5k block. All future qmallocs/qstrupds are taken out of this block, with new blocks allocated when needed.
for(i=0;i<2000;i++) {
	data[i]=qstrdup(q,"abc");
}
qmalloc_free_all(q);


NOTE ABOUT QMALLOC

Normally pointers returned by qmalloc are aligned on 4 or 8 bytes boundaries
on unix machines, so
they can be used for storing pointers and ints without "Bus Error" on Solaris-Sparc
and other similar architectures.

If data is only going to be used for storing single byte chars, you can use
qmalloc_unaligned instead of qmalloc to save on memory.


*/


#ifdef WIN32
#define POINTER_ALIGNMENT 1
#define INT_ALIGNMENT 1
#define DOUBLE_ALIGNMENT 1
#endif


#ifndef POINTER_ALIGNMENT
#define POINTER_ALIGNMENT sizeof(void *)
#endif
#ifndef INT_ALIGNMENT
#define INT_ALIGNMENT sizeof(int)
#endif
#ifndef DOUBLE_ALIGNMENT
#define DOUBLE_ALIGNMENT sizeof(double)
#endif

#ifndef max
#define max(a,b) ((a)>(b)?(a):(b))
#endif

#define ALIGNMENT_1 (max(POINTER_ALIGNMENT,INT_ALIGNMENT))
#define ALIGNMENT_2 (max(ALIGNMENT_1,DOUBLE_ALIGNMENT))
/*
Platforms such as Solaris_Sparc require 4
Alpha requires 8
*/

Qmalloc *qmalloc_new_do(int chunksize, char *file, int line)
{
	Qmalloc qbf;
	Qmalloc *q;
	qbf.mlist=NULL;
	qbf.mcur=NULL;
	qbf.zleft=0;
	qbf.zupto=0;
	qbf.file=file;
	qbf.line=line;
	qbf.total_allocated=0;
	qbf.total_leeked=0;
	qbf.totused=0;
	if (chunksize<1000) chunksize=1000;
	qbf.chunksize=chunksize;
	q=qmalloc(&qbf,sizeof(qbf));
	memcpy(q,&qbf,sizeof(qbf));
	q->original_chunksize=q->chunksize;
	q->original_total_allocated=q->total_allocated;
	q->original_upto=q->zupto;
	return q;
}

void qmalloc_allocate_chunk(Qmalloc *q)
{
	QMalloclist *m;
	int alloc_size;
	if (q->chunksize<sizeof(QMalloclist)+100) q->chunksize=sizeof(QMalloclist)+100;
	alloc_size=q->chunksize+sizeof(QMalloclist);
	q->totused+=alloc_size;
#ifdef USE_DMALLOC
	m = d_mallocid(DMID,alloc_size,q->file,q->line);
//	m = simple_malloc(alloc_size);
#else
	m = malloc(alloc_size);
#endif
	memset(m,0,alloc_size);
	q->zupto=sizeof(QMalloclist);
	q->zleft=alloc_size-q->zupto;
	m->data = (char *)m;
	m->next=NULL;
	if (q->mcur==NULL) {
		q->mlist = q->mcur = m;
	} else {
		q->mcur->next = m;
		q->mcur = m;
	}
}

void *qmalloc2(Qmalloc *q, int sz, int alignby)
{
	char *s;
/*	dmsg("zmalloc(%d) (%d %p)\n",sz,zleft,mcur);*/
	if (alignby>1) {
		int md=q->zupto % alignby;
		if (md!=0) {
			int newupto=q->zupto+(alignby-md);
			q->zleft-=(newupto-q->zupto);
			q->zupto=newupto;
		}
	}

	if (sz>=q->zleft) {

		q->chunksize*=2; /* In case initial chunksize is inadequate, we eventually compensate by doubling chunksize whenever it gets used up. */
		if (sz>q->chunksize)
			q->chunksize=sz;
		qmalloc_allocate_chunk(q);

	}
	s = q->mcur->data + q->zupto;
	q->zupto += sz;
	q->zleft -= sz;
	q->total_allocated+=sz;
	return s;
}
void *qcalloc(Qmalloc *q, int sz)
{
	void *p = qmalloc(q,sz);
	memset(p,0,sz);
	return p;
}
void *qmalloc(Qmalloc *q, int sz)
{
	return qmalloc2(q,sz,ALIGNMENT_2);
}

void *qmalloc_unaligned(Qmalloc *q, int sz)
/*
Same as qmalloc, but the returned pointer is 4-byte aligned.
Essential for non-char * allocations on Solaris-Sparc.
*/
{
	return qmalloc2(q,sz,1);
}

char *qstrdup(Qmalloc *q, char *s)
{
	char *n;
	if (s==NULL) return NULL;
	if (*s==0) return "";
	n = qmalloc_unaligned(q,(int)strlen(s)+1);
	strcpy(n,s);
	return n;
}

void qfree(Qmalloc *q, int s)
{
	q->total_leeked+=s;
}

void qfreestr(Qmalloc *q, char *s)
{
	if (s)
		q->total_leeked+=(int)strlen(s)+1;
}

int qtotal_leeked(Qmalloc *q)
{
	if (!q) return 0;
	return q->total_leeked;
}


void qmalloc_free_all_do(Qmalloc *q, char *file, int line)
{
	QMalloclist *m,*mnext;
	if (!q) return;
	for (m=q->mlist;m!=NULL;m=mnext) {
		mnext = m->next;
#ifdef USE_DMALLOC
		d_free(m->data,file,line);
//		simple_free(m->data,file,line);
#else
		free(m->data);
#endif
	}
}

void qmalloc_free_blocks_do(Qmalloc *q, char *file, int line)
/*
Frees all memory alloacted using the qmalloc structure, but not
the qmalloc structure itself. You should use qmalloc_free_all instead 
in most situations.
*/
{
	QMalloclist *m,*mnext;
	if (!q) return;
	m=q->mlist;
	if (!m) return;
	q->chunksize=q->original_chunksize;
	q->total_allocated=q->original_total_allocated;
	q->zupto=q->original_upto;
	q->zleft=q->chunksize-q->total_allocated;
	q->totused=q->chunksize+sizeof(QMalloclist);
	for (m=q->mlist->next;m!=NULL;m=mnext) {
		mnext = m->next;
#ifdef USE_DMALLOC
		d_free(m->data,file,line);
//		simple_free(m->data,file,line);
#else
		free(m->data);
#endif
	}
	q->mlist->next=NULL;
	q->mcur=q->mlist;

}



int qmalloc_test(void)
{
	char *p[1000];
	int plen[1000];
	int i;
	Qmalloc *q = qmalloc_new(1000);
	for (i=0;i<1000;i++) {
		int len = rand()%200;
		plen[i] = len;
		p[i] = qcalloc(q,len); 
	}
	for (i=0;i<1000;i++) {
		memset(p[i],1,plen[i]);
	}
	qmalloc_free_all(q);
	return 0;
}

