#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include "error.h"

#define memory_IMPORT
#include "memory.h"

#define MAGIC 987654321

/**
 * Double linked list of allocated blocks. This is the header prepended to each
 * user's allocated block.
 */
typedef struct Block {
	
	/** Previous block. */
	struct Block *prev;
	
	/** Next block. */
	struct Block *next;
	
	/** Size of the users' data this block header precedes. */
	int size;
	
	/** Name of the source C file where this block was allocated. */
	char *file;
	
	/** Line number in the source C file where this block was allocated. */
	int line;
	
	/** User's defined destructor function. */
	void (*destructor)(void *data);
	
	/** Magic number for memory corruption detection. */
	int magic;
} Block;

/**
 * First block of the doubly linked list of allocated blocks. Well, actually
 * this block is the last allocated...
 */
static Block *firstBlock = NULL;

/**
 * List of registered cleanup functions.
 */
typedef struct _CleanupEntry {
	/** Next registered cleanup function. */
	struct _CleanupEntry *next;
	/** Registered cleanup function. */
	void (*cleanup)();
} CleanupEntry;

/**
 * First cleanup function of the list (that is, the last registered function).
 */
static CleanupEntry *firstCleanup = NULL;

#define ERASE_LEADING_LEN 1000

void * memory_allocate_PRIVATE(char *file, int line, int size, void (*destructor)(void *data))
{
	Block *b;

	assert(size >= 0);
	b = malloc(sizeof(Block) + size);
	if( b == NULL )
		error_internal_PRIVATE(file, line, "out of memory from malloc(%d)", size);
	b->next = firstBlock;
	b->prev = NULL;
	b->size = size;
	b->file = file;
	b->line = line;
	b->destructor = destructor;
	b->magic = MAGIC;
	if( firstBlock != NULL )
		firstBlock->prev = b;
	firstBlock = b;
	if( size > 0 )
		memset(b + 1, 0, size <= ERASE_LEADING_LEN? size : ERASE_LEADING_LEN);
	return b + 1;
}


static Block * getBlock(void *p)
{
	Block *b;
	
	b = (Block *) p - 1;
	if( b->magic != MAGIC )
		error_internal("memory corruption detected: not a block allocated by this module", 0);
	return b;
}


static void destructorRecursionDetector(void *data)
{
	Block *b;
	
	b = getBlock(data);
	error_internal("recursion in destructor detected while releasing block allocated in %s:%d", b->file, b->line);
}


void memory_dispose(void * p)
{
	Block *b;
	void (*destructor)(void *data);
	
	if( p == NULL )
		return;
	b = getBlock(p);
	if( b->destructor != NULL ){
		destructor = b->destructor;
		b->destructor = destructorRecursionDetector;
		destructor(p);
	}
	if( b->size > 0 )
		memset(p, 0, b->size <= ERASE_LEADING_LEN? b->size : ERASE_LEADING_LEN);
	if( b == firstBlock ){
		firstBlock = b->next;
		if( firstBlock != NULL )
			firstBlock->prev = NULL;
	} else {
		b->prev->next = b->next;
		if( b->next != NULL )
			b->next->prev = b->prev;
	}
	memset(b, 0, sizeof(Block));
	free(b);
}


void * memory_realloc_PRIVATE(char *file, int line, void * p, int size)
{
	Block *bold, *bnew;
	
	if( p == NULL )
		return memory_allocate_PRIVATE(file, line, size, NULL);
	assert( size >= 0 );
	bold = getBlock(p);
	if( bold->destructor != NULL )
		error_internal_PRIVATE(file, line, "resizing block having destructor allocated in %s is not supported", bold->file);
	bold->magic = 0;
	bnew = realloc(bold, sizeof(Block) + size);
	if( bnew == NULL )
		error_internal_PRIVATE(file, line, "out of memory from realloc(...,%d)",
			sizeof(Block) + size);
	if( bnew != bold ){
		if( firstBlock == bold )
			firstBlock = bnew;
		if( bnew->prev != NULL )
			bnew->prev->next = bnew;
		if( bnew->next != NULL )
			bnew->next->prev = bnew;
	}
	bnew->magic = MAGIC;
	bnew->size = size;
	return bnew + 1;
}


void memory_strcpy(char *dst, int dst_capacity, char *src)
{
	assert(dst_capacity >= 1);
	char *dst_end = dst + dst_capacity - 1;
	while( dst < dst_end && *src != 0 ){
		*dst = *src;
		dst++;
		src++;
	}
	*dst = 0;
}


char * memory_strdup(char * s)
{
	int len;
	char * p;

	len = strlen(s)+1;
	p = memory_allocate(len, NULL);
	memcpy(p, s, len);
	return p;
}


int memory_startsWith(char * s, char * head)
{
	while( *head != 0 && *head == *s ){
		head++;
		s++;
	}
	return *head == 0;
}


int memory_startsWithIgnoreCase(char * s, char * tag)
{
	while( (*tag!=0) && (toupper(*tag)==toupper(*s)) ){ tag++; s++; };
	return  *tag==0;
}


int memory_endsWith(char * s, char * tail)
{
	int slen, taillen;
	
	slen = strlen(s);
	taillen = strlen(tail);
	if( slen < taillen )
		return 0;
	s = s + slen - taillen;
	while( *s != 0 ){
		if( *s != *tail )
			return 0;
		s++;
		tail++;
	}
	return 1;
}


char * memory_indexOfMem(char *s, int slen, char *target, int targetlen)
{
	char *end;
	
	if( slen < 0 || targetlen < 0 )
		error_internal("memory_indexOfMem: slen=%d, taillen=%d", slen, targetlen);
	if( targetlen <= 0 )
		return s;
	if( slen <= 0 || slen < targetlen )
		return NULL;
	/* Now 1 <= targetlen <= slen. */
	if( targetlen == 1 )
		return memchr(s, *target, slen);
	/* Search stops when s goes beyond this limit: */
	end = s + slen - targetlen;
	while(s <= end){
		if( *s == *target ){
			if( memcmp(s, target, targetlen) == 0 )
				return s;
		}
		s++;
	}
	return NULL;
}


int memory_indexOfStringIgnoreCase(char * str, char * word)
{
	int i, a;

	i=0;
	a = toupper(*word);
	while(*str!=0){
		if((toupper(*str)==a) && (memory_startsWithIgnoreCase(str, word)))
			return i;
		str++;
		i++;
	}
	return -1;
}


char * memory_getEnvKey(char * name, char * default_value)
{
	char * k;

	k = getenv(name);
	if( k != NULL )
		return  k;
	else if( default_value != NULL )
		return  default_value;
	else
		error_external("missing mandatory environmental variable `%s'", name);
	return  NULL; /* avoid gcc -Wall flow control complains */
}


char *memory_format(char *fmt, ...)
{
	/* FIXME: allowed 4 recursion levels: should be enough, but... */
	va_list ap;
	static int idx = 0;
	static char buf[4][1000];

	idx = (idx + 1) % 4;
	va_start(ap, fmt);
	vsnprintf(buf[idx], 1000, fmt, ap);
	va_end(ap);
	return buf[idx];
}


void memory_registerCleanup(void (*cleanup)())
{
	CleanupEntry *c;
	
	c = firstCleanup;
	while(c != NULL){
		if( c->cleanup == cleanup )
			return; /* already registered */
		c = c->next;
	}
	c = memory_allocate(sizeof(CleanupEntry), NULL);
	c->cleanup = cleanup;
	c->next = firstCleanup;
	firstCleanup = c;
}


static void memory_cleanupAll()
{
	CleanupEntry *c;
	
	while(firstCleanup != NULL){
		c = firstCleanup;
		firstCleanup = c->next;
		c->cleanup();
		memory_dispose(c);
	}
}



#define MAX_REPORT_LEAKS 100

int memory_report()
{
	Block *b;
	int i, j, len, c, n, tot_size;
	char *bytes;
	
	memory_cleanupAll();
	if( firstBlock == NULL )
		return 0;
	fprintf(stderr, "%s, memory_report() leaks detection, first %d listed:\n\n",
			error_prog_name, MAX_REPORT_LEAKS);
	b = firstBlock;
	i = 0;
	fprintf(stderr, "no.  magic  size    content               file:line\n");
	fprintf(stderr, "---  -----  ------  --------------------  --------------\n");
	while( b != NULL && i < MAX_REPORT_LEAKS ){
		i++;
		fprintf(stderr, "%3d  %5s  %6d  ",
			i, (b->magic == MAGIC)? "ok":"BAD", b->size);
		len = b->size;
		if( len > 20 )
			len = 20;
		bytes = (char *) (b + 1);
		for(j = 0; j < len; j++){
			c = bytes[j] & 255;
			if( !(32 <= c && c <= 126) )
				c = '.';
			fprintf(stderr, "%c", c);
		}
		for(; j <= 20; j++)
			fprintf(stderr, " ");
		fprintf(stderr, " %s:%d\n", b->file, b->line);
		b = b->next;
	}
	
	n = 0;
	tot_size = 0;
	b = firstBlock;
	while(b != NULL){
		n++;
		tot_size += b->size;
		b = b->next;
	}
	fprintf(stderr, "%d total blocks, %d bytes wasted.\n", n, tot_size);
	
	return 1;
}