/* Copyright (C) 2003,2004 MORIOKA Tomohiko
   This file is part of the CHISE Library.

   The CHISE Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The CHISE Library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the CHISE Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include <string.h>
#include <stdlib.h>
#include "chise-name.h"

struct CHISE_HASH_TABLE_ENTRY
{
  void *key;
  void *value;
};

struct CHISE_HASH_TABLE
{
  size_t size;
  CHISE_HASH_TABLE_ENTRY *data;
};

CHISE_HASH_TABLE* chise_make_hash_table (size_t size);
void chise_destroy_hash_table (CHISE_HASH_TABLE* hash);
int chise_hash_c_string (const unsigned char *ptr);

CHISE_HASH_TABLE*
chise_make_hash_table (size_t size)
{
  CHISE_HASH_TABLE* hash
    = (CHISE_HASH_TABLE*)malloc (sizeof (CHISE_HASH_TABLE));

  if (hash == NULL)
    return NULL;

  hash->data
    = (CHISE_HASH_TABLE_ENTRY*) malloc (sizeof (CHISE_HASH_TABLE_ENTRY)
					* size);
  if (hash->data == NULL)
    {
      free (hash);
      return NULL;
    }

  hash->size = size;
  memset (hash->data, 0, sizeof (CHISE_HASH_TABLE_ENTRY) * size);
  return hash;
}

void
chise_destroy_hash_table (CHISE_HASH_TABLE* table)
{
  if (table == NULL)
    return;
  free (table->data);
  free (table);
}


/* derived from hashpjw, Dragon Book P436. */
int
chise_hash_c_string (const unsigned char *ptr)
{
  int hash = 0;

  while (*ptr != '\0')
    {
      int g;
      hash = (hash << 4) + *ptr++;
      g = hash & 0xf0000000;
      if (g)
	hash = (hash ^ (g >> 24)) ^ g;
    }
  return hash & 07777777777;
}


CHISE_NAME_TABLE*
chise_make_name_table ()
{
  return chise_make_hash_table (256);
}

void
chise_destroy_name_table (CHISE_NAME_TABLE* table)
{
  int i;

  for (i = 0; i < table->size; i++)
    {
      CHISE_NAME_TABLE_ENTRY entry = table->data[i];

      if (entry.key != NULL)
	{
	  if (entry.value != NULL)
	    free (entry.value);
	  free (entry.key);
	}
    }
  chise_destroy_hash_table (table);
}

int
chise_name_table_put (CHISE_NAME_TABLE* table,
		      const unsigned char *key, void *value)
{
  int i, index;
  CHISE_NAME_TABLE_ENTRY* entry;

  if (table == NULL)
    return -1;

  index = chise_hash_c_string (key) % table->size;
  for (i = index; i < table->size; i++)
    {
      entry = &table->data[i];
      if (entry->key == NULL)
	{
	  size_t len = strlen (key);

	  entry->key = (unsigned char*)malloc (len + 1);
	  if (entry->key == NULL)
	    return -1;
	  strcpy (entry->key, key);
	  entry->value = value;
	  return 0;
	}
      else if (strcmp (entry->key, key) == 0)
	{
	  entry->value = value;
	  return 0;
	}
    }
  if (chise_name_table_grow (table) == 0)
    return chise_name_table_put (table, key, value);
  return -1;
}

void *
chise_name_table_get (CHISE_NAME_TABLE* table,
		      const unsigned char *key)
{
  int i, index;
  CHISE_NAME_TABLE_ENTRY entry;

  if (table == NULL)
    return NULL;

  index = chise_hash_c_string (key) % table->size;
  for (i = index; i < table->size; i++)
    {
      entry = table->data[i];
      if (entry.key == NULL)
	return NULL;
      else if (strcmp (entry.key, key) == 0)
	return entry.value;
    }
  return NULL;
}

int
chise_name_table_grow (CHISE_NAME_TABLE* table)
{
  CHISE_NAME_TABLE *new_table
    = chise_make_hash_table ( table->size * 2
			      /* - (table->size * 4 / 5) */ );
  int i;

  if (new_table == NULL)
    return -1;

  for (i = 0; i < table->size; i++)
    {
      CHISE_NAME_TABLE_ENTRY entry = table->data[i];
      if ( (entry.key != NULL) && (entry.value != NULL) )
	{
	  int status
	    = chise_name_table_put (new_table, entry.key, entry.value);
	  if (status != 0)
	    {
	      chise_destroy_hash_table (new_table);
	      return -1;
	    }
	}
    }
  table->size = new_table->size;
  table->data = new_table->data;
  free (new_table);
  return 0;
}
