/*
 * Copyright 1990 by Baylor College of Medicine ALL RIGHTS RESERVED. 
 *
 * This program is subject to a license agreement between 
 * Baylor College of Medicine and MIT. Any use inconsistent with
 * said license and any use by persons other than the faculty, 
 * students and staff at MIT or any use on a computer not operated 
 * as part of the Athena Computing Environment (ACE) is expressly 
 * prohibited.
 */
#include <stdio.h>
#include "etrealloc.h"
#include <malloc.h>

/*
 *		This is an EXTREMELY useful function.
 *		It allows the reallocing of pointers with guaranteed
 *		O(n * log(n)) time in increasing the size of the list.
 *			(n represents the size of the list)
 *
 *		A pointer that is alloced using etrealloc MUST be freed
 *		with etfree.
 */

#define HASH_SIZE 5000
typedef struct Hash_Entry *Hash_Entry ;
struct Hash_Entry {
	char *p ;
	int size ;
	Hash_Entry next ;
} ;
static Hash_Entry entries[HASH_SIZE] ;
#define HASH(p) (entries + ((unsigned long)p) % HASH_SIZE)

/*
 *		Add a pointer to the list.
 */
static
add_hash(p,size)
	char *p ;
	unsigned size ;
{
	Hash_Entry *h = HASH(p) ;
	Hash_Entry new = (Hash_Entry)malloc(sizeof *new) ;
	Hash_Entry old = *h ;
	if (new == NULL)
	{
		die("malloc: returned NULL\n") ;
	}
	new->p = p ;
	new->size = size ;
	new->next = old ;
	*h = new ;
}

/*
 *		Finds a pointer in the list.
 */
static
Hash_Entry *
find_hash(p)
	char *p ;
{
	Hash_Entry *h = HASH(p) ;
	Hash_Entry curr ;
	while((curr = *h) != NULL)
	{
		if (p == curr->p)
			return h ;
		h = &curr->next ;
	}
	return h ;
}

/*
 *
 */
char *
etmalloc(size)
	unsigned size ;
{
	char *p ;
	if (size == 0)
		return NULL ;
	if ((p = malloc(size)) == NULL)
	{
		die("malloc: returned NULL\n") ;
	}
	add_hash(p,size) ;
	if (Debug_Malloc)
	{
		fprintf(stderr,"etmalloc(%u) = 0x%x\n",size,p) ;
		fflush(stderr) ;
	}
	return p ;
}

static
remove_entry(h)
	Hash_Entry *h ;
{
	Hash_Entry t = *h ;
	*h = t->next ;
	free((char *)t) ;
}

/*
 *		Main routine.
 */
char *
etrealloc(p,size)
	char *p ;
	unsigned size ;
{
	char *new_p ;

	/* If pointer null then allocate new space */
	if (p == NULL)
	{
		new_p = etmalloc(size) ;
	}

	/* If allocating no space, free up original */
	else if (size == 0)
	{
		etfree(p) ;
		new_p = NULL ;
	}

	/* Else find old pointer and increase or decrease space accordingly */
	else
	{
		Hash_Entry *h = find_hash(p) ;
		Hash_Entry t = *h ;
		if (t == NULL)
		{
			die("etrealloc: Attempt to realloc bad pointer\n") ;
		}

		/* If size greater than what is available then double size needed */
		if (size > t->size)
		{
			unsigned new_size = size * 2 ;
			if ((new_p = realloc(p,new_size)) == NULL)
			{
				die("realloc returned NULL\n") ;
			}
			if (new_p != p)
			{
				remove_entry(h) ;
				add_hash(new_p,new_size) ;
			}
			else
				t->size = new_size ;
		}

		/* If size needed less than half available then reduce */
		else if (size < t->size / 2)
		{
			if ((new_p = realloc(p,size)) == NULL)
			{
				die("realloc returned NULL\n") ;
			}
			if (new_p != p)
			{
				remove_entry(h) ;
				add_hash(new_p,size) ;
			}
			else
				t->size = size ;
		}

		/* Else nothing changed */
		else
			new_p = p ;

		if (Debug_Malloc)
		{
			fprintf(stderr,"etrealloc(0x%x,%u) = 0x%x\n",p,size,new_p) ;
			fflush(stderr) ;
		}
	}
	return new_p ;
}

/*
 *		Frees up space used by etrealloc.
 */
etfree(p)
	char *p ;
{
	if (Debug_Malloc)
	{
		fprintf(stderr,"etfree(0x%x)\n",p) ;
		fflush(stderr) ;
	}
	if (p != NULL)
	{
		Hash_Entry *h = find_hash(p) ;
		if (*h == NULL)
		{
			die("etfree: Attempt to free non-existent pointer\n") ;
		}
		remove_entry(h) ;
		free(p) ;
	}
}
