#include <string.h>
#include "hash.h"

#ifdef emacs
#include "config.h"
#include "lisp.h"
extern Lisp_Object Qnil;
extern char *elisp_hvector_malloc();
extern void elisp_hvector_free();
#define NULL_ENTRY ((void *) Qnil)
#else
#define NULL_ENTRY ((void *) 1)
#endif

#define MAXPRIME 62

static int 
primes []={
  29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 
  761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 
  8419, 10103, 12143, 14591, 17519, 21023, 25229, 30293, 36353, 43627, 52361, 
  62851, 75431, 90523, 108631, 130363, 156437, 187751, 225307, 270371, 324449, 
  389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 1674319,
  2009191, 2411033, 2893249
};

/* strings code */

/* from base/generic-hash.cc, and hence from Dragon book, p436 */
static unsigned long
string_hash (x)
     char *x;
{ 
  unsigned int h = 0;
  unsigned int g;

  if (!x) return 0;

  while (*x != 0)
  {
    h = (h << 4) + *x++;
    if ((g = h & 0xf0000000) != 0)
      h = (h ^ (g >> 24)) ^ g;
  }

  return h;
}

static int 
string_eq (st1, st2)
     char *st1, *st2;
{
  if (!st1)
    return (st2)?0:1;
  else if (!st2)
    return 0;
  else
    return !strcmp (st1, st2);
}


static unsigned int 
prime_size (size)
     unsigned int size;
{
  unsigned int i;
  for (i = 0; i < MAXPRIME; i++)
    if (size <= primes [i]) return primes [i];
  return primes [MAXPRIME - 1];
}

static void 
rehash (/* hentry *harray, c_hashtable ht, unsigned int size */);

#define KEYS_DIFFER_P(old, new, testfun) \
  ((testfun)?(((old) == (new))?0:(!(testfun ((old), new)))):((old) != (new)))

void *
gethash (key, hash, ret_value)
     void *key; c_hashtable hash; void **ret_value;
{
  hentry *harray = hash->harray;
  int (*test_function)() = hash->test_function;
  unsigned int hsize = hash->size;
  unsigned int hcode_initial = 
    (hash->hash_function)?(hash->hash_function(key)):((unsigned long) key);
  unsigned int hcode = hcode_initial % hsize;
  hentry *e = &harray [hcode];
  void *e_key = e->key;

  if (!key) 
    {
      *ret_value = hash->zero_entry;
      return (void *) hash->zero_set;
    }
    
  if ((e_key)?
      (KEYS_DIFFER_P (e_key, key, test_function)):
      (e->contents == NULL_ENTRY))
    {
      unsigned int h2 = hsize - 2;
      unsigned int incr = 1 + (hcode_initial % h2);
      do
        {
          hcode = hcode + incr;
          if (hcode >= hsize) hcode = hcode - hsize;
          e = &harray [hcode];
          e_key = e->key;
        } 
      while ((e_key)?
             (KEYS_DIFFER_P (e_key, key, test_function)):
             (e->contents == NULL_ENTRY));
    }

  *ret_value = e->contents;
  return e->key;
}

void 
clrhash (hash)
     c_hashtable hash;
{
  memset (hash->harray, 0, sizeof (hentry) * hash->size);
  hash->zero_entry = 0;
  hash->zero_set = 0;
  hash->fullness = 0;
}

void
free_hashtable (hash)
     c_hashtable hash;
{
#ifdef emacs
  if (hash->elisp_table) return;
#endif
  free (hash->harray);
  free (hash);
}

c_hashtable
make_hashtable (hsize)
     unsigned int hsize;
{
  c_hashtable res = (c_hashtable) xmalloc (sizeof (struct _C_hashtable));
  bzero (res, sizeof (struct _C_hashtable));
  res->size = prime_size ((13 * hsize) / 10);
  res->harray = (hentry *) xmalloc (sizeof (hentry) * res->size);
  clrhash (res);
  return res;
}

c_hashtable
make_general_hashtable (unsigned int hsize,
			unsigned long (*hash_function)(),
			int (*test_function)())
{
  c_hashtable res = (c_hashtable) xmalloc (sizeof (struct _C_hashtable));
  bzero (res, sizeof (struct _C_hashtable));
  res->size = prime_size ((13 * hsize) / 10);
  res->harray = (hentry *) xmalloc (sizeof (hentry) * res->size);
  res->hash_function = hash_function;
  res->test_function = test_function;
  clrhash (res);
  return res;
}

c_hashtable 
make_strings_hashtable (hsize)
     unsigned int hsize;
{
  return make_general_hashtable (hsize, string_hash, string_eq);
}

#ifdef emacs
unsigned int
compute_harray_size (hsize)
     unsigned int hsize;
{
  return prime_size ((13 * hsize) / 10);
}
#endif

void
copy_hash (dest, src)
     c_hashtable dest; c_hashtable src;
{
#ifdef emacs
  /* if these are not the same, then we are losing here */
  if ( ((dest->elisp_table)?1:0) != ((src->elisp_table)?1:0) )
    {
      extern void error ();
      error ("Incompatible hashtable types to copy_hash.");
      return;
    }
#endif

  if (dest->size != src->size)
    {
#ifdef emacs
      if (dest->elisp_table) 
        elisp_hvector_free (dest->harray, dest->elisp_table);
      else
#endif
        free (dest->harray);

      dest->size = src->size;
#ifdef emacs
      if (dest->elisp_table)
        dest->harray = 
          (hentry *) elisp_hvector_malloc
            (sizeof (hentry) * dest->size, dest->elisp_table);
      else
#endif
        dest->harray = 
          (hentry *) 
            xmalloc (sizeof (hentry) * dest->size);
    }
  dest->fullness = src->fullness;
  dest->zero_entry = src->zero_entry;
  dest->zero_set = src->zero_set;
  dest->hash_function = src->hash_function;
  dest->test_function = src->test_function;
  memcpy (dest->harray, src->harray, sizeof (hentry) * dest->size);
}
  
void
grow_hashtable (hash, new_size)
     c_hashtable hash; unsigned int new_size;
{
  unsigned int old_hsize = hash->size;
  hentry *old_harray = hash->harray;
  unsigned int new_hsize = prime_size (new_size);
  hentry *new_harray;

#ifdef emacs
  if (hash->elisp_table)
    new_harray = 
      (hentry *) elisp_hvector_malloc
        (sizeof (hentry) * new_hsize, hash->elisp_table);
  else
#endif
    new_harray =
      (hentry *) xmalloc (sizeof (hentry) * new_hsize);

  hash->size = new_hsize;
  hash->harray = new_harray;

  /* do the rehash on the "grown" table */
  {
    int old_zero_set = hash->zero_set;
    void *old_zero_entry = hash->zero_entry;
    clrhash (hash);
    hash->zero_set = old_zero_set;
    hash->zero_entry = old_zero_entry;
    rehash (old_harray, hash, old_hsize);
  }

#ifdef emacs
  if (hash->elisp_table) 
    elisp_hvector_free (old_harray, hash->elisp_table);
  else
#endif
    free (old_harray);
}

void 
puthash (key, cont, hash)
     void *key; void *cont; c_hashtable hash;
{
  unsigned int hsize = hash->size;
  int (*test_function)() = hash->test_function;
  unsigned int fullness = hash->fullness;
  hentry *harray;
  void *e_key;
  hentry *e;
  unsigned int hcode_initial = 
    (hash->hash_function)?(hash->hash_function(key)):((unsigned long) key);
  unsigned int hcode;
  unsigned int incr = 0;
  unsigned int h2;
  void *oldcontents;

  if (!key) 
    {
      hash->zero_entry = cont;      
      hash->zero_set = 1;
      return;
    }

  if (hsize < (1 + ((13 * fullness) / 10)))
    {
      grow_hashtable (hash, hsize + 1);
      hsize = hash->size;
      fullness = hash->fullness;
    }

  harray= hash->harray;
  h2 = hsize - 2;

  hcode = hcode_initial % hsize;
  
  e_key = harray [hcode].key;
  if (e_key && (KEYS_DIFFER_P (e_key, key, test_function)))
    {
      h2 = hsize - 2;
      incr = 1 + (hcode_initial % h2);
      do
        {
          hcode = hcode + incr;
          if (hcode >= hsize) hcode = hcode - hsize;
          e_key = harray [hcode].key;
        } 
      while (e_key && (KEYS_DIFFER_P (e_key, key, test_function)));
    }
  oldcontents = harray [hcode].contents;
  harray [hcode].key = (void *) key;
  harray [hcode].contents = (void *) cont;
  /* if the entry that we used was a deleted entry,
     check for a non deleted entry of the same key,
     then delete it */
  if (!e_key && (oldcontents == NULL_ENTRY))
    {
      if (!incr) incr = 1 + ((unsigned long) key % h2);

      do
        {
          hcode = hcode + incr;
          if (hcode >= hsize) hcode = hcode - hsize;
          e = &harray [hcode];
          e_key = e->key;
        }
      while ((e_key)?
             (KEYS_DIFFER_P (e_key, key, test_function)):
             (e->contents == NULL_ENTRY));

      if (e_key)
        {
          e->key = 0;
          e->contents = NULL_ENTRY;
        }
    }

  /* only increment the fullness when we used up a new hentry */
  if (!e_key || (KEYS_DIFFER_P (e_key, key, test_function)))
    hash->fullness++;
}

static void
rehash (harray, hash, size)
     hentry *harray; c_hashtable hash; unsigned int size;
{
  hentry *limit = harray + size;
  hentry *e;
  for (e = harray; e < limit; e++) 
    if (e->key) puthash (e->key, e->contents, hash);
}

void 
remhash (key, hash)
     void *key; c_hashtable hash;
{
  hentry *harray = hash->harray;
  int (*test_function)() = hash->test_function;
  unsigned int hsize = hash->size;
  unsigned int hcode_initial = 
    (hash->hash_function)?(hash->hash_function(key)):((unsigned long) key);
  unsigned int hcode = hcode_initial % hsize;
  hentry *e = &harray [hcode];
  void *e_key = e->key;

  if (!key) 
    {
      hash->zero_entry = 0;
      hash->zero_set = 0;
      return;
    }

  if ((e_key)?
      (KEYS_DIFFER_P (e_key, key, test_function)):
      (e->contents == NULL_ENTRY))
    {
      unsigned int h2 = hsize - 2;
      unsigned int incr = 1 + (hcode_initial % h2);
      do
        {
          hcode = hcode + incr;
          if (hcode >= hsize) hcode = hcode - hsize;
          e = &harray [hcode];
          e_key = e->key;
        }
      while ((e_key)?
             (KEYS_DIFFER_P (e_key, key, test_function)):
             (e->contents == NULL_ENTRY));
    }
  if (e_key)
    {
      e->key = 0;
      e->contents = NULL_ENTRY;
/* This seems to break the world and I don't understand why. 
   Keymaps need a correct count of how many items are really in
   the table, taking remhash into account, but it looks like I
   have to do it by hand in the keymap code.  -jwz

      hash->fullness--;
 */
    }
}

void 
maphash (mf, hash, arg)
     maphash_function mf; c_hashtable hash; void *arg;
{
  hentry *e;
  hentry *limit;
  
  if (hash->zero_set) (*mf) (0, hash->zero_entry, arg);

  for (e = hash->harray, limit = e + hash->size; e < limit; e++)
    if (e->key) (*mf) (e->key, e->contents, arg);
}


void 
map_remhash (predicate, hash, arg)
     remhash_predicate predicate; 
     c_hashtable hash; 
     void *arg;
{
  hentry *e;
  hentry *limit;
  
  if (hash->zero_set && ((*predicate) (0, hash->zero_entry, arg)))
    {
      hash->zero_set = 0;
      hash->zero_entry = 0;
    }

  for (e = hash->harray, limit = e + hash->size; e < limit; e++)
    if ((*predicate) (e->key, e->contents, arg))
      {
        e->key = 0;
        e->contents = NULL_ENTRY;
      }
}

