/* MegaZeux
 *
 * Copyright (c) 2008 Patrick Walton <pcwalton@uchicago.edu>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdio.h>
#include <stdlib.h>

#include "hashtable.h"

HashTable *create_managed_hash_table(HashFunc hash_func, EqualFunc equal_func,
     DestroyFunc key_destroy, DestroyFunc val_destroy)
{
  HashTable *tab;

  tab = malloc(sizeof(HashTable));
  tab->hash_func = hash_func;
  tab->equal_func = equal_func;
  tab->nbuckets = 8;
  tab->buckets = calloc(tab->nbuckets, sizeof(HashEntry *));
  tab->key_destroy = key_destroy;
  tab->val_destroy = val_destroy;

  return tab;
}

HashTable *create_hash_table(HashFunc hash_func, EqualFunc equal_func)
{
  return create_managed_hash_table(hash_func, equal_func, free, free);
}

unsigned int int_hash(const void *ptr)
{
  return *((unsigned int *)ptr);
}

int int_equal(const void *ap, const void *bp)
{
  return *((int *)ap) == *((int *)bp);
}

/*
 *  This function expects to be able to free key and val for each entry
 *  in the hash table. Therefore, you do NOT have to free them yourself.
 */
void destroy_hash_table(HashTable *tab)
{
  HashEntry *ent;
  int i;

  for (i = 0; i < tab->nbuckets; i++)
  {
    ent = tab->buckets[i];
    while (ent)
    {
      HashEntry *next = ent->next;
      (tab->key_destroy)(ent->key);
      (tab->val_destroy)(ent->val);
      free(ent);
      ent = next;
    }
  }

  free(tab->buckets);
  free(tab);
}

/*
 *  This function takes ownership of key and val. Thus you must keep them
 *  around. In particular, this means key and val cannot be stack-allocated.
 */
void insert_into_hash_table(HashTable *tbl, void *key, void *val)
{
  HashEntry *ent, *new_ent;
  int bucket;
  unsigned int hash;
  
  hash = tbl->hash_func(key);
  bucket = hash % tbl->nbuckets;

  ent = tbl->buckets[bucket];
  while (ent)
  {
    if (tbl->equal_func(ent->key, key))
    {
      (tbl->val_destroy)(ent->val);
      ent->val = val;
      return;
    }
    ent = ent->next;
  }

  new_ent = malloc(sizeof(HashEntry));
  new_ent->key = key;
  new_ent->val = val;
  new_ent->next = tbl->buckets[bucket];
  tbl->buckets[bucket] = new_ent;
}

HashEntry *lookup_in_hash_table(HashTable *tbl, void *key)
{
  HashEntry *ent;
  int bucket;
  unsigned int hash;
  
  hash = tbl->hash_func(key);
  bucket = hash % tbl->nbuckets;

  ent = tbl->buckets[bucket];
  while (ent)
  {
    if (tbl->equal_func(ent->key, key))
      return ent;
    ent = ent->next;
  }

  return NULL;
}

HashTableIter *create_hash_table_iter(HashTable *table)
{
  HashTableIter *iter;
  iter = malloc(sizeof(HashTableIter));
  iter->table = table;
  iter->bucket = -1;
  iter->entry = NULL;
  return iter;
}

HashEntry *hash_table_iter_next(HashTableIter *iter)
{
  /*
   *  Go to the next entry in the chain, if we haven't yet hit the end of the
   *  chain.
   */
  if (iter->bucket != -1 && iter->entry && iter->entry->next)
  {
    iter->entry = iter->entry->next;
    return iter->entry;
  }

  do
  {
    if (++iter->bucket >= iter->table->nbuckets)
    {
      iter->entry = NULL;
      break;
    }
    iter->entry = iter->table->buckets[iter->bucket];
  } while (!iter->entry);

  return iter->entry;
}

void destroy_hash_table_iter(HashTableIter *iter)
{
  free(iter);
}

