/**
 * \file hash.c
 * Generic hash table. 
 *
 * This code taken from Mesa and adapted.
 */

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "eglhash.h"


#define TABLE_SIZE 1023  /**< Size of lookup table/array */

#define HASH_FUNC(K)  ((K) % TABLE_SIZE)


/*
 * Unfinished mutex stuff
 */

typedef int _EGLMutex;

static void
_eglInitMutex(_EGLMutex m)
{
}

static void
_eglDestroyMutex(_EGLMutex m)
{
}

static void
_eglLockMutex(_EGLMutex m)
{
}

static void
_eglUnlockMutex(_EGLMutex m)
{
}



typedef struct _egl_hashentry _EGLHashentry;

struct _egl_hashentry
{
   EGLuint Key;            /**< the entry's key */
   void *Data;             /**< the entry's data */
   _EGLHashentry *Next;    /**< pointer to next entry */
};


struct _egl_hashtable
{
   _EGLHashentry *Table[TABLE_SIZE];  /**< the lookup table */
   EGLuint MaxKey;                    /**< highest key inserted so far */
   _EGLMutex Mutex;                   /**< mutual exclusion lock */
};


/**
 * Create a new hash table.
 * 
 * \return pointer to a new, empty hash table.
 */
_EGLHashtable *
_eglNewHashTable(void)
{
   _EGLHashtable *table = (_EGLHashtable *) calloc(1, sizeof(_EGLHashtable));
   if (table) {
      _eglInitMutex(table->Mutex);
      table->MaxKey = 1;
   }
   return table;
}



/**
 * Delete a hash table.
 * Frees each entry on the hash table and then the hash table structure itself.
 * Note that the caller should have already traversed the table and deleted
 * the objects in the table (i.e. We don't free the entries' data pointer).
 *
 * \param table the hash table to delete.
 */
void
_eglDeleteHashTable(_EGLHashtable *table)
{
   EGLuint i;
   assert(table);
   for (i = 0; i < TABLE_SIZE; i++) {
      _EGLHashentry *entry = table->Table[i];
      while (entry) {
	 _EGLHashentry *next = entry->Next;
	 free(entry);
	 entry = next;
      }
   }
   _eglDestroyMutex(table->Mutex);
   free(table);
}



/**
 * Lookup an entry in the hash table.
 * 
 * \param table the hash table.
 * \param key the key.
 * 
 * \return pointer to user's data or NULL if key not in table
 */
void *
_eglHashLookup(const _EGLHashtable *table, EGLuint key)
{
   EGLuint pos;
   const _EGLHashentry *entry;

   assert(table);

   if (!key)
      return NULL;

   pos = HASH_FUNC(key);
   entry = table->Table[pos];
   while (entry) {
      if (entry->Key == key) {
	 return entry->Data;
      }
      entry = entry->Next;
   }
   return NULL;
}



/**
 * Insert a key/pointer pair into the hash table.  
 * If an entry with this key already exists we'll replace the existing entry.
 * 
 * \param table the hash table.
 * \param key the key (not zero).
 * \param data pointer to user data.
 */
void
_eglHashInsert(_EGLHashtable *table, EGLuint key, void *data)
{
   /* search for existing entry with this key */
   EGLuint pos;
   _EGLHashentry *entry;

   assert(table);
   assert(key);

   _eglLockMutex(table->Mutex);

   if (key > table->MaxKey)
      table->MaxKey = key;

   pos = HASH_FUNC(key);
   entry = table->Table[pos];
   while (entry) {
      if (entry->Key == key) {
         /* replace entry's data */
	 entry->Data = data;
         _eglUnlockMutex(table->Mutex);
	 return;
      }
      entry = entry->Next;
   }

   /* alloc and insert new table entry */
   entry = (_EGLHashentry *) malloc(sizeof(_EGLHashentry));
   entry->Key = key;
   entry->Data = data;
   entry->Next = table->Table[pos];
   table->Table[pos] = entry;

   _eglUnlockMutex(table->Mutex);
}



/**
 * Remove an entry from the hash table.
 * 
 * \param table the hash table.
 * \param key key of entry to remove.
 *
 * While holding the hash table's lock, searches the entry with the matching
 * key and unlinks it.
 */
void
_eglHashRemove(_EGLHashtable *table, EGLuint key)
{
   EGLuint pos;
   _EGLHashentry *entry, *prev;

   assert(table);
   assert(key);

   _eglLockMutex(table->Mutex);

   pos = HASH_FUNC(key);
   prev = NULL;
   entry = table->Table[pos];
   while (entry) {
      if (entry->Key == key) {
         /* found it! */
         if (prev) {
            prev->Next = entry->Next;
         }
         else {
            table->Table[pos] = entry->Next;
         }
         free(entry);
         _eglUnlockMutex(table->Mutex);
	 return;
      }
      prev = entry;
      entry = entry->Next;
   }

   _eglUnlockMutex(table->Mutex);
}



/**
 * Get the key of the "first" entry in the hash table.
 * 
 * This is used in the course of deleting all display lists when
 * a context is destroyed.
 * 
 * \param table the hash table
 * 
 * \return key for the "first" entry in the hash table.
 *
 * While holding the lock, walks through all table positions until finding
 * the first entry of the first non-empty one.
 */
EGLuint
_eglHashFirstEntry(_EGLHashtable *table)
{
   EGLuint pos;
   assert(table);
   _eglLockMutex(table->Mutex);
   for (pos = 0; pos < TABLE_SIZE; pos++) {
      if (table->Table[pos]) {
         _eglUnlockMutex(table->Mutex);
         return table->Table[pos]->Key;
      }
   }
   _eglUnlockMutex(table->Mutex);
   return 0;
}


/**
 * Given a hash table key, return the next key.  This is used to walk
 * over all entries in the table.  Note that the keys returned during
 * walking won't be in any particular order.
 * \return next hash key or 0 if end of table.
 */
EGLuint
_eglHashNextEntry(const _EGLHashtable *table, EGLuint key)
{
   const _EGLHashentry *entry;
   EGLuint pos;

   assert(table);
   assert(key);

   /* Find the entry with given key */
   pos = HASH_FUNC(key);
   entry = table->Table[pos];
   while (entry) {
      if (entry->Key == key) {
         break;
      }
      entry = entry->Next;
   }

   if (!entry) {
      /* the key was not found, we can't find next entry */
      return 0;
   }

   if (entry->Next) {
      /* return next in linked list */
      return entry->Next->Key;
   }
   else {
      /* look for next non-empty table slot */
      pos++;
      while (pos < TABLE_SIZE) {
         if (table->Table[pos]) {
            return table->Table[pos]->Key;
         }
         pos++;
      }
      return 0;
   }
}


/**
 * Dump contents of hash table for debugging.
 * 
 * \param table the hash table.
 */
void
_eglHashPrint(const _EGLHashtable *table)
{
   EGLuint i;
   assert(table);
   for (i = 0; i < TABLE_SIZE; i++) {
      const _EGLHashentry *entry = table->Table[i];
      while (entry) {
	 printf("%u %p\n", entry->Key, entry->Data);
	 entry = entry->Next;
      }
   }
}



/**
 * Return a new, unused hash key.
 */
EGLuint
_eglHashGenKey(_EGLHashtable *table)
{
   EGLuint k;

   _eglLockMutex(table->Mutex);
   k = table->MaxKey;
   table->MaxKey++;
   _eglUnlockMutex(table->Mutex);
   return k;
}