#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>


#include "mem-private.h"


static unsigned long chunks = 0; /* how many calls to malloc() et al? */


/*
	The alignment type: double on most architectures
 */

#define ALIGN_SIZE	sizeof( double )


/*
	The magic tag value: all our mlist blocks must contain
	this as the tag.
 */

#define TAG_VALUE	0xa55a


/* The memory list type */

typedef struct mlist *mlist;
struct mlist
{
	unsigned	tag;
	unsigned long	size;
	mlist		next;
	mlist		prev;
	char	*	file;
	int		line;
};


#define HDR_SIZE	sizeof( struct mlist )
#define RESERVE_SIZE	( ( (HDR_SIZE+ALIGN_SIZE-1) / ALIGN_SIZE )\
				* ALIGN_SIZE )
#define C2H(a)		( (mlist) ( ((char *) (a)) - RESERVE_SIZE ) )
#define H2C(a)		( ((char *) (a)) + RESERVE_SIZE )


/* PUBLIC VARIABLES */

int	Mem_crash = 1;		/* Should we crash if malloc etc fail  - Yes */


/* PRIVATE VARIABLES */

static size_t		Mem_size = 0;	 /* How much memory is used */
static mlist		Mem_list = NULL; /* list of all allocated memory */



static void null( char * , char * , int );
static void add( mlist );
static void del( mlist );


void * Mem_malloc( size_t size, char *file, int line )
{
	mlist	new;

	chunks++;

	new	= (mlist) malloc( (size_t) size + RESERVE_SIZE );

	if( new == NULL )
	{
		null( "Mem_malloc", file, line );
		return NULL;
	}

	new->tag	= TAG_VALUE;
	new->size	= size;
	new->file	= file;
	new->line	= line;

	add( new );

	Mem_size += new->size;

	return H2C(new);
}


void * Mem_xmalloc( size_t size, char *file, int line )
{

	int temp;
	void *result;

	/* disable crashing */
	temp = Mem_crash;
	Mem_crash = 0;

	result = Mem_malloc( size, file, line );

	assert( result != NULL );

	/* reset crashing to previous */
	Mem_crash = temp;

	return result;
}


void * Mem_xdatadup( size_t size, void *data, char *file, int line )
{
	int temp;
	void *result;

	/* disable crashing */
	temp = Mem_crash;
	Mem_crash = 0;

	result = Mem_malloc( size, file, line );

	assert( result != NULL );

	memcpy( result, data, size );

	/* June 2011: Reenable previous crash setting: thanks to
	   benjamin.grabham10@imperial.ac.uk
	 */
	Mem_crash = temp;

	return result;
}


void * Mem_calloc( size_t n, size_t size, char *file, int line )
{
	mlist	new;

	chunks++;

	new	= (mlist) malloc( (size_t) n * size + RESERVE_SIZE );

	if( new == NULL )
	{
		null( "Mem_calloc", file, line );
		return NULL;
	}
	memset( (void *) new, 0, (int) n * size + RESERVE_SIZE );

	new->tag	= TAG_VALUE;
	new->size	= n * size;
	new->file	= file;
	new->line	= line;

	add( new );

	Mem_size += new->size;

	return H2C(new);
}


void * Mem_realloc( void *p, size_t size, char*file, int line )
{
	mlist	new;

	chunks++;

	new	= C2H( p );

	if( new->tag != TAG_VALUE )
	{
		fprintf( stderr, "Mem_realloc: invalid pointer %s(%d)\n",
			file, line );
		Mem_show( stderr );
		exit( Mem_crash ? Mem_crash : 1 );
	}

	/* Invalidate */
	new->tag 	= ! new->tag;
	Mem_size	-= new->size;

	del( new );

	new = (mlist) realloc( (void *) new, (size_t) RESERVE_SIZE + size );
	if( new == NULL )
	{
		null( "Mem_realloc", file, line );
		return NULL;
	}

	/* Validate new one */
	new->tag 	= TAG_VALUE;
	new->size	= size;
	new->file	= file;
	new->line	= line;

	Mem_size	+= new->size;

	add( new );

	return H2C( new );
}



char * Mem_strdup( char *p, char *file, int line )
{
	char	*s;

	chunks++;

	s	= (char *) Mem_malloc( (size_t) strlen(p)+1, file, line );

	if( s != NULL )
	{
		strcpy( s, p );
	}

	return	s;

}


char * Mem_xstrdup( char *p, char *file, int line )
{
	char	*s;

	chunks++;

	s	= (char *) Mem_malloc( (size_t) strlen(p)+1, file, line );

	assert( s != NULL );
	strcpy( s, p );

	return	s;

}


void Mem_free( void *p, char *file, int line )
{
	mlist	new;

	new	= C2H( p );

	if( new->tag != TAG_VALUE )
	{
		fprintf( stderr, "Mem_free: invalid pointer %s(%d)\n",
			file, line );
		Mem_show( stderr );
		exit( Mem_crash ? Mem_crash : 1 );
	}

	/* Invalidate */
	new->tag 	= ! new->tag;
	Mem_size	-= new->size;

	del( new );

	free( new );
}


size_t Mem_used( void )
{
	return Mem_size;
}


void Mem_show( FILE *f )
{
	mlist m;

	fprintf( f, "\nMemory In Use: total size %lu\n\n",
		(unsigned long)Mem_size );

	fprintf( f, "File\t\tLine\tSize\n" );

	for( m = Mem_list; m != NULL; m = m->next )
	{
		fprintf( f, "%s\t%d\t%6lu\n", m->file, m->line, m->size );
	}
}


static void null( char *procname, char *file, int line )
{
	if( Mem_crash )
	{
		fprintf( stderr,
			"%s: out of memory %s(%d)\n",
			procname, file, line );
		Mem_show( stderr );
		exit( Mem_crash );
	}
}


static void add( mlist p )
{
	p->next = Mem_list;
	p->prev = NULL;

	if( Mem_list != NULL )
	{
		Mem_list->prev = p;
	}
	Mem_list = p;
}


static void del( mlist p )
{
	if( p->next != NULL )
	{
		p->next->prev = p->prev;
	}
	if( p->prev != NULL )
	{
		p->prev->next = p->next;
	} else
	{
		Mem_list = p->next;
	}

}


void Mem_check( void )
{
	if( Mem_used() != 0 )
	{
		fprintf( stderr, "\nMemory list not empty!!\n" );
		Mem_show( stderr );
		fprintf( stderr,
			"\n%lu calls to malloc/realloc/calloc/strdup!!\n",
			chunks );
		exit( 1 );
	}
}

void Mem_exit( int n )
{
	Mem_check();
	exit( n );
}
