/**************************************************************

mem.c (C-Munipack project)
Memory management routines
Copyright (C) 2003 David Motl, dmotl@volny.cz

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************/

#define CMPACK_MEM_DO_NOT_USE_DEFINES

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "config.h"
#include "comfun.h"
#include "mem.h"

#ifdef CMPACK_DEBUG_HEAP

/*********************   DEBUG HEAP   *****************************/

/* Number of bits from address used for hashing */
#define HASH_BITS	12

/* How many least significant bits shall be ommited from a hash */
#define HASH_SKIP	3

/* Size of hash table */
#define HASH_LENGTH (1<<HASH_BITS)

/* Memory block descriptor: */
struct tMemBlock
{
	unsigned char *data;		/**< Pointer to data */
	size_t num, size;			/**< Number of items, size of item in bytes */
	const char *file;			/**< Source file */
	int line;					/**< Source line */
	struct tMemBlock *next;		/**< Pointer to a next block or NULL */
};

/* Heap - a hash table */
static struct tMemBlock *heap[HASH_LENGTH];

/* Mutex */
static CmpackMutex mutex;

/* Acquire the mutex */
#define cmpack_mem_lock() { cmpack_mutex_lock(&mutex); }

/* Release the mutex */
#define cmpack_mem_unlock() { cmpack_mutex_unlock(&mutex); }

static size_t allocsize;

/* Init debug heap */
static void heap_init(void)
{
	cmpack_mutex_init(&mutex);
	memset(heap, 0, HASH_LENGTH*sizeof(struct tMemBlock*));
	allocsize = 0;
}

/* Free allocated memory */
static void heap_free()
{
	int i;
	struct tMemBlock *list, *next;

	cmpack_mem_lock();
	for (i=0; i<HASH_LENGTH; i++) {
		list = heap[i];
		while (list) {
			next = list->next;
			free(list->data);
			free(list);
			list = next;
		}
		heap[i] = NULL;
	}
	cmpack_mem_unlock()
	cmpack_mutex_destroy(&mutex);
}

/* Add block to a heap */
static void heap_insert(struct tMemBlock *mb)
{
	intptr_t hash = (((intptr_t)mb->data+4) >> HASH_SKIP) & ((1<<HASH_BITS)-1);

	cmpack_mem_lock();
	mb->next = heap[hash];
	heap[hash] = mb;
	cmpack_mem_unlock()
}

/* Find block in a heap */
static struct tMemBlock *heap_search(void *data)
{
	intptr_t hash = (((intptr_t)data) >> HASH_SKIP) & ((1<<HASH_BITS)-1);
	struct tMemBlock *list;

	cmpack_mem_lock();
	for (list = heap[hash]; list!=NULL; list=list->next) {
		if (list->data+4==data) {
			cmpack_mem_unlock()
			return list;
		}
	}
	cmpack_mem_unlock()
	return NULL;
}

/* Delete block from a heap */
static struct tMemBlock *heap_delete(void *data)
{
	intptr_t hash = (((intptr_t)data) >> HASH_SKIP) & ((1<<HASH_BITS)-1);
	struct tMemBlock *list, *prev = NULL;

	cmpack_mem_lock();
	for (list = heap[hash]; list!=NULL; list=list->next) {
		if (list->data+4==data) {
			if (prev) 
				prev->next = list->next;
			else
				heap[hash] = list->next;
			cmpack_mem_unlock()
			return list;
		}
		prev = list;
	}
	cmpack_mem_unlock()
	return NULL;
}

/* Allocated new block */
static struct tMemBlock *create_block(size_t num, size_t size, const char *file, int line, int initval)
{
	size_t bytes = num * size;
	struct tMemBlock *block;
	unsigned char *data;

	data = malloc(bytes+8);
	assert(data != NULL);

	block = malloc(sizeof(struct tMemBlock));
	assert(block != NULL);

	block->num = num;
	block->size = size;
	block->data = data;
	block->file = file;
	block->line = line;

	memset(data, 0xFD, 4);
	memset(data + 4, initval, bytes);
	memset(data + 4 + bytes, 0xFD, 4);

	cmpack_mem_lock();
	allocsize += num*size;
	cmpack_mem_unlock()

	return block;
}

/* Free memory block */
static void destroy_block(struct tMemBlock *block)
{
	size_t bytes = block->num * block->size;

	cmpack_mem_lock();
	allocsize -= block->num*block->size;
	cmpack_mem_unlock()

	memset(block->data, 0xFE, 4);
	memset(block->data + 4, 0xDD, bytes);
	memset(block->data + 4 + bytes, 0xFE, 4);
	free(block->data);
	free(block);
}

/* Check paddings */
static void check_block(struct tMemBlock *mb)
{
	int i;
	size_t bytes;
	const unsigned char *ptr;

	assert(mb->data != NULL);

	bytes = mb->num * mb->size;
	ptr = mb->data;
	for (i=0; i<3; i++) {
		assert((*ptr) == 0xFD);
		ptr++;
	}
	ptr = mb->data + (4 + bytes);
	for (i=0; i<3; i++) {
		assert((*ptr) == 0xFD);
		ptr++;
	}
}


/* Resize memory block */
static void resize_block(struct tMemBlock *block, size_t new_bytes, const char *file, int line)
{
	size_t old_bytes = block->num * block->size;

	block->data = realloc(block->data, new_bytes+8);
	assert(block->data != NULL);

	block->num = 1;
	block->size = new_bytes;
	block->file = file;
	block->line = line;

	memset(block->data, 0xFD, 4);
	if (new_bytes>old_bytes)
		memset(block->data + (4 + old_bytes), 0xCD, new_bytes - old_bytes);
	memset(block->data + (4 + new_bytes), 0xFD, 4);

	cmpack_mem_lock();
	allocsize -= old_bytes;
	allocsize += new_bytes;
	cmpack_mem_unlock()

}

void *cmpack_malloc_dbg(size_t size, const char *file, int line)
{
	struct tMemBlock *block;
	
	block = create_block(1, size, file, line, 0xCD);
	heap_insert(block);
	return (void*)(block->data+4);
}

void *cmpack_malloc(size_t size)
{
	return cmpack_malloc_dbg(size, NULL, 0);
}

void *cmpack_calloc_dbg(size_t num, size_t size, const char *file, int line)
{
	struct tMemBlock *block;
	
	block = create_block(num, size, file, line, 0x00);
	heap_insert(block);
	return (void*)(block->data+4);
}

void *cmpack_calloc(size_t num, size_t size)
{
	return cmpack_calloc_dbg(num, size, NULL, 0);
}

void *cmpack_realloc_dbg(void *ptr, size_t size, const char *file, int line)
{
	struct tMemBlock *block;

	if (!ptr)
		return cmpack_malloc_dbg(size, file, line);

	block = heap_delete(ptr);
	assert(block != NULL);
	check_block(block);
	resize_block(block, size, file, line);
	heap_insert(block);
	return (void*)(block->data + 4);
}

void *cmpack_realloc(void *ptr, size_t size)
{
	return cmpack_realloc_dbg(ptr, size, NULL, 0);
}

void cmpack_free(void *ptr)
{
	struct tMemBlock *block;

	if (ptr) {
		block = heap_delete(ptr);
		assert(block != NULL);
		check_block(block);
		destroy_block(block);
	}
}

void cmpack_mem_init(void)
{
	heap_init();
}

void cmpack_mem_clean(void)
{
	int leaks;
	size_t i, j, bytes;
	struct tMemBlock *block;

	/* Check memory leaks */
	cmpack_mem_lock();
	leaks = 0;
	for (i=0; i<HASH_LENGTH; i++) {
		for (block=heap[i]; block!=NULL; block=block->next)
			leaks++;
	}
	if (leaks>0) {
		/* Print memory leaks */
		fprintf(stderr, "\nWarning: %d Memory leak(s) detected.\n", leaks);
		for (i=0; i<HASH_LENGTH; i++) {
			for (block=heap[i]; block!=NULL; block=block->next) {
				fprintf(stderr, "Memory block: num=%lu, size=%lu\n", (unsigned long)block->num, (unsigned long)block->size);
				if (block->file)
					fprintf(stderr, "\tAllocated at %s line %d\n", block->file, block->line);
				fprintf(stderr, "\t");
				bytes = block->size * block->num;
				if (bytes>32)
					bytes = 32;
				for (j=0; j<bytes; j++) 
					fprintf(stderr, "%.02X ", block->data[j+4]);
				for (j=0; j<bytes; j++) {
					if (block->data[j+4]<0 || block->data[j+4]>=' ')
						fprintf(stderr, "%c", block->data[j+4]);
					else
						fprintf(stderr, ".");
				}
				fprintf(stderr, "\n");
			}
		}
	}
	cmpack_mem_unlock()

	heap_free(heap);
}

/* Make copy of a string */
char *cmpack_strdup_dbg(const char *src, const char *file, int line)
{
	if (src) {
		char *buf = cmpack_malloc_dbg(strlen(src)+1, file, line);
		strcpy(buf, src);
		return buf;
	}
	return NULL;
}

/* Make copy of a string */
char *cmpack_strdup(const char *src)
{
	return cmpack_strdup_dbg(src, __FILE__, __LINE__);
}

size_t cmpack_allocsize(void)
{
	return allocsize;
}

#else

/************************   CRT HEAP   **********************************/

void *cmpack_malloc(size_t size)
{
	return malloc(size);
}

void *cmpack_malloc_dbg(size_t size, const char *file, int line)
{
	return malloc(size);
}

void *cmpack_calloc(size_t num, size_t size)
{
	return calloc(num, size);
}

void *cmpack_calloc_dbg(size_t num, size_t size, const char *file, int line)
{
	return calloc(num, size);
}

void *cmpack_realloc(void *ptr, size_t size)
{
	return realloc(ptr, size);
}

void *cmpack_realloc_dbg(void *ptr, size_t size, const char *file, int line)
{
	return realloc(ptr, size);
}

void cmpack_free(void *ptr)
{
	free(ptr);
}


/* Make copy of a string */
char *cmpack_strdup(const char *src)
{
	if (src) {
		char *buf = cmpack_malloc(strlen(src)+1);
		strcpy(buf, src);
		return buf;
	}
	return NULL;
}

/* Make copy of a string */
char *cmpack_strdup_dbg(const char *src, const char *file, int line)
{
	return cmpack_strdup(src);
}

void cmpack_mem_init(void)
{
}

void cmpack_mem_clean(void)
{
}

size_t cmpack_allocsize(void)
{
	return 0;
}

#endif

