/*
 * Copyright 2003-2004 Brandon Long
 * All Rights Reserved.
 *
 * ClearSilver Templating System
 *
 * This code is made available under the terms of the ClearSilver License.
 * http://www.clearsilver.net/license.hdf
 *
 */

#include "cs_config.h"

#include <stdlib.h>
#include <string.h>

#include "neo_misc.h"
#include "neo_err.h"
#include "neo_hash.h"

static NEOERR *_hash_resize(NE_HASH *hash);
static NE_HASHNODE **_hash_lookup_node (NE_HASH *hash, void *key, UINT32 *hashv);

NEOERR *ne_hash_init (NE_HASH **hash, NE_HASH_FUNC hash_func, NE_COMP_FUNC comp_func)
{
  NE_HASH *my_hash = NULL;

  my_hash = (NE_HASH *) calloc(1, sizeof(NE_HASH));
  if (my_hash == NULL)
    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for NE_HASH");

  my_hash->size = 256;
  my_hash->num = 0;
  my_hash->hash_func = hash_func;
  my_hash->comp_func = comp_func;

  my_hash->nodes = (NE_HASHNODE **) calloc (my_hash->size, sizeof(NE_HASHNODE *));
  if (my_hash->nodes == NULL)
  {
    free(my_hash);
    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for NE_HASHNODES");
  }

  *hash = my_hash;

  return STATUS_OK;
}

void ne_hash_destroy (NE_HASH **hash)
{
  NE_HASH *my_hash;
  NE_HASHNODE *node, *next;
  int x;

  if (hash == NULL || *hash == NULL)
    return;

  my_hash = *hash;

  for (x = 0; x < my_hash->size; x++)
  {
    node = my_hash->nodes[x];
    while (node)
    {
      next = node->next;
      free(node);
      node = next;
    }
  }
  free(my_hash->nodes);
  my_hash->nodes = NULL;
  free(my_hash);
  *hash = NULL;
}

NEOERR *ne_hash_insert(NE_HASH *hash, void *key, void *value)
{
  UINT32 hashv;
  NE_HASHNODE **node;

  node = _hash_lookup_node(hash, key, &hashv);

  if (*node)
  {
    (*node)->value = value;
  }
  else
  {
    *node = (NE_HASHNODE *) malloc(sizeof(NE_HASHNODE));
    if (node == NULL)
      return nerr_raise(NERR_NOMEM, "Unable to allocate NE_HASHNODE");

    (*node)->hashv = hashv;
    (*node)->key = key;
    (*node)->value = value;
    (*node)->next = NULL;
  }
  hash->num++;

  return _hash_resize(hash);
}

void *ne_hash_lookup(NE_HASH *hash, void *key)
{
  NE_HASHNODE *node;

  node = *_hash_lookup_node(hash, key, NULL);

  return (node) ? node->value : NULL;
}

void *ne_hash_remove(NE_HASH *hash, void *key)
{
  NE_HASHNODE **node, *remove;
  void *value = NULL;

  node = _hash_lookup_node(hash, key, NULL);
  if (*node)
  {
    remove = *node;
    *node = remove->next;
    value = remove->value;
    free(remove);
    hash->num--;
  }
  return value;
}

int ne_hash_has_key(NE_HASH *hash, void *key)
{
  NE_HASHNODE *node;

  node = *_hash_lookup_node(hash, key, NULL);

  if (node) return 1;
  return 0;
}

void *ne_hash_next(NE_HASH *hash, void **key)
{
  NE_HASHNODE **node = 0;
  UINT32 hashv, bucket;

  if (*key)
  {
    node = _hash_lookup_node(hash, key, NULL);

    if (*node)
    {
      bucket = (*node)->hashv & (hash->size - 1);
    }
    else
    {
      hashv = hash->hash_func(*key);
      bucket = hashv & (hash->size - 1);
    }
  }
  else
  {
    bucket = 0;
  }

  if (*node)
  {
    if ((*node)->next)
    {
      *key = (*node)->next->key;
      return (*node)->next->value;
    }
    bucket++;
  }

  while (bucket < hash->size)
  {
    if (hash->nodes[bucket])
    {
      *key = hash->nodes[bucket]->key;
      return hash->nodes[bucket]->value;
    }
    bucket++;
  }

  return NULL;
}

static NE_HASHNODE **_hash_lookup_node (NE_HASH *hash, void *key, UINT32 *o_hashv)
{
  UINT32 hashv, bucket;
  NE_HASHNODE **node;

  hashv = hash->hash_func(key);
  if (o_hashv) *o_hashv = hashv;
  bucket = hashv & (hash->size - 1);
  /* ne_warn("Lookup %s %d %d", key, hashv, bucket); */

  node = &(hash->nodes[bucket]);

  if (hash->comp_func)
  {
    while (*node && !(hash->comp_func((*node)->key, key)))
      node = &(*node)->next;
  }
  else
  {
    /* No comp_func means we're doing pointer comparisons */
    while (*node && (*node)->key != key)
      node = &(*node)->next;
  }

  /* ne_warn("Node %x", node); */
  return node;
}

/* Ok, we're doing some weirdness here... */
static NEOERR *_hash_resize(NE_HASH *hash)
{
  NE_HASHNODE **new_nodes;
  NE_HASHNODE *entry, *prev;
  int x, next_bucket;
  int orig_size = hash->size;
  UINT32 hash_mask;

  if (hash->size > hash->num)
    return STATUS_OK;

  /* We always double in size */
  new_nodes = (NE_HASHNODE **) realloc (hash->nodes, (hash->size*2) * sizeof(NE_HASHNODE));
  if (new_nodes == NULL)
    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to resize NE_HASH");

  hash->nodes = new_nodes;
  orig_size = hash->size;
  hash->size = hash->size*2;

  /* Initialize new parts */
  for (x = orig_size; x < hash->size; x++)
  {
    hash->nodes[x] = NULL;
  }

  hash_mask = hash->size - 1;

  for (x = 0; x < orig_size; x++)
  {
    prev = NULL;
    next_bucket = x + orig_size;
    for (entry = hash->nodes[x]; 
	 entry; 
	 entry = prev ? prev->next : hash->nodes[x]) 
    {
      if ((entry->hashv & hash_mask) != x)
      {
	if (prev)
	{
	  prev->next = entry->next;
	}
	else
	{
	  hash->nodes[x] = entry->next;
	}
	entry->next = hash->nodes[next_bucket];
	hash->nodes[next_bucket] = entry;
      }
      else
      {
	prev = entry;
      }
    }
  }
  
  return STATUS_OK;
}

int ne_hash_str_comp(const void *a, const void *b)
{
  return !strcmp((const char *)a, (const char *)b);
}

UINT32 ne_hash_str_hash(const void *a)
{
  return ne_crc((unsigned char *)a, strlen((const char *)a));
}

int ne_hash_int_comp(const void *a, const void *b)
{
  if (a == b) return 1;
  return 0;
}

UINT32 ne_hash_int_hash(const void *a)
{
  return (UINT32)(long)(a);
}
