#include <stdlib.h>

#include "hashtable.h"

/* Log Management */
#define FATAL_HASHTABLE
#define ERROR_HASHTABLE
//#define INFO_HASHTABLE
//#define WARN_HASHTABLE
//#define DEBUG_HASHTABLE
//#define USE_HASH_PROTECTION

#define HASH(table, key) ((table)->hash(key) % (table)->num_buckets)
#define SECUREHASH(func, table, key) (func((table)->hash(key)) % (table)->num_buckets)

size_t protecthash(size_t i)
{
    /* Aim to protect against poor hash functions by adding logic here
     * - logic taken from java 1.4 hashtable source */    
    i += ~(i << 9);
    i ^=  ((i >> 14) | (i << 18)); /* >>> */
    i +=  (i << 4);
    i ^=  ((i >> 10) | (i << 22)); /* >>> */
    return i;
}

int init_hashtable(struct hashtable *table, 
                   size_t buckets, 
                   size_t (*hash)(void *elem),
                   void *(*cpkeyfun)(void *source),
                   void *(*cpvaluefun)(void *source),   
                   int (*keycmp)(void *key1, void *key2))
{
   size_t i;

   table->buckets = malloc(sizeof(*(table->buckets)) * buckets);
   if(!(table->buckets))
   {
      return 0;
   }
   table->num_buckets = buckets;
   table->num_elements = 0;
   table->hash = hash;
   table->cpkeyfun = cpkeyfun;
   table->cpvaluefun = cpvaluefun;
   table->keycmp = keycmp;

   /*initialize the elements in the table->buckets array*/
   for(i = 0;i < buckets;i++)
   {
      table->buckets[i] = NULL;
   } 

   /* Default values */
   table->key_free_function = free;
   table->value_free_function = free;

   table->iterlist = Dllist_create(free);

   createSemaphore(1, &table->mutex);
   createSemaphore(1, &table->mutexiter);
   table->nreaditer = 0;

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) init_hashtable <%d>\n", buckets);
#endif   
   return 1;
}

void set_key_free_function_hashtable(struct hashtable *table,
                                     void (*free_func)(void *ptr))
{
   decSemaphore(&table->mutex);
   table->key_free_function = free_func;
   incSemaphore(&table->mutex);
#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) set_key_free_function_hashtable\n");
#endif
}

void set_value_free_function_hashtable(struct hashtable *table,
                                       void (*free_func)(void *ptr))
{
   decSemaphore(&table->mutex);
   table->value_free_function = free_func;
   incSemaphore(&table->mutex);
#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) set_value_free_function_hashtable\n");
#endif
}

int insert_hashtable(struct hashtable *table, void* key, void* value)
{
   size_t ind;
   void *nkey;
   void *nvalue;
   struct element *elem;

   decSemaphore(&table->mutex);

#ifdef USE_HASH_PROTECTION
   ind = SECUREHASH(protecthash, table, key);
#else   
   ind = HASH(table, key);
#endif
   
   nkey = table->cpkeyfun(key);
   nvalue = table->cpvaluefun(value);
   elem = malloc(sizeof *elem);

   if(!elem || !nvalue || !nkey)
   {
      free(elem);
      table->value_free_function(nvalue);
      table->key_free_function(nkey);
      
      incSemaphore(&table->mutex);
      return 0;
   }
   /*all memory handling done, now put the key-value pair into the table */
   table->num_elements++;
   elem->key = nkey;
   elem->value = nvalue;

   /*add element to the front of the list*/
   elem->next = table->buckets[ind];
   table->buckets[ind] = elem;

   /*add element to iterator list*/
   elem->iteratoritem = Dllist_insert(table->iterlist, elem);

   incSemaphore(&table->mutex);

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) insert_hashtable Hash <%d>\n", ind);
#endif
   return 1;
}

void *get_hashtable(struct hashtable *table, void *key)
{
   size_t ind;
   struct element *tmp;

   decSemaphore(&table->mutex);

#ifdef USE_HASH_PROTECTION
   ind = SECUREHASH(protecthash, table, key);
#else
   ind = HASH(table, key);
#endif   

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) get_hashtable Hash <%d>\n", ind);   
#endif
   
   if((tmp = table->buckets[ind]) == NULL)
   {
      /*There are no elements in this bucket*/
      
      incSemaphore(&table->mutex);
      return NULL;
   }

   /*search the rest of the bucket*/
   while((tmp) != NULL)
   {
      if(!(table->keycmp(key, tmp->key)))
      {
         incSemaphore(&table->mutex);
         return tmp->value;
      }
      tmp = tmp->next;
   }
   
   incSemaphore(&table->mutex);

   return NULL;
}


void *remove_hashtable(struct hashtable *table, void *key)
{
   size_t ind;
   struct element *elem;
   struct element *prev;

   decSemaphore(&table->mutex);

#ifdef USE_HASH_PROTECTION
   ind = SECUREHASH(protecthash, table, key);
#else
   ind = HASH(table, key);
#endif
   
   elem = table->buckets[ind];
   
   if(elem == NULL)
   {
      /*no element with the given key*/
      incSemaphore(&table->mutex);
      return NULL;
   }

   /*check if it is the first element*/
   if(!(table->keycmp(key, elem->key)))
   {
      /*The reason for this check of the first element is that
        the buckets field needs to be changed. And that cannot 
        be done through the prev pointer
      */
      void *tmp = elem->value;
      table->buckets[ind] = elem->next;

      /*delete element to iterator list*/
      Dllist_remove(table->iterlist, elem->iteratoritem);
      
      table->key_free_function(elem->key);
      free(elem);
      table->num_elements--;
      
      incSemaphore(&table->mutex);
      return tmp;
   }

   prev = elem;
   elem = elem->next;
   while(elem != NULL)
   {
      if(!(table->keycmp(key, elem->key)))
      {
         /*found the right element*/
         void *tmp = elem->value;
         prev->next = elem->next;

         /*delete element to iterator list*/
         Dllist_remove(table->iterlist, elem->iteratoritem);
      
         table->key_free_function(elem->key);
         free(elem);
         table->num_elements--;
         
         incSemaphore(&table->mutex);
         return tmp;
      }
      prev = elem;
      elem = elem->next;
   }

   /*No element with the given key*/
   
   incSemaphore(&table->mutex);

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) remove_hashtable Hash <%d>\n", ind);
#endif
   return NULL;
}

int delete_hashtable(struct hashtable *table, void *key)
{
   void *e = remove_hashtable(table, key);
   
   if(e == NULL)
   {
      return 0;
   }
   table->value_free_function(e);

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) delete_hashtable\n");
#endif
   return 1;
}

int rehash_hashtable(struct hashtable *table, size_t new_size)
{
   struct iterator_hashtable keys;
   struct iterator_hashtable values;
   struct hashtable tmp; /*A temporary hashtable*/
   size_t i;   
   
   if(!get_iterator_hashtable(table, &keys, KEY)
      || !get_iterator_hashtable(table, &values, VALUE))
   {      
      return 0;
   }

   tmp = *table;
   tmp.buckets = malloc(new_size * sizeof *(tmp.buckets));
   tmp.num_buckets = new_size;
   if(tmp.buckets == NULL)
   {      
      return 0;
   }

   /*All buckets must be initialized to NULL*/
   for(i = 0;i < new_size;i++)
   {
      tmp.buckets[i] = NULL;
   } 

   /*insert everything into the new buckets*/
   while(more_iterator(&keys))
   {
      if(!insert_hashtable(&tmp, 
                           next_iterator(&keys), 
                           next_iterator(&values)))
      {
         free(tmp.buckets);         
         return 0;
      }
   }
   table->num_buckets = new_size;
   free(table->buckets);
   table->buckets = tmp.buckets;
   free_iterator(table, &keys);
   free_iterator(table, &values);

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) rehash_hashtable\n");
#endif   
   return 1;
}

unsigned num_elements_hashtable(struct hashtable *table)
{
   unsigned num;
   
   decSemaphore(&table->mutex);
   num = table->num_elements;
   incSemaphore(&table->mutex);

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) num_elements_hashtable <%d>\n", num);
#endif
   return num;
}

double fill_factor_hashtable(struct hashtable *table)
{
   double ffactor;

   decSemaphore(&table->mutex);
   ffactor = (((double)table->num_elements)) / table->num_buckets;
   incSemaphore(&table->mutex);

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) fill_factor_hashtable <%d>\n", ffactor);
#endif
   return ffactor;
}

unsigned collisions_hashtable(struct hashtable *table)
{
   unsigned count = 0;
   size_t i;
   struct element *e;

   decSemaphore(&table->mutex);
   
   for(i = 0;i < table->num_buckets;i++)
   {
      /* Check if the bucket is empty */
      if(table->buckets[i] != NULL)
      {
         /* Point to first element in bucket */
         e = table->buckets[i];
         /* This will skip the first element in the bucket which is 
            what we want */
         while((e = e->next) != NULL)
         {
            count++;
         }
      }
   }

   incSemaphore(&table->mutex);

#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) collisions_hashtable <%d>\n", count);
#endif
   return count;
}

void free_bucket(struct hashtable *table, struct element *elem)
{
   struct element *aux;

   while (elem != NULL)
   {
      aux = elem;
      elem = elem->next;
      
      /*delete element to iterator list*/
      Dllist_remove(table->iterlist, aux->iteratoritem);

      table->key_free_function(aux->key);
      table->value_free_function(aux->value);
      free(aux);      
   }
   
#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) free_bucket\n");
#endif
}


void free_hashtable(struct hashtable *table)
{
   size_t i;

   decSemaphore(&table->mutex);
   
   /*first all elements must be freed*/
   for(i = 0;i < table->num_buckets;i++)
   {
      if(table->buckets[i] != NULL)
      {
         free_bucket(table, table->buckets[i]);
      }
   }
   /*then free the buckets*/
   free(table->buckets);

   incSemaphore(&table->mutex);
   
#ifdef DEBUG_HASHTABLE
   printf("(HASHTABLE) free_hashtable\n");
#endif
}


/*  code to handle iterator below here */
int more_iterator(struct iterator_hashtable *iter)
{
   if (iter == NULL){
     return 0;
   }
   return (iter->dllist_iter != NULL);
}


void *next_iterator(struct iterator_hashtable *iter)
{
   void *retval;
   struct element *e;
   
   if (iter == NULL){
     return NULL;     
   }

   e = (struct element *) iter->dllist_iter->item;   
   /*Check if there actually is a next element and if there is, return it*/
   if(e != NULL)
   {
      retval = (iter->what == KEY) ? (e->key) : (e->value);
      iter->dllist_iter = iter->dllist_iter->next;
      return retval;
   }
   else
   {
      return NULL;
   }
   
}

void free_iterator(struct hashtable *table, struct iterator_hashtable *iter)
{
   /*
   free(*dta);
   *data = NULL;
   */
   decSemaphore(&table->mutexiter);
   if (table->nreaditer > 0){
     table->nreaditer = table->nreaditer - 1;
   }
   if (table->nreaditer == 0){
     incSemaphore(&table->mutex);
   }   
   incSemaphore(&table->mutexiter);   
}

int get_iterator_hashtable(struct hashtable *table, struct iterator_hashtable *iterator, int key_or_value)
{   
   /*iterator = malloc(sizeof *iterator);
   if(iterator == NULL) {
      return 0;
   }
   */
   decSemaphore(&table->mutexiter);
   if (table->nreaditer == 0){
     decSemaphore(&table->mutex);
   }
   table->nreaditer = table->nreaditer + 1;
   incSemaphore(&table->mutexiter);
   
   iterator->what = key_or_value;
   iterator->dllist_iter = table->iterlist->first;   
   
   return 1;
}
