/* $Id: hash.c,v 1.2 2003/09/01 14:24:08 fredette Exp $ */

/* libtme/hash.c - hash table support: */

/*
 * Copyright (c) 2003 Matt Fredette
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Matt Fredette.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <tme/common.h>
_TME_RCSID("$Id: hash.c,v 1.2 2003/09/01 14:24:08 fredette Exp $");

/* includes: */
#include <tme/hash.h>

/* our hash table size array: */
static unsigned long _tme_hash_sizes[] = {
  2,
  3,
  5,
  7,
  11,
  17,
  37,
  83,
  281,
  421,
  631,
  947,
  2131,
  7193,
  10789,
  16183,
  81929,
  414763,
  933217,
  10629917,
  35875969,
  80720929,
};

/* this allocates and returns a new hash: */
tme_hash_t
tme_hash_new(tme_hash_func_t hash_func,
	     tme_compare_func_t compare_func,
	     tme_hash_data_t value_null)
{
  tme_hash_t hash;

  hash = tme_new0(struct _tme_hash, 1);
  hash->_tme_hash_size = _tme_hash_sizes[0];
  hash->_tme_hash_table = 
    tme_new0(struct _tme_hash_bucket *,
	     hash->_tme_hash_size);
  hash->_tme_hash_count = 0;
  hash->_tme_hash_hash = hash_func;
  hash->_tme_hash_compare = compare_func;
  hash->_tme_hash_null = value_null;
  return (hash);
}

/* this destroys a hash: */
void
tme_hash_destroy(tme_hash_t hash)
{
  struct _tme_hash_bucket *bucket, *bucket_next;
  unsigned long bucket_i;

  /* free all of the buckets in the hash table: */
  for (bucket_i = 0;
       bucket_i < hash->_tme_hash_size;
       bucket_i++) {
    for (bucket = hash->_tme_hash_table[bucket_i];
	 bucket != NULL;
	 bucket = bucket_next) {
      bucket_next = bucket->_tme_hash_bucket_next;
      tme_free(bucket);
    }
  }
  tme_free(hash->_tme_hash_table);
  tme_free(hash);
}

/* this does an internal lookup in a hash table: */
static struct _tme_hash_bucket *
_tme_hash_lookup_internal(tme_hash_t hash,
			  tme_hash_data_t key,
			  struct _tme_hash_bucket ***__bucket)
{
  unsigned long bucket_i;
  struct _tme_hash_bucket **_bucket, *bucket;
  
  /* hash the key: */
  bucket_i = (*hash->_tme_hash_hash)(key) % hash->_tme_hash_size;

  /* walk the chain of buckets: */
  for (_bucket = hash->_tme_hash_table + bucket_i;
       (bucket = *_bucket) != NULL;
       _bucket = &bucket->_tme_hash_bucket_next) {
    
    /* compare the key in this bucket with the lookup key.
       if it succeeds, return the bucket: */
    if ((*hash->_tme_hash_compare)(key, bucket->_tme_hash_bucket_key)) {
      if (__bucket != NULL) {
	*__bucket = _bucket;
      }
      return (bucket);
    }
  }

  /* the lookup failed.  return where the bucket might be inserted: */
  if (__bucket != NULL) {
    *__bucket = _bucket;
  }
  return (NULL);
}

/* this inserts a value into a hash table: */
void
tme_hash_insert(tme_hash_t hash,
		tme_hash_data_t key,
		tme_hash_data_t value)
{
  struct _tme_hash_bucket *bucket, *bucket_next, **_bucket;
  struct _tme_hash hash_new;
  int size_i;
  unsigned long bucket_i;

  /* if this key is not already present in the hash table: */
  bucket = _tme_hash_lookup_internal(hash, key, &_bucket);
  if (bucket == NULL) {

    /* if we need to resize this hash table: */
    if ((hash->_tme_hash_count * 2) > hash->_tme_hash_size) {

      /* make a copy of the top of the hash: */
      hash_new = *hash;

      /* set the new size of the hash: */
      hash_new._tme_hash_size = hash->_tme_hash_count * 2;
      for (size_i = 0;
	   _tme_hash_sizes[size_i] < hash_new._tme_hash_size;
	   size_i++) {
	if (size_i + 1 == TME_ARRAY_ELS(_tme_hash_sizes)) {
	  abort();
	}
      }
      hash_new._tme_hash_size = _tme_hash_sizes[size_i];

      /* allocate the new hash table: */
      hash_new._tme_hash_table = 
	tme_new0(struct _tme_hash_bucket *,
		 hash_new._tme_hash_size);

      /* move everything from the old hash table into the new: */
      for (bucket_i = 0;
	   bucket_i < hash->_tme_hash_size;
	   bucket_i++) {
	for (bucket = hash->_tme_hash_table[bucket_i];
	     bucket != NULL;
	     bucket = bucket_next) {
	  bucket_next = bucket->_tme_hash_bucket_next;
	  _tme_hash_lookup_internal(&hash_new,
				    bucket->_tme_hash_bucket_key,
				    &_bucket);
	  bucket->_tme_hash_bucket_next = *_bucket;
	  *_bucket = bucket;
	}
      }

      /* free the old hash table: */
      tme_free(hash->_tme_hash_table);

      /* set the new top of the hash: */
      *hash = hash_new;
    
      /* do the internal lookup again: */
      _tme_hash_lookup_internal(hash, key, &_bucket);
    }

    /* create the new bucket and link it in: */
    bucket = tme_new(struct _tme_hash_bucket, 1);
    bucket->_tme_hash_bucket_next = *_bucket;
    *_bucket = bucket;

    /* increment the number of keys in the hash: */
    hash->_tme_hash_count++;
  }

  /* set the key and value in the bucket: */
  bucket->_tme_hash_bucket_key = key;
  bucket->_tme_hash_bucket_value = value;
}

/* this looks up a key in the hash: */
tme_hash_data_t
tme_hash_lookup(tme_hash_t hash,
		tme_hash_data_t key)
{
  struct _tme_hash_bucket *bucket;

  bucket = _tme_hash_lookup_internal(hash, key, NULL);
  return (bucket != NULL
	  ? bucket->_tme_hash_bucket_value
	  : hash->_tme_hash_null);
}

/* this removes a key in the hash: */
void
tme_hash_remove(tme_hash_t hash,
		tme_hash_data_t key)
{
  struct _tme_hash_bucket *bucket, **_bucket;

  bucket = _tme_hash_lookup_internal(hash, key, &_bucket);
  if (bucket != NULL) {
    *_bucket = bucket->_tme_hash_bucket_next;
    tme_free(bucket);
    hash->_tme_hash_count--;
  }
}

/* this calls a function for each key and value in the hash: */
void
tme_hash_foreach(tme_hash_t hash,
		 tme_foreach_func_t func,
		 void *private)
{
  struct _tme_hash_bucket *bucket;
  unsigned long bucket_i;

  /* walk all of the buckets in the hash table: */
  for (bucket_i = 0;
       bucket_i < hash->_tme_hash_size;
       bucket_i++) {
    for (bucket = hash->_tme_hash_table[bucket_i];
	 bucket != NULL;
	 bucket = bucket->_tme_hash_bucket_next) {
      (*func)(bucket->_tme_hash_bucket_key,
	      bucket->_tme_hash_bucket_value,
	      private);
    }
  }
}

/* this calls a function for each key and value in the hash.
   if the function returns TRUE the entry is removed: */
unsigned long
tme_hash_foreach_remove(tme_hash_t hash,
			tme_foreach_remove_func_t func,
			void *private)
{
  struct _tme_hash_bucket **_bucket, *bucket;
  unsigned long bucket_i, count;

  /* walk all of the buckets in the hash table: */
  count = 0;
  for (bucket_i = 0;
       bucket_i < hash->_tme_hash_size;
       bucket_i++) {
    for (_bucket = &hash->_tme_hash_table[bucket_i];
	 (bucket = *_bucket) != NULL; ) {
      if ((*func)(bucket->_tme_hash_bucket_key,
		  bucket->_tme_hash_bucket_value,
		  private)) {
	*_bucket = bucket->_tme_hash_bucket_next;
	tme_free(bucket);
	hash->_tme_hash_count--;
	count++;
      }
      else {
	_bucket = &bucket->_tme_hash_bucket_next;
      }
    }
  }
  return (count);
}

/* this hashes a direct value: */
unsigned long
tme_direct_hash(tme_hash_data_t key)
{
  return ((tme_uintptr_t) key);
}

/* this compares two direct values: */
int
tme_direct_compare(tme_hash_data_t key0,
		   tme_hash_data_t key1)
{
  return (key0 == key1);
}

/* this hashes a string value: */
unsigned long
tme_string_hash(tme_hash_data_t key)
{
  /* this is cribbed from the Dragon book: */
  const char *p;
  char c;
  unsigned long h, g;

  p = (const char *) key;
  h = 0;

  for (; (c = *(p++)) != '\0'; ) {
    h = (h << 4) + c;
    g = (h & 0xf0000000);
    if (g) {
      h = h ^ (g >> 24);
      h = h ^ g;
    }
  }

  return (h);
}

/* this compares two string values: */
int
tme_string_compare(tme_hash_data_t key0,
		   tme_hash_data_t key1)
{
  return (!strcmp((const char *) key0, (const char *) key1));
}

