//
//memiip.c
//
//Provides wrappers to the functions: malloc, realloc, free
//
//Currently these wrappers will erase allocated memory before use and 
// erase deallocated memory after use.
//
//
//-UserX 2001/11/02
//
//todo: The current system of handling memtags will not scale. If it proves to be a bottle neck
// the should be replaced with a smarter system.
//
//todo: This will need to be made thread safe if IIP goes multithreaded.
//
//-UserX 2001/11/03

/**
Functions for handling memory. <P>
@author UserX
@name memory
*/
//@{

#include "misc/compat.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <malloc.h>
#include <memory.h>
#include <assert.h>
#include "base/mem.h"
#include "misc/exitcode.h"
#include "base/logger.h"


char memabortbuffer[4096] = {0};


typedef struct MemHeader {
	char *group;
	char *subgroup;
	int mtag;
	size_t size;
} MemHeader;

#define MEMTAGLISTBASE 1023 //63
#define MEMTAGLISTSTEP 1024 //64

typedef struct MemTag {
	MemHeader *membuffer;
	int freetag;
} MemTag;

MemTag *mtlist = NULL;
int mtlistsize = 0;
int mtlistmax = 0;
int *mtfree = NULL;
int mtfreesize = 0;
int mtfreemax = 0;

int memtotal = 0;
int memalloctotal = 0;
int memfreetotal = 0;

#define ASSERTMEMHEADER(x) {assert(x->mtag < mtlistmax && x->mtag >= 0); assert(mtlist[x->mtag].membuffer == x);}
	

/**
Initializes the memory management. 
This must be called before any calls to other memory management functions.
*/
void memInit(void) {
	int i;
	if(mtlist == NULL) {
		mtlist = malloc(MEMTAGLISTBASE * sizeof(MemTag));
		mtlistmax = MEMTAGLISTBASE;
		for(i = 0; i < mtlistmax; i++) {
			mtlist[i].membuffer = NULL;
			mtlist[i].freetag = -1;
		}
	}
	if(mtfree == NULL) {
		mtfree = malloc(mtlistmax * sizeof(int));
		mtfreemax = mtlistmax;
		mtfreesize = 0;
		for(i = 0; i < mtlistmax; i++) {
			if(mtlist[i].membuffer == NULL) {
				mtlist[i].freetag = mtfreesize;
				mtfree[mtfreesize++] = i;
			}
		}
	}
}


// expand the number of references to free tags
void memTagExpandFree(void) {
	mtfreemax += MEMTAGLISTSTEP;
	mtfree = realloc(mtfree, mtfreemax * sizeof(int));
}

// expand the number of used tags
void memTagExpandList(void) {
	MemTag *newlist;
	int newsize;
	int i; 
	unsigned int j;
	int k;

	newsize = mtlistmax + MEMTAGLISTSTEP;
	newlist = malloc(newsize * sizeof(MemTag));

	for(i = 0; i < newsize; i++) {
		newlist[i].freetag = -1;
		newlist[i].membuffer = NULL;
	}
	for(i = 0; i < mtlistmax; i++) {
		if(mtlist[i].membuffer != NULL) {
			for(j = (unsigned int)(void *) mtlist[i].membuffer, k = 0;k < newsize; j++, k++) {
				j %= newsize;
				if(newlist[j].membuffer == NULL) {
					newlist[j] = mtlist[i];
					newlist[j].membuffer->mtag = j;
					break;
				}
			}
		}
	}
	free(mtlist);
	mtlist = newlist;
	mtlistmax = newsize;

	mtfreesize = 0;

	for(i = 0; i < mtlistmax; i++) {
		if(mtlist[i].membuffer == NULL) {
			mtlist[i].freetag = mtfreesize;
			mtfree[mtfreesize++] = i;
		}
		if(mtfreesize >= mtfreemax) {
			memTagExpandFree();
		}
	}
}


//verify a buffer is in the tag list
//returns non-zero if the tag is unknown
int memTagCheck(void *membuffer) {
	int i;
	unsigned int j;
	j = (unsigned int) membuffer;
	for(i = 0; i < mtlistmax; i++, j++) {
		j %= mtlistmax;
		if(mtlist[j].membuffer == membuffer) {
			return 0;
		}
	}
	return 1;
}

//add a buffer to the taglist
void memTagAdd(void *membuffer) {
	int i;
	unsigned int j;
	if(mtlistsize >= mtlistmax) {
		memTagExpandList();
	}
	for(j = (unsigned int) membuffer, i = 0; i < mtlistmax; j++, i++) {
		j %= mtlistmax;
		if(mtlist[j].membuffer == NULL) {
			mtlist[j].membuffer = membuffer;
			mtlistsize++;
			mtfreesize--;
			mtfree[mtlist[j].freetag] = mtfree[mtfreesize];
			mtlist[mtfree[mtlist[j].freetag]].freetag = mtlist[j].freetag;//j;
			mtlist[j].freetag = -1;
			((MemHeader *)membuffer)->mtag = j;
			return;
		}
	}
	assert(0); //this routine must exit before the for loop can finish
}

//remove a buffer from the tag list
void memTagDelete(void *membuffer) {
	int i;
	if(mtfreesize >= mtfreemax) {
		memTagExpandFree();
	}

	ASSERTMEMHEADER(((MemHeader *)membuffer));
	i = ((MemHeader *) membuffer)->mtag;
	//assert(i < mtlistmax);
	//assert(mtlist[i].membuffer == membuffer);

	mtlist[i].freetag = mtfreesize;
	mtfree[mtfreesize++] = i;

	mtlistsize--;

	mtlist[i].membuffer = NULL;

}

/**
Test to see if a memory pointer allocated by the memory manager. 
@return Non-zero if the pointer doesn't belong to the memory manager. Zero if it does. 
*/
int memUnknown(void *membuffer) {
	MemHeader *mh;
	if(membuffer == NULL) {
		return 1;
	}

	mh = (MemHeader *)(((uint8 *) membuffer) - sizeof(MemHeader));
	return memTagCheck(mh);
}



/**
Allocate memory. 
@param memsize The number of bytes of memory to allocate. 
@param group What "group" this memory allocation belongs to. Useful for identifying memory leaks. 
@param subgroup What "sub-group" this memory allocation belongs to. Useful for identifying memory leaks. 
@return Pointer to the created memory buffer.
*/
void *memAlloc(size_t memsize, char *group, char *subgroup) {
	MemHeader *mh;

	memtotal += memsize;
	memalloctotal += memsize;

	mh = malloc(memsize + sizeof(MemHeader));

	if(mh == NULL) {
		assert(mh != NULL);
		abort();
	}

	memset(mh, 0, memsize + sizeof(MemHeader));

	memTagAdd(mh);

	mh->size = memsize;
	mh->group = group;
	mh->subgroup = subgroup;

	return ((uint8 *) mh) + sizeof(MemHeader);
}

/**
Release allocated memory.
@param membuffer Pointer to the memory buffer to be released.
*/
void memFree(void *membuffer) {
	MemHeader *mh;
	if(membuffer == NULL) {
		return;
	}

	mh = (MemHeader *)(((uint8 *) membuffer) - sizeof(MemHeader));
	memTagDelete(mh);

	memtotal -= mh->size;
	memfreetotal += mh->size;

	memset(mh, 0, mh->size + sizeof(MemHeader));
	free(mh);

}

/**
Release allocated memory but will not fail if the buffer does not belong to it. 
@param membuffer Pointer to the memory buffer to be released.
*/
void memSafeFree(void *membuffer) {
	MemHeader *mh;
	if(membuffer == NULL) {
		return;
	}

	mh = (MemHeader *)(((uint8 *) membuffer) - sizeof(MemHeader));
	if(memTagCheck(mh) == 0) {
		memFree(membuffer);
	}
	return;
}

/**
Resize allocated memory. 
@param membuffer Pointer to the memory buffer to be resized.
@param memsize The new size for the buffer.
@return The new pointer to the memory buffer.
*/
void *memResize(void *membuffer, size_t memsize) {
	MemHeader *mh;
	MemHeader *omh;
	if(membuffer == NULL) {
		return memAlloc(memsize, NULL, NULL);
	}

	omh = (MemHeader *)(((uint8 *) membuffer) - sizeof(MemHeader));
	mh = malloc(memsize + sizeof(MemHeader));

	assert(mh != NULL);

	memTagDelete(omh);

	memtotal -= omh->size;
	memtotal += memsize;
	memalloctotal += memsize;
	memfreetotal += omh->size;


	memset(mh, 0, memsize + sizeof(MemHeader));

	memTagAdd(mh);

	mh->size = memsize;
	mh->group = omh->group;
	mh->subgroup = omh->subgroup;

	if(omh->size < mh->size) {
		memsize = omh->size;
	} else {
		memsize = mh->size;
	}

	memcpy(((uint8 *) mh) + sizeof(MemHeader), ((uint8 *) omh) + sizeof(MemHeader), memsize);

	memset(omh, 0, omh->size + sizeof(MemHeader));
	free(omh);

	return ((uint8 *) mh) + sizeof(MemHeader);
	
}

/**
Duplicates an existing memory allocation.
@param membuffer A pointer to an existing memory allocation
@return A pointer that contains a copy of the source buffer.
*/
void *memClone(void *membuffer) {
	MemHeader *mh;
	if(membuffer == NULL) {
		return NULL;
	}
	mh = (MemHeader *)(((uint8 *) membuffer) - sizeof(MemHeader));
	ASSERTMEMHEADER(mh);
	
	return memCopy(membuffer, mh->size, mh->group, mh->subgroup);
}

/**
Creates a new allocation by copying a buffer.
@param membuffer A pointer to the buffer to copy. 
@param memsize The number of bytes of memory to allocate. 
@param group What "group" this memory allocation belongs to. Useful for identifying memory leaks. 
@param subgroup What "sub-group" this memory allocation belongs to. Useful for identifying memory leaks. 
@return A pointer to the created memory buffer that contains a copy of the source buffer.
*/
void *memCopy(void *membuffer, size_t memsize, char *group, char *subgroup) {
	void *dstbuffer;
	if(membuffer == NULL) {
		return NULL;
	}
	if(memsize == 0) {
		return NULL;
	}

	dstbuffer = memAlloc(memsize, group, subgroup);
	memcpy(dstbuffer, membuffer, memsize);
	return dstbuffer;
}


/**
Return the "sub-group" descriptor for this memory allocation. 
@param membuffer A pointer to an exist buffer allocation. 
@return A pointer to the "sub-group" descriptor.
*/
char *memGetSubgroup(void *membuffer) {
	MemHeader *mh;
	mh = (MemHeader *)(((uint8 *) membuffer) - sizeof(MemHeader));
	ASSERTMEMHEADER(mh);
	return mh->subgroup;
}

/**
Return the "group" descriptor for this memory allocation. 
@param membuffer A pointer to an exist buffer allocation. 
@return A pointer to the "group" descriptor.
*/
char *memGetGroup(void *membuffer) {
	MemHeader *mh;
	mh = (MemHeader *)(((uint8 *) membuffer) - sizeof(MemHeader));
	ASSERTMEMHEADER(mh);
	return mh->group;
}



/**
Dumps a memory map. 
Useful for spotting memory leaks.
*/
void memDump(void) {
	int i;
	char *extra = NULL;
	sprintf(memabortbuffer, "mem(%4d/%4d) used(%d) total:allocs(%d),frees(%d)", mtlistsize, mtlistmax, memtotal, memalloctotal, memfreetotal);
	logmsgsafe(memabortbuffer);
	for(i = 0; i < mtlistmax; i++) {
		if(mtlist[i].membuffer != NULL) {
			extra = NULL;
			if(mtlist[i].membuffer->group != NULL) {
				if(strcmp(mtlist[i].membuffer->group, "String") == 0) {
					extra = ((char *) mtlist[i].membuffer) + sizeof(MemHeader);
				}
			}
			sprintf(memabortbuffer, "mem[%4d]:%p,%8x:\"%s\",\"%s\":\"%.2048s\"", i, mtlist[i].membuffer, mtlist[i].membuffer->size, mtlist[i].membuffer->group, mtlist[i].membuffer->subgroup, extra);
			logmsgsafe(memabortbuffer);
		}
	}
		
}

//@}
