#include "mhash.h"
#include "mdebug.h"
#include <stdio.h>

static hash_it* hash_it_new(char* key, void* item, hash_it* coll_it
                                    , hash_it* next_it)
{
   hash_it* obj = (hash_it*)MALLOC(sizeof(hash_it));

   obj->mKey = STRDUP(key);

   obj->mItem = item;
   obj->mCollisionIt = coll_it;

   obj->mNextIt = next_it;   

   return obj;
}
   
static inline void hash_it_release(hash_it* self)
{
   FREE(self->mKey);
      
   FREE(self);
}
         
hash_obj* hash_new(int size)
{
   hash_obj* self = (hash_obj*)MALLOC(sizeof(hash_obj));
   
   self->mTableSize = size;
   self->mTable = (hash_it**)MALLOC(sizeof(hash_it*) * size);
   memset(self->mTable, 0, sizeof(hash_it*)*size);

   self->mEntryIt = NULL;

   return self;
}

void hash_delete(hash_obj* self)
{
   hash_it* it = self->mEntryIt;

   while(it) {
      hash_it* next_it = it->mNextIt;
      hash_it_release(it);
      it = next_it;
   }
   
   FREE(self->mTable);
   
   FREE(self);
}

static unsigned int get_hash_value(hash_obj* self, char* key)
{
   unsigned int i = 0;
   while(*key) {
      i += *key;
      key++;
   }
   return i % self->mTableSize;
}
   
void hash_put(hash_obj* self, char* key, void* item)
{
   unsigned int hash_key = get_hash_value(self, key);
   hash_it* it
       = hash_it_new(key, item, self->mTable[hash_key], self->mEntryIt);
   
   self->mTable[hash_key] = it;
   self->mEntryIt = it;
}

static void erase_from_list(hash_obj* self, hash_it* rit)
{
   if(rit == self->mEntryIt) {
      self->mEntryIt = rit->mNextIt;
   }
   else {
      hash_it* it = self->mEntryIt;

      while(it->mNextIt) {
         if(it->mNextIt == rit) {
            it->mNextIt = it->mNextIt->mNextIt;
            break;
         }
            
         it = it->mNextIt;
      }
   }
}
         
void hash_erase(hash_obj* self, char* key)
{
   const unsigned int hash_value = get_hash_value(self, key);
   hash_it* it = self->mTable[hash_value];
   
   if(it == NULL)
      ;
   else if(strcmp(it->mKey, key) == 0) {
      hash_it* it2 = self->mEntryIt;   
      self->mTable[ hash_value ] = it->mCollisionIt;

      erase_from_list(self, it);
      
      hash_it_release(it);
   }
   else {
      hash_it* prev_it = it;
      it = it->mCollisionIt;
      while(it) {
         if(strcmp(it->mKey, key) == 0) {
            prev_it->mCollisionIt = it->mCollisionIt;
            erase_from_list(self, it);
            hash_it_release(it);
            
            break;
         }
         
         prev_it = it;
         it = it->mCollisionIt;
      }
   }
}

void hash_clear(hash_obj* self)
{
   int i;
   int max;
   hash_it* it = self->mEntryIt;

   while(it) {
      hash_it* next_it = it->mNextIt;
      hash_it_release(it);
      it = next_it;
   }

   max = self->mTableSize;
   for(i=0; i<max; i++)self->mTable[i] = NULL;
   
   self->mEntryIt = NULL;
}

void hash_replace(hash_obj* self, char* key, void* item)
{
   hash_it* it = self->mTable[get_hash_value(self, key)];

   if(it) {
      do {
         if(strcmp(it->mKey, key) == 0) {
            it->mItem = item;
            break;
         }
   
         it = it->mCollisionIt;
      } while(it);
   }
}

#ifdef DEBUG
void hash_show(hash_obj* self)
{
   char tmp[4096];
   int i;
   int max;
   hash_it* it;
   
   max = self->mTableSize;
   for(i=0; i<max; i++){
      strcpy(tmp, "");

      strcat(tmp, Format("table[%d]: " , i));
      
      it = self->mTable[i];
      while(it) {
         strcat(tmp, Format("item[\"%s\"], ", it->mKey));
         it = it->mCollisionIt;
      }
      M((tmp));
   }
}
#endif

void* hash_item(hash_obj* self, char* key)
{
   hash_it* it = self->mTable[ get_hash_value(self, key) ];
   while(it) {
      if(strcmp(key, it->mKey) == 0) return it->mItem;
      it = it->mCollisionIt;
   }

   return NULL;
}

void* hash_item_addr(hash_obj* self, char* key)
{
   hash_it* it = self->mTable[ get_hash_value(self, key) ];
   while(it) {
      if(strcmp(key, it->mKey) == 0) return &it->mItem;
      it = it->mCollisionIt;
   }

   return NULL;
}
   
char* hash_key(hash_obj* self, void* item)
{
   hash_it* it = self->mEntryIt;

   while(it) {
      if(it->mItem == item) return it->mKey;
      it = it->mNextIt;
   }

   return NULL;
}

int hash_count(hash_obj* self)
{
   int result = 0;
   hash_it* it = self->mEntryIt;

   while(it) {
      result++;
      it = it->mNextIt;
   }
               
   return result;
}   
