/*
 *  Copyright 2009 OpenSourceStewardshipFoundation.org
 *  Licensed under GNU General Public License version 2
 *
 *
 * Author: seanhalle@yahoo.com
 */

#include "PrivateHash.h"


 HashTable *
makeHashTable( int numHashSlots, FreeEntryContentFnPtr freeFn )
 { HashTable * retTable;
   retTable = VMS_int__malloc( sizeof( HashTable ) );

   retTable->freeEntryContentFn = freeFn;
   
   retTable->entries    = VMS_int__malloc( numHashSlots * sizeof(HashEntry *) );
   retTable->numEntries = 0;
   retTable->tableSz    = numHashSlots;

   nullOutTablesArray( retTable );

   return retTable;
 }

 void
doubleTableSize( HashTable *table )
 { int i, oldTableSz, newTableSz;
   HashEntry *entry, *nextEntry, **oldEntries, **newEntries;

   oldTableSz = table->tableSz;
   oldEntries = table->entries;

   newTableSz = 2 * oldTableSz + 1;
   newEntries = VMS_int__malloc( newTableSz * sizeof(HashEntry *) );

   table->tableSz    = newTableSz;
   table->entries    = newEntries;
   table->numEntries = 0; //about to add them all back!

      // move all the entries from old to new
   for( i=0; i < oldTableSz; i++ )
    { if( oldEntries[i] != NULL )
       { entry = oldEntries[i];
         while( entry != NULL )
          { nextEntry = entry->next;
            entry->next = NULL;
            putEntryIntoTable( entry, table ); //does not allocate anything
            entry = nextEntry;
          }
       }
    }
 }

void
nullOutTablesArray( HashTable *table )
 { int i, tableSz;
   tableSz = table->tableSz;
   HashEntry ** entries = table->entries;
   for( i = 0; i < tableSz; i++ )
      entries[ i ] = NULL;
 }

unsigned int
hashThisKey( char* s, int hashSz )
 { unsigned int h = 0;
   unsigned int i;
   hashkey_t* key = (hashkey_t*)s;

   for(i=0 ; i<sizeof(hashkey_t); i++ )
      h = key->hashable[i] + h*31;
   return h % hashSz;
 }

/*Need this to be separated out, for use in both getParam and putParam
 */
HashEntry *
getEntryFromTable( char *key, HashTable * table )
 {  unsigned int
   hashIndex = hashThisKey( key, table->tableSz );
    HashEntry*
   hashEntry = table->entries[ hashIndex ];
   for( ; hashEntry != NULL; hashEntry = hashEntry->next )
    {
       if( memcmp( hashEntry->key, key, sizeof(hashkey_t) ) == 0 )
           return hashEntry;
    }
   return NULL;
 }

void *
getValueFromTable( char *key, HashTable * table )
 { HashEntry *entry;
   entry = getEntryFromTable( key, table );
   if( entry == NULL ) return NULL;

   return entry->content;
 }


/*If key already has a value, clobber the old one and replace it
 */
 int
addValueIntoTable( char* key, void *content, HashTable *table )
 { unsigned int hashIdx;
   HashEntry* hashEntry;

   hashEntry = getEntryFromTable( key, table );
   if( hashEntry == NULL )
    { hashIdx = hashThisKey( key, table->tableSz );
      hashEntry = (HashEntry*) VMS_int__malloc( sizeof( HashEntry ) );
            if( hashEntry == NULL )  return 0;
      hashEntry->key = VMS_int__malloc( sizeof(hashkey_t) );
            if( hashEntry->key == NULL ) return 0;
      memcpy( hashEntry->key, key, sizeof(hashkey_t) );
      hashEntry->next = (table->entries)[hashIdx];
      (table->entries)[hashIdx] = hashEntry;
      table->numEntries += 1;
      if( table->tableSz < table->numEntries ) doubleTableSize( table );
    }
   else
    { (*(table->freeEntryContentFn))( hashEntry->content );
    }
   hashEntry->content = content;
   return 1;
 }

 int
putEntryIntoTable( HashEntry *entry, HashTable *table )
 { unsigned int hashIdx;
   HashEntry* testEntry;

   testEntry = getEntryFromTable( entry->key, table );
   if( testEntry == NULL )
    { hashIdx = hashThisKey( entry->key, table->tableSz );
      entry->next = (table->entries)[hashIdx];
      (table->entries)[hashIdx] = entry;
      table->numEntries += 1;
      if( table->tableSz < table->numEntries ) doubleTableSize( table );
    }
   else
    { (*(table->freeEntryContentFn))( testEntry->content );
         //being lazy -- will create bug in code that relies on having ptr to
         // elem given to insert into table!
      testEntry->content = entry->content;
      entry->content = NULL;
      freeHashEntryButNotContent( entry );
    }
   return 1;
 }

/*Better version
 */
 void
untested_putEntryIntoTable( HashEntry *entry, HashTable * table )
 { HashEntry *testEntry, *prevEntry = NULL;
    unsigned int
   hashIndex = hashThisKey( entry->key, table->tableSz );
   
   testEntry = table->entries[ hashIndex ];
   for( ; testEntry != NULL; testEntry = testEntry->next )
    { if( memcmp( testEntry->key, entry->key, sizeof(hashkey_t)) == 0 )
       { if( prevEntry == NULL )
          { table->entries[hashIndex] = entry;
            entry->next = testEntry->next;
          }
         else
          { prevEntry->next = entry;
            entry->next = testEntry->next;
          }
         freeHashEntryUsing( testEntry, table ); //frees content too!
         return;
       }
    }
      //wasn't found, so insert
   entry->next = table->entries[hashIndex];
   table->entries[hashIndex] = entry;
 }



 bool8
deleteEntryFromTable( char *key, HashTable *table )
 { HashEntry  *hashEntry;
   HashEntry **addrOfHashEntryPtr;
   unsigned int hashIndex;

   hashIndex = hashThisKey( key, table->tableSz );
   addrOfHashEntryPtr = &( table->entries[ hashIndex ] );
   hashEntry = *addrOfHashEntryPtr;
   while( hashEntry != NULL )
    { if( memcmp( hashEntry->key, key, sizeof(hashkey_t) ) == 0 )
       {
         *addrOfHashEntryPtr = hashEntry->next;
         //TODO: Free the contents of entry?
         freeHashEntryButNotContent( hashEntry );
         table->numEntries -= 1;
         return TRUE;
       }
      addrOfHashEntryPtr = &( hashEntry->next );
      hashEntry = *addrOfHashEntryPtr;
    }
   return FALSE;
 }
 
void
freeHashTable( HashTable *table )
 { int i;
   HashEntry *hashEntry, *temp, **entries;

   entries = table->entries;
   for( i=0; i < table->tableSz; i++ )
    { if( entries[i] != NULL )
       { hashEntry = entries[i];
         while( hashEntry != NULL )
          {
            temp = hashEntry->next;
            freeHashEntryUsing( hashEntry, table );
            hashEntry = temp;
          }
       }
    }
 }

void
freeHashEntryUsing( HashEntry *entry, HashTable *table )
 {
   if( entry->content != NULL )
      (*(table->freeEntryContentFn))( entry->content );
   VMS_int__free( entry->key ); //was VMS_int__malloc'd above, so free it
   VMS_int__free( entry );
 }

void
freeHashEntryButNotContent( HashEntry *entry )
 {
   VMS_int__free( entry->key ); //was VMS_int__malloc'd above, so free it
   VMS_int__free( entry );
 }

