/* Scoped hash table routines. There is no hashDelete() function;
   instead, you use hashPush() to set a scope frame, and hashPop()
   to remove all entries placed into that frame. */

#include "../h/cdefs.h"
#include <strings.h>

typedef struct hashTabEntry {
	struct hashTabEntry *next;
	struct hashTabEntry *link;
	char *name;
	int level;
	int idx;
	void *obj;
} HashTabEntry;

static HashTabEntry **table;
static HashTabEntry *entryList;
static int tableSize = 0;
static int numEntries = 0;
static int level = 0;
static int (*freeFxn)();

static int hash();

static char *copystr(s)
char *s;
{
	return (strcpy((char*)malloc(strlen(s)+1), s));
}

hashTabCreate (size, fxn)
int size;
int (*fxn)();
{
	extern free();
	int k;

	table = (HashTabEntry**)malloc(size*sizeof(HashTabEntry*));
	for (k=0; k<size; k++)
	 { table[k] = 0;
	 }
	tableSize = size;
	numEntries = 0;
	if (fxn)
	 { freeFxn = fxn;
	 }
	else
	 { freeFxn = free;
	 }
	level = 0;
	entryList = 0;
}

hashTabFree()
{
	HashTabEntry *ent, *ent_next;
	int k;

	for (k=0; k<tableSize; k++)
	 { for (ent=table[k]; ent; ent=ent_next)
	    { ent_next = ent->next;
	      if (ent->obj)
	       { (*freeFxn)(ent->obj);
	       }
	      if (ent->name)
	       { free (ent->name);
	       }
	      free ((char*)ent);
	    }
	 }
	free ((char*)table);
}

static hash(name)
register char *name;
{
	register unsigned long int idx;

	idx = 0;
	while (*name)
	 { idx += *name ++;
	   idx <<= 2;
	   idx += (idx >> 24) & 0xff;
	 }
	return(idx%tableSize);
}

void *hashInsert(name,obj,clobber,oldo)
char *name;
void *obj;
int clobber;
void **oldo;
{
	HashTabEntry *e;
	int idx;

	if (tableSize == 0)
	 { return (NULL);
	 }
#if 0
	if (numEntries >= table_size)
	 { if (grow())
	    { return (NULL);
	    }
	 }
#endif
	idx = hash(name);
	for (e=table[idx]; e && e->level == level; e=e->next)
	 { if (strcmp(e->name,name) == 0)
	    { break;
	    }
	 }
	if (!e || e->level != level)
	 { if ((e = ALLOC(HashTabEntry)) == NULL)
	    { return (NULL);
	    }
	   if ((e->name = copystr(name)) == NULL)
	    { return (NULL);
	    }
	   numEntries ++;
	   e->level = level;
	   e->idx = idx;
	   e->link = entryList;
	   entryList = e;
	   e->next = table[idx];
	   table[idx] = e;
	   if (oldo)
	    { *oldo = NULL;
	    }
	   return (e->obj = obj);
	 }
	else if (clobber)
	 { if (oldo)
	    { *oldo = e->obj;
	    }
	   return (e->obj = obj);
	 }
	else
	 { if (oldo)
	    { *oldo = NULL;
	    }
	   return (NULL);
	 }
}

hashPushLevel()
{
	/* CAUTION: hashPushLevel() and hashPopLevel() *CANNOT* be used 
	   in conjunction with hashDelete(). */

	level++;
}

hashPopLevel()
{
	/* CAUTION: hashPushLevel() and hashPopLevel() *CANNOT* be used 
	   in conjunction with hashDelete(). */

	HashTabEntry *e, *e_link;

	level--;
	for (e=entryList; e && e->level>level; e=e_link)
	 { e_link = e->link;
	   /* the LIFO property of the entryList guarantees that e
	      is at the beginning of whatever list it is on. */

	   table[e->idx] = e->next;
	   if (e->obj)
	    { freeFxn((char*)e->obj);
	    }
	   if (e->name)
	    { free(e->name);
	    }
	   free((char*)e);
	 }
	entryList = e;
}

void *hashFind(name)
char *name;
{
	/* The LIFO property of scoping ensures that the nearest
	   item in scope is the first on the list. */	   

	register HashTabEntry *e;

	if (tableSize == 0)
	 { return(0);
	 }
	for (e=table[hash(name)]; e; e=e->next)
	 { if (strcmp(e->name,name) == 0)
	    { return (e->obj);
	    }
	 }
	return(0);
}
