/*
 *      Copyright (c) 1996 Ascend Communications, Inc.
 *      All rights reserved.
 *
 *	Permission to copy all or part of this material for any purpose is
 *	granted provided that the above copyright notice and this paragraph
 *	are duplicated in all copies.  THIS SOFTWARE IS PROVIDED ``AS IS''
 *	AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 *	LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *	FOR A PARTICULAR PURPOSE.
 *
 *	Written by Greg McGary <gkm@eng.ascend.com>, August 1996
 */

/* $Id: hash.c,v 1.2 1996/12/12 00:04:46 baskar Exp $ */

#include "hash.h"

#define MALLOC(t, n) ((t *) (malloc (sizeof (t) * (n))))
#define CALLOC(t, n) ((t *) (calloc (sizeof (t), (n))))

/* Implement double hashing with open addressing.  The table size is
   always a power of two.  The secondary (`increment') hash function
   is forced to return an odd-value, in order to be relatively prime
   to the table size.  This guarantees that the increment can
   potentially hit every slot in the table during collision
   resolution.  */

void *hash_deleted_item = &hash_deleted_item;

static void hash_rehash P__((struct hash_table* ht));

/* Table size must be given as a power of two.  */

void
hash_init (ht, size, hash_1, hash_2, hash_cmp)
     struct hash_table* ht;
     unsigned long size;
     hash_func_t hash_1;
     hash_func_t hash_2;
     hash_compare_func_t hash_cmp;
{
  ht->ht_size = size;
  ht->ht_vector = (void**) CALLOC (struct token *, ht->ht_size);
  ht->ht_capacity = ht->ht_size * 7 / 8; /* maximum 87.5% load factor */
  ht->ht_cardinality = 0;
  ht->ht_hash_1 = hash_1;
  ht->ht_hash_2 = hash_2;
  ht->ht_compare = hash_cmp;
}

/* Return the address of the table slot matching KEY.  If KEY isn't
   found, return the address of the empty slot suitable for insertion
   via hash_insert_at ().  */

void **
hash_find_slot (ht, key)
     struct hash_table* ht;
     void CONST *key;
{
  void **slot;
  void **deleted_slot = 0;
  unsigned int hash_2 = 0;
  unsigned int hash_1 = (*ht->ht_hash_1) (key);

  for (;;)
    {
      hash_1 %= ht->ht_size;
      slot = &ht->ht_vector[hash_1];

      if (*slot == 0)
	return slot;
      if (*slot == hash_deleted_item)
	{
	  if (deleted_slot == 0)
	    deleted_slot = slot;
	}
      else
	{
	  if (key == *slot)
	    return slot;
	  if ((*ht->ht_compare) (key, *slot) == 0)
	    return slot;
	}
      if (!hash_2)
	  hash_2 = (*ht->ht_hash_2) (key) | 1;
      hash_1 += hash_2;
    }
}

/* Insert ITEM in the table.  If an item with a matching key was
   already in the table, replace it and return the old one.  If no
   item matching key was found, return ITEM.  */

void *
hash_insert (ht, item)
     struct hash_table* ht;
     void *item;
{
  void **slot = hash_find_slot (ht, item);
  return hash_insert_at (ht, item, slot);
}

/* Double the size of the hash table in the event of overflow... */

static void
hash_rehash (ht)
     struct hash_table* ht;
{
  unsigned long old_ht_size = ht->ht_size;
  void **old_vector = ht->ht_vector;
  void **ovp;
  void **slot;

  ht->ht_size *= 2;
  ht->ht_capacity = ht->ht_size - (ht->ht_size >> 4);
  ht->ht_vector = (void **) CALLOC (struct token *, ht->ht_size);

  for (ovp = old_vector; ovp < &old_vector[old_ht_size]; ovp++)
    {
      if (*ovp == 0)
	continue;
      slot = hash_find_slot (ht, *ovp);
      *slot = *ovp;
    }
  free (old_vector);
}

/* Insert ITEM at table position SLOT, which was located by a previous
   call to hash_find_slot.  Return value is the same as for
   hash_insert() */

void *
hash_insert_at (ht, item, slot)
     struct hash_table* ht;
     void *item;
     void *slot;
{
  void *old_item = *(void **) slot;
  if (HASH_VACANT (old_item))
    {
      ht->ht_cardinality++;
      old_item = item;
    }
  *(void **) slot = item;
  if (ht->ht_cardinality >= ht->ht_capacity)
    hash_rehash (ht);
  return old_item;
}

/* Delete the item table position SLOT and return it.
   hash_insert() */

void *
hash_delete_at (ht, slot)
     struct hash_table* ht;
     void *slot;
{
  void *item = *(void **) slot;
  if (!HASH_VACANT (item))
    {
      *(void **) slot = hash_deleted_item;
      ht->ht_cardinality--;
      return item;
    }
  else
    return 0;
}

void
hash_delete_all (ht)
     struct hash_table* ht;
{
  memset (ht->ht_vector, 0, ht->ht_size * sizeof (void *));
  ht->ht_cardinality = 0;
}

void
hash_iterate (ht, iter_func, arg)
     struct hash_table *ht;
     hash_iter_func_t iter_func;
     void *arg;
{
  void **slot;
  void **end = &ht->ht_vector[ht->ht_size];

  for (slot = ht->ht_vector; slot < end; slot++)
    {
      if (!HASH_VACANT (*slot))
	(*iter_func) (*slot, arg);
    }
}

/* Dump all items into a NULL-terminated vector.  Use the
   user-supplied vector, or malloc one.  */

void**
hash_dump (ht, vector_0)
     struct hash_table *ht;
     void **vector_0;
{
  void **vector;
  void **slot;
  void **end = &ht->ht_vector[ht->ht_size];

  if (vector_0 == 0)
    vector_0 = MALLOC (void *, ht->ht_cardinality + 1);
  vector = vector_0;

  for (slot = ht->ht_vector; slot < end; slot++)
    if (!HASH_VACANT (*slot))
      *vector++ = *slot;
  *vector = 0;
  return vector_0;
}
