/* implementation of auto-resizing hash table */
/* ifile - intelligent mail filter for EXMH/MH
   ifile is Copyright (C) 1997  Jason Rennie <jrennie@ai.mit.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 (see file 'COPYING'); if not, write to the Free
   Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA  02111-1307, USA.
   */

#include <stddef.h>    /* stdlib doesn't have NULL defined on SunOS 4.1.3_U1 */
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <hash_table.h>

/* Crock:  hashtables should create their own copies of string indices,
   except when they're resizing */
/* Crock added by Jeremy Brown <jhbrown@ai.mit.edu>, who isn't proud
   of it. */
int htable_privatize_indices = 1;


char * ifile_strdup (const char *s1);


/* Initializes values of hash table, allocates space for DATA array.
 * HASH_TABLE->DATA *must* be malloced. */
/* written by Jason Rennie <jrennie@ai.mit.edu> */
void
htable_init(htable * hash_table, long int num_slots,
	    unsigned long (*hash_fun) (const void *, long int))
{
  long int i = 0;

  /* makes the size of the array prime (up to about i=30) */
  for (i=7; ((1 << i) - 1) < num_slots; i += 2);
  num_slots = (1 << i) - 1;

  hash_table->num_slots = num_slots;
  hash_table->num_entries = 0;
  hash_table->data = (hash_elem *) malloc(num_slots*sizeof(hash_elem));
  hash_table->hash = hash_fun;

  for (i=0; i < num_slots; i++)
    {
      hash_table->data[i].index = NULL;
      hash_table->data[i].entry = NULL;
    }
}


/* Frees all memory used by HASH TABLE.  Uses caller-provided
   functions to free entry and index; NULL functions mean nothing needs
   to be done. */
/* written by Jason Rennie <jrennie@ai.mit.edu> */
void
htable_free_guts(htable * hash_table, void (*free_index)(void *), void (*free_entry)(void *))
{
  long int i;
  long int slots = hash_table->num_slots;
  hash_elem * data = hash_table->data;

  if (free_index) 
    for (i=0; i < slots; i++)
      free_index(data[i].index);

  if (free_entry) 
    for (i=0; i < slots; i++)
      free_entry(data[i].entry);

  free(hash_table->data);
}

/* See above function */
void
htable_free(htable * hash_table, void (*free_index)(void *), void (*free_entry)(void *)) {
  htable_free_guts(hash_table, free_index, free_entry);
  free(hash_table);
}

  



/* Adds an entry to the hash table, expands the hash table if it is too
 * crowded.  Assumes that INDEX and ENTRY will not be deallocated during
 * the life of the hash table */
/* written by Jason Rennie <jrennie@ai.mit.edu> */
void
htable_put(htable * hash_table, void * index, void * entry)
{
  unsigned long key;
  long int i;

  if ((float)(hash_table->num_entries+1) / (float)(hash_table->num_slots) >=
      CROWDED_PCT)
    htable_resize(hash_table, hash_table->num_slots*2);

  key = hash_table->hash(index, hash_table->num_slots);
  i = key % hash_table->num_slots;

  assert(i >= 0);

  /* loop until we find an open slot, or a slot with the same index */
  while (hash_table->data[i].entry != NULL &&
	 strcmp((char *) index,	(char *) (hash_table->data[i].index)) != 0)
    {
      key++;
      i = key % hash_table->num_slots;
      assert(i >= 0);
    }

  if (hash_table->data[i].entry == NULL) {
    hash_table->data[i].index = htable_privatize_indices? ifile_strdup(index) : index;
    hash_table->num_entries++;
  }

  hash_table->data[i].entry = entry;
}


/* Returns the ENTRY for a given INDEX.  Returns NULL if an ENTRY
 * for INDEX does not exist in the hash table */
/* written by Jason Rennie <jrennie@ai.mit.edu> */
void *
htable_lookup(htable * hash_table, void * index)
{
  unsigned long key;
  long int i;
  
  key = hash_table->hash(index, hash_table->num_slots);
  i = key % hash_table->num_slots;
  
  assert(i >= 0);

  /* loop until we find an open slot, or a slot with the same index */
  while (hash_table->data[i].entry != NULL &&
	 strcmp((char *) index,	(char *) (hash_table->data[i].index)) != 0)
    {
      key++;
      i = key % hash_table->num_slots;
      assert(i >= 0);
    }
  
  return hash_table->data[i].entry;
}


/* Returns first element in hash table.  Returns NULL if hash table i
 * empty. */
/* written by Jason Rennie <jrennie@ai.mit.edu> */
hash_elem *
htable_init_traversal(htable * hash_table)
{
  long int slots = hash_table->num_slots;
  long int slot_size = sizeof(hash_elem);
  long int elem;
  long int base = (long int) hash_table->data;
  long int limit = slots*slot_size + base;

  for (elem = base;
       elem < limit && (*(hash_elem *)elem).entry == NULL;
       elem += slot_size);
  
  if (elem >= limit)
    return NULL;
  else
    return (hash_elem *) elem;
}


/* Returns the next element of the hash table.  Sequential calls of
 * this function traverses all hash table entries.  Returns NULL if
 * ELEM is the last element of the traversal */
/* written by Jason Rennie <jrennie@ai.mit.edu> */
hash_elem *
htable_next_traversal(htable * hash_table, hash_elem * elem)
{
  long int slots = hash_table->num_slots;
  long int slot_size = sizeof(hash_elem);
  long int base = (long int) hash_table->data;
  long int limit = slots*slot_size + base;
  long int elem_ptr;

  /* takes advantage of the fact that DATA is an array */
  for (elem_ptr = (long int) elem + slot_size;
       elem_ptr < limit && (*(hash_elem *)elem_ptr).entry == NULL;
       elem_ptr += slot_size);

  if ((long int) elem_ptr >= limit)
    return NULL;
  else
    return (hash_elem *) elem_ptr;
}
      

/* Resizes DATA so that it is capable of containing at least NUM_SLOTS
 * entries.  This function is reliant on the current implementation
 * of other htable functions. */
/* written by Jason Rennie <jrennie@ai.mit.edu> */
void
htable_resize(htable * hash_table, long int num_slots)
{
  htable fake_htable;
  hash_elem * elem;
  
  htable_init(&fake_htable, num_slots, hash_table->hash);

  htable_privatize_indices = 0;
  for (elem = htable_init_traversal(hash_table);
       elem != NULL; elem = htable_next_traversal(hash_table, elem))
    htable_put(&fake_htable, (*elem).index, (*elem).entry);
  htable_privatize_indices = 1;

  free(hash_table->data);
  hash_table->data = fake_htable.data;
  hash_table->num_slots = fake_htable.num_slots;
}
