

/* Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
/* Portions Copyright (C) 2000-2019 The Xastir Group */

#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif  // HAVE_CONFIG_H

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

#include "hashtable.h"
#include "hashtable_private.h"

// Must be last include file
#include "leak_detection.h" /* defines GC_MALLOC/GC_FREE */



/*
Credit for primes table: Aaron Krowne
 http://br.endernet.org/~akrowne/
 http://planetmath.org/encyclopedia/GoodHashTablePrimes.html
*/
static const unsigned int primes[] =
{
  53, 97, 193, 389,
  769, 1543, 3079, 6151,
  12289, 24593, 49157, 98317,
  196613, 393241, 786433, 1572869,
  3145739, 6291469, 12582917, 25165843,
  50331653, 100663319, 201326611, 402653189,
  805306457, 1610612741
};
const unsigned int prime_table_length = sizeof(primes)/sizeof(primes[0]);
const float max_load_factor = 0.65;

/*****************************************************************************/
struct hashtable *
create_hashtable(unsigned int minsize,
                 unsigned int (*hashf) (void*),
                 int (*eqf) (void*,void*))
{
  struct hashtable *h;
  unsigned int pindex, size = primes[0];
  /* Check requested hashtable isn't too large */
  if (minsize > (1u << 30))
  {
    return NULL;
  }
  /* Enforce size as prime */
  for (pindex=0; pindex < prime_table_length; pindex++)
  {
    if (primes[pindex] > minsize)
    {
      size = primes[pindex];
      break;
    }
  }
  h = (struct hashtable *)malloc(sizeof(struct hashtable));
  if (NULL == h)
  {
    return NULL;  /*oom*/
  }
  h->table = (struct entry **)malloc(sizeof(struct entry*) * size);
  if (NULL == h->table)
  {
    free(h);  /*oom*/
    return NULL;
  }
  memset(h->table, 0, size * sizeof(struct entry *));
  h->tablelength  = size;
  h->primeindex   = pindex;
  h->entrycount   = 0;
  h->hashfn       = hashf;
  h->eqfn         = eqf;
  h->loadlimit    = ceil(size * max_load_factor);
  return h;
}

/*****************************************************************************/
unsigned int
hash(struct hashtable *h, void *k)
{
  /* Aim to protect against poor hash functions by adding logic here
   * - logic taken from java 1.4 hashtable source */
  unsigned int i = h->hashfn(k);
  i += ~(i << 9);
  i ^=  ((i >> 14) | (i << 18)); /* >>> */
  i +=  (i << 4);
  i ^=  ((i >> 10) | (i << 22)); /* >>> */
  return i;
}

/*****************************************************************************/
static int
hashtable_expand(struct hashtable *h)
{
  /* Double the size of the table to accomodate more entries */
  struct entry **newtable;
  struct entry *e;
  struct entry **pE;
  unsigned int newsize, i, index;
  /* Check we're not hitting max capacity */
  if (h->primeindex == (prime_table_length - 1))
  {
    return 0;
  }
  newsize = primes[++(h->primeindex)];

  newtable = (struct entry **)malloc(sizeof(struct entry*) * newsize);
  if (NULL != newtable)
  {
    memset(newtable, 0, newsize * sizeof(struct entry *));
    /* This algorithm is not 'stable'. ie. it reverses the list
     * when it transfers entries between the tables */
    for (i = 0; i < h->tablelength; i++)
    {
      while (NULL != (e = h->table[i]))
      {
        h->table[i] = e->next;
        index = indexFor(newsize,e->h);
        e->next = newtable[index];
        newtable[index] = e;
      }
    }
    free(h->table);
    h->table = newtable;
  }
  /* Plan B: realloc instead */
  else
  {
    newtable = (struct entry **)
               realloc(h->table, newsize * sizeof(struct entry *));
    if (NULL == newtable)
    {
      (h->primeindex)--;
      return 0;
    }
    h->table = newtable;
    memset(newtable[h->tablelength], 0, newsize - h->tablelength);
    for (i = 0; i < h->tablelength; i++)
    {
      for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE)
      {
        index = indexFor(newsize,e->h);
        if (index == i)
        {
          pE = &(e->next);
        }
        else
        {
          *pE = e->next;
          e->next = newtable[index];
          newtable[index] = e;
        }
      }
    }
  }
  h->tablelength = newsize;
  h->loadlimit   = ceil(newsize * max_load_factor);
  return -1;
}

/*****************************************************************************/
unsigned int
hashtable_count(struct hashtable *h)
{
  return h->entrycount;
}

/*****************************************************************************/
int
hashtable_insert(struct hashtable *h, void *k, void *v)
{
  /* This method allows duplicate keys - but they shouldn't be used */
  unsigned int index;
  struct entry *e;
  if (++(h->entrycount) > h->loadlimit)
  {
    /* Ignore the return value. If expand fails, we should
     * still try cramming just this value into the existing table
     * -- we may not have memory for a larger table, but one more
     * element may be ok. Next time we insert, we'll try expanding again.*/
    hashtable_expand(h);
  }
  e = (struct entry *)malloc(sizeof(struct entry));
  if (NULL == e)
  {
    --(h->entrycount);  /*oom*/
    return 0;
  }
  e->h = hash(h,k);
  index = indexFor(h->tablelength,e->h);
  e->k = k;
  e->v = v;
  e->next = h->table[index];
  h->table[index] = e;
  return -1;
}

/*****************************************************************************/
void * /* returns value associated with key */
hashtable_search(struct hashtable *h, void *k)
{
  struct entry *e;
  unsigned int hashvalue, index;
  hashvalue = hash(h,k);
  index = indexFor(h->tablelength,hashvalue);
  e = h->table[index];
  while (NULL != e)
  {
    /* Check hash value to short circuit heavier comparison */
    if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
    {
      return e->v;
    }
    e = e->next;
  }
  return NULL;
}

/*****************************************************************************/
void * /* returns value associated with key */
hashtable_remove(struct hashtable *h, void *k)
{
  /* TODO: consider compacting the table when the load factor drops enough,
   *       or provide a 'compact' method. */

  struct entry *e;
  struct entry **pE;
  void *v;
  unsigned int hashvalue, index;

  hashvalue = hash(h,k);
  index = indexFor(h->tablelength,hash(h,k));
  pE = &(h->table[index]);
  e = *pE;
  while (NULL != e)
  {
    /* Check hash value to short circuit heavier comparison */
    if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
    {
      *pE = e->next;
      h->entrycount--;
      v = e->v;
      freekey(e->k);
      free(e);
      return v;
    }
    pE = &(e->next);
    e = e->next;
  }
  return NULL;
}

/*****************************************************************************/
/* destroy */
void
hashtable_destroy(struct hashtable *h, int free_values)
{
  unsigned int i;
  struct entry *e, *f;
  struct entry **table = h->table;
  if (free_values)
  {
    for (i = 0; i < h->tablelength; i++)
    {
      e = table[i];
      while (NULL != e)
      {
        f = e;
        e = e->next;
        freekey(f->k);
        free(f->v);
        free(f);
      }
    }
  }
  else
  {
    for (i = 0; i < h->tablelength; i++)
    {
      e = table[i];
      while (NULL != e)
      {
        f = e;
        e = e->next;
        freekey(f->k);
        free(f);
      }
    }
  }
  free(h->table);
  free(h);
}

/*
 * Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * */


