#include "jcabc2ps.h"
#include "misc.h"

#define Chunk struct _chunk_
Chunk {
	void *p;		// Pointer
	int   s;		// Size
	int   u;		// Usage
	char  d[16];	// Description
} *chunk = 0;
int  chunks = 0;		// Chunk count
int  chunkm = 0;		// Chunk count max
long chunktotal = 0;	// Chunk size total
#define EXTRA 64		// Extra bytes on each chunk
#define MEM_FREE  1		// Freed chunk of memory
#define MEM_INUSE 2		// Alloc'd chunk of memory

int imin(int i, int j) {if (i <= j) return i; return j;}
int imax(int i, int j) {if (i >= j) return i; return j;}

int FindChunk(
	void *p,	// Pointer to chunk
	char *d)	// Description
{	int   i;
	for (i = 0; i < chunkm; i++) {
		if (chunk[i].p == p) {
			return i;
		}
	}
	return -1;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Register a new chunk of memory.  The chunk[] list  is  our  list  of  known
* chunks.   It's valid for a chunk to be added twice, since a freed chunk may
* be malloc'd again later.  So we look through the list for the chunk, and if
* we find it, we use its old entry.  We also complain if we are asked to make
* a dubious change of usage.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void NewChunk(
	void *p,	// Pointer to chunk
	int   s,	// Size of chunk (bytes)
	char *d,	// Description
	int   u)	// Usage code
{	char *F = "NewChunk";
	int  i, n;
	V3 "%s: %08lX s=%d u=%d=%s d=\"%s\" ...\n",F,(ulong)p,s,u,(u==1?"FREE":u==2?"INUSE":"???"),d V;
	if (chunks >= chunkm) {
		V3 "%s: Increase chunk table size from %d to %d.\n",F,chunkm,chunkm+100 V;
		chunk = (Chunk*)realloc(chunk,(chunkm+100)*sizeof(Chunk));
		if (!chunk) {
			V1 "%s: ### OUT OF SPACE (can't get %lu bytes for chunk list) ###\n",F,(chunkm+100)*sizeof(Chunk) V;
			chunkm = 0;
			return;
		}
		bzero(chunk+chunkm,100*sizeof(Chunk));	// Make sure the new entries are zeroed out.
		chunkm += 100;		// Up the chunk count.
	}
	for (i = 0; i < chunkm; i++) {
		V3 "%s: i=%d u=%d\n",F,i,u V;
		switch (chunk[i].u) {
		default:
			V1 "%s: ### MEM_type %d unknown, treated as MEM_INUSE ###\n",F,chunk[i].u V;
		case MEM_INUSE:
			if ((chunk[i].p != 0) && (chunk[i].p != p)) {
				continue;	/* It's another block */
			}
			if (chunk[i].p == p) {
				V1 "%s: ### chunk %08lX already marked INUSE ###\n",F,(ulong)p V;
				return;
			}
			if (chunk[i].p == p) {
				if (u == MEM_INUSE) {
					V1 "%s: ### chunk %08X already marked INUSE ###\n",F,(uint)p V;
					return;
				}
			}
			/* chunk[i].p == 0 but marked INUSE */
		case 0:		// Empty chunk struct
			chunktotal += (2 * s);
		case MEM_FREE:		// Chunk struct is free; use it
			if (u == MEM_FREE) {
				V1 "%s: ### chunk %08X already marked FREE ###\n",F,(uint)p V;
				return;
			}
			n = imin(15,strlen(d));	// Use up to 15 bytes of description
			chunk[i].u = u;
			chunk[i].s = s; chunktotal += s;
			chunk[i].p = p;
			strncpy(chunk[i].d,d,n);
			chunk[i].d[n] = 0;
			V3 "%s: %08X s=%d u=%d d=\"%s\" free chunk %d (total size = %ld).\n",F,(uint)p,chunk[i].s,chunk[i].u,chunk[i].d,i,chunktotal V;
			if (i >= chunks) chunks = i + 1;
			return;
		}
	}
	V1 "%s: ### AWK!! Ran off end of chunk list ###\n",F V;
	return;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Debug wrapper around malloc(). We also call NewChunk() to register the chunk *
* with our mem-debug gimmick.                                                  *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void *Malloc(
	size_t n,	/* Number of bytes */
	char  *d)	/* Description, for messages */
{	char  *F = "Malloc";
	void  *p = 0;
	n += (2 * EXTRA);	// For debugging memory problems.
	V3 "%s: Get %zd bytes for \"%s\"\n",F,n,d V;
	if ((p = (void*)malloc(n))) {
		p += EXTRA;
		V3 "%s: %08X %zd bytes \"%s\"\n",F,(uint)p,n,d V;
		NewChunk(p,n,d,MEM_INUSE);
		return p;
	}
	V3 "%s: ### Can't get %zd bytes for %s ###\n",F,n,d V;
	return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Debug wrapper around free().  This uses our mem-debug stuff to spot attempts *
* to  free  stuff that wasn't malloc'd or to free a chunk twice.  Returns 1 if *
* the chunk is freed, 0 otherwise.                                             *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Free(
	void *p,
	char *d)
{	char *F = "Free";
	int   i, j;
	if (!p) {
		V1 "%s: ### Attempt to free block %08X ###\n",F,(uint)p V;
		return;
	}
	if ((i = j = FindChunk(p,d)) < 0) {
		V1 "%s: ### Attempt to free unallocated block %08X ###\n",F,(uint)p V;
		i = i / (i - j);	// BOMB HERE!
		return;
	}
	if (chunk[i].u != MEM_INUSE) {
		V1 "%s: ### Attempt to free block %08X that is not INUSE ###\n",F,(uint)p V;
		return;
	}
	chunktotal -= chunk[i].s;
	V3 "%s: %08X \"%s\" is %d-byte chunk %d, total=%ld.\n",F,(uint)p,d,chunk[i].s,i,chunktotal V;
	chunk[i].u = MEM_FREE;
	p -= EXTRA;
	free(p);
}
