/* Table_t is an abstract table of long/void * pairs.  The long value is
 * the key.  Table_t is implemented as a standard hash table of HASH_SIZE
 * entries with linked list resolution.  The hash_table is dynamically
 * allocated within table.c, and its address is returned as a void pointer
 * to the caller.
 */

#include <stdio.h>
#include "table.h"

#ifndef YES
#define YES 1
#endif

#ifndef NO
#define NO 0
#endif

#define HASH_SIZE 1023
#define BLOCK_SIZE 100

extern void free(char* ptr);
extern char* malloc(unsigned size);

/* Linked list node for hash table lists. */

typedef struct hash_node_t {
   long key;
   void *data;
   struct hash_node_t *next;
} hash_node_t;

/* Node of block-allocated data to streamline table allocation and deallocation
 * It was taking too damn long the simple way.  Field "data" is the block
 * of allocated space from which to draw new hash nodes.  "next_index"
 * tells how many entries in "data" have been used or, equivalently, the 
 * index of the next unused entry.  "next_block" points to the next
 * block_node_t in the linked list of the things maintained by each table
 * object.
 */

typedef struct block_node_t {
   int next_index;
   hash_node_t data[BLOCK_SIZE];
   struct block_node_t *next_block;
} block_node_t;
   
/* Main data structure for a table.  "h_table" points to the hash table, 
 * a block of head pointers to lists of hash_node_t's.  "blocks" points to
 * to the list of allocation blocks used for the table so far.
 */

typedef struct {
   hash_node_t **h_table;  
   block_node_t *blocks;
   long num_elements;
} table_data_t;

/* Node for iterator information.  Table points to the table being iterated
 * upon.  Ndx indicates the current hash_table entry, and "point" points to
 * the current node within the hash table entry.
 */
typedef struct {
   table_data_t *table;
   int ndx;
   hash_node_t *point;
} iterator_data_t;

/* Create_table allocates a block of hash_node_t pointers, all 
 * initialized to NULL.  It returns a pointer to the block.
 */
table_t create_table()
{
   table_data_t *table;
   register int ndx;

   table = (table_data_t *) malloc(sizeof(table_data_t));
   table->h_table = (hash_node_t **) malloc(sizeof(hash_node_t *) * HASH_SIZE);
   table->blocks = NULL;
   table->num_elements = 0;
   for (ndx = 0; ndx < HASH_SIZE; ndx++)
      table->h_table[ndx] = NULL;

   return table;
}

/* Delete an item out of the table.
 */
void destroy_item(table_t table, long key)
{
   table_data_t *t = (table_data_t *) table;
   hash_node_t **finder = t->h_table + key % HASH_SIZE;
   
   for ( ; *finder != NULL && (*finder)->key != key; finder = &(*finder)->next)
      ;

   if (*finder == NULL)
      return;

   (*finder)->key = -1;
   *finder = (*finder)->next;
   t->num_elements--;
}

/* Add_item adds a new key and associated data item to the table assuming
 * the key is not already in the table.  It returns YES on success, NO
 * if the key is already present.  It uses a simple modulo by the hash
 * table size as a "hash function".
 */
int add_item(table_t table, long key, void *item)
{
   hash_node_t **entry;
   register hash_node_t *search;
   table_data_t *t = (table_data_t *) table;
   register block_node_t *new_blk;

   entry = t->h_table + key % HASH_SIZE;
   for (search = *entry; search != NULL; search = search->next)
      if (search->key == key)
         break;

   if (search != NULL) 
      return NO;
   else {
      if (t->blocks == NULL || t->blocks->next_index == BLOCK_SIZE) {
         new_blk = (block_node_t *) malloc(sizeof(block_node_t));
         new_blk->next_index = 0;
         new_blk->next_block = t->blocks;
         t->blocks = new_blk;
      }
      search = t->blocks->data + t->blocks->next_index++;
      search->next = *entry;
      *entry = search;
      search->key = key;
      search->data = item;
      t->num_elements++;
      return YES;
   }
}

/* Num_items returns the number of elements in the table.
*/
long num_items(table_t table)
{
   return (*(table_data_t *)table).num_elements;
}

/* Get_item returns the pointer to the data associated with the given key.
 * If the key is not recognized, then NULL is returned.  Like add_item,
 * it uses a simple modulo for the "hash function".
 */
void *get_item(table_t table, long key)
{
   register hash_node_t *search;

   search = ((table_data_t *)table)->h_table[key%HASH_SIZE];
   while (search != NULL && search->key != key)
      search = search->next;

   return search == NULL ? NULL : search->data;
}

/* Destroy table deallocates all memory used to store the table.  The memory
 * allocated to store the actual data is not de-allocated, but the 
 * pointers maintained to it in the table are, along with the table itself.
 */
void destroy_table(table_t table)
{
   register block_node_t *destroy;
   register block_node_t *temp;
   table_data_t *t = (table_data_t *)table;

   for (destroy = t->blocks; destroy != NULL;) {
      temp = destroy->next_block;
      free((char*)destroy);
      destroy = temp;
   }
   free((char*)t->h_table);
   free((char*)t);
}

/* Create_iterator creates an iterator on "table" and returns it.  
 * The iterator is initially at the beginning of the table.  The iterator
 * returns the key/data pairs in the table in some guaranteed order (not
 * necessarily sorted).  If modifications are made to the table during
 * the life of the iterator, its behavior may or may not reflect the
 * modification.  An arbitrary number of iterators may exist on the same
 * table at the same time.
 *
 * The iterator for the hash table is simply a structure showing present
 * hash entry list and position within that list.  The order of iteration
 * is by hash table entries, and within an entry by nodes in the list.
 */
iterator_t create_iterator(table_t table)
{
   iterator_data_t *itr;
   register int ndx;

   itr = (iterator_data_t *) malloc(sizeof(iterator_data_t));
   itr->table = (table_data_t *)table;
   for (ndx = 0; ndx < HASH_SIZE && itr->table->h_table[ndx] == NULL;)
      ndx++;
   itr->ndx = ndx;
   itr->point = ndx < HASH_SIZE ? itr->table->h_table[ndx] : NULL; 

   return itr;
}

/* Next_item returns the key/data pair at the present position of "itr",
 * and increments "itr" to the next position.  If the present position of
 * "itr" is past the end of the table, next_item returns NO, otherwise
 * YES.  If itr is past the end of the table, ndx will be left equal to
 * HASH_SIZE and point will be NULL.
 */
int next_item(iterator_t itr, long *key, void **data)
{
   iterator_data_t *my_itr = (iterator_data_t *) itr;
   register int ndx;

   if ((ndx = my_itr->ndx) >= HASH_SIZE)
      return NO;
   else {
      *key = my_itr->point->key;
      *data = my_itr->point->data;
      if ((my_itr->point = my_itr->point->next) == NULL) {
         while (ndx++ < HASH_SIZE && my_itr->table->h_table[ndx] == NULL)
            ;
         my_itr->point = ndx < HASH_SIZE ? my_itr->table->h_table[ndx] : NULL;  
         my_itr->ndx = ndx;
      }
      return YES;
   }
}

/* Destroy_iterator destroys "itr" and frees all storage associated 
 * with the iterator.
 */
void destroy_iterator(iterator_t itr)
{
   free((char*)itr);
}