/* A module for a simple "name context", i.e. lisp-style atoms */

#include "gt1-misc.h"

#include "gt1-namecontext.h"
#if defined(_WIN32) || defined(macintosh) || defined(__APPLE__)
#	include <string.h>
#endif

/* btw, I do not know who wrote the following comment. I modified this
   file somewhat from gimp's app/procedural_db.c hash function. */

/* I have seen something like that in Perl/TK.  --MW */

static unsigned int
gt1_name_context_hash_func (const char *string)
{
  unsigned int result;
  int c;
  int i;

  /*
   * I tried a zillion different hash functions and asked many other
   * people for advice.  Many people had their own favorite functions,
   * all different, but no-one had much idea why they were good ones.
   * I chose the one below (multiply by 9 and add new character)
   * because of the following reasons:
   *
   * 1. Multiplying by 10 is perfect for keys that are decimal strings,
   *    and multiplying by 9 is just about as good.
   * 2. Times-9 is (shift-left-3) plus (old).  This means that each
   *    character's bits hang around in the low-order bits of the
   *    hash value for ever, plus they spread fairly rapidly up to
   *    the high-order bits to fill out the hash value.  This seems
   *    works well both for decimal and non-decimal strings.
   *
   * tclHash.c --
   *
   *      Implementation of in-memory hash tables for Tcl and Tcl-based
   *      applications.
   *
   * Copyright (c) 1991-1993 The Regents of the University of California.
   * Copyright (c) 1994 Sun Microsystems, Inc.
   */

  result = 0;
  for (i = 0; (c = ((const unsigned char *)string)[i]) != '\0'; i++)
    result += (result << 3) + c;

  return result;
}

static unsigned int
gt1_name_context_hash_func_size (const char *string, int size)
{
  unsigned int result;
  int i;

  result = 0;
  for (i = 0; i < size; i++)
    result += (result << 3) + ((const unsigned char *)string)[i];

  return result;
}

Gt1NameContext *
gt1_name_context_new (void)
{
  Gt1NameContext *nc;
  int i;

  nc = gt1_new (Gt1NameContext, 1);
  nc->num_entries = 0;
  nc->table_size = 16;
  nc->table = gt1_new (Gt1NameContextHashEntry, nc->table_size);
  for (i = 0; i < nc->table_size; i++)
    nc->table[i].name = NULL;

  return nc;
}

void
gt1_name_context_free (Gt1NameContext *nc)
{
  int i;

  for (i = 0; i < nc->table_size; i++)
    if (nc->table[i].name != NULL)
      gt1_free (nc->table[i].name);
  gt1_free (nc->table);
  gt1_free (nc);
}

static char *
gt1_name_context_strdup (const char *s)
{
  int len;
  char *new;

  len = strlen (s);
  new = gt1_new (char, len + 1);
  memcpy (new, s, len);
  new[len] = '\0';
  return new;
}

static gt1_boolean
gt1_name_context_streq_size (const char *s1, const char *s2, int size2)
{
  int i;

  /* could rewrite for 32 bits at a time, but I wouldn't worry */
  for (i = 0; i < size2; i++)
    if (s1[i] != s2[i])
      return gt1_false;
  return s1[i] == 0;
}

static char *
gt1_name_context_strdup_size (const char *s, int size)
{
  char *new;

  new = gt1_new (char, size + 1);
  memcpy (new, s, size);
  new[size] = '\0';
  return new;
}

/* double the size of the hash table, rehashing as needed */
static void
gt1_name_context_double (Gt1NameContext *nc)
{
  int i, j;
  int oldsize, newmask;
  Gt1NameContextHashEntry *old_table, *new_table;

  oldsize = nc->table_size;
  old_table = nc->table;
  nc->table_size = oldsize << 1;
  newmask = nc->table_size - 1;
  new_table = gt1_new (Gt1NameContextHashEntry, nc->table_size);

  for (j = 0; j < nc->table_size; j++)
    new_table[j].name = NULL;

  for (i = 0; i < oldsize; i++)
    {
      if (old_table[i].name)
	{
	  for (j = gt1_name_context_hash_func(old_table[i].name);
	       new_table[j & newmask].name;
	       j++);
	  new_table[j & newmask] = old_table[i];
	}
    }

  gt1_free (old_table);
  nc->table = new_table;
}

/* Return the unique (to this name context) Gt1NameId for the given string,
   allocating a new one if necessary. */
Gt1NameId
gt1_name_context_intern (Gt1NameContext *nc, const char *name)
{
  int i;
  int mask;

  mask = nc->table_size - 1;
  for (i = gt1_name_context_hash_func (name); nc->table[i & mask].name; i++)
    if (!strcmp (nc->table[i & mask].name, name))
      return nc->table[i & mask].Gt1NameId;

  /* not found, allocate a new one */
  if (nc->num_entries >= nc->table_size >> 1)
    {
      gt1_name_context_double (nc);
      mask = nc->table_size - 1;
      for (i = gt1_name_context_hash_func (name); nc->table[i & mask].name; i++);
    }

  i &= mask;
  nc->table[i].name = gt1_name_context_strdup (name);
  nc->table[i].Gt1NameId = nc->num_entries;

  return nc->num_entries++;
}
/* Return the unique (to this name context) Gt1NameId for the given string,
   return GT1_UNKNOWN if not found*/
Gt1NameId gt1_name_context_interned (Gt1NameContext *nc, const char *name)
{
  int i;
  int mask;

  mask = nc->table_size - 1;
  for (i = gt1_name_context_hash_func (name); nc->table[i & mask].name; i++)
    if (!strcmp (nc->table[i & mask].name, name))
      return nc->table[i & mask].Gt1NameId;

  return GT1_UNKNOWN;
}

/* Return the unique (to this name context) Gt1NameId for the given
   string, allocating a new one if necessary. The string is not
   necessarily null-terminated; the size is given explicitly. */
Gt1NameId
gt1_name_context_intern_size (Gt1NameContext *nc, const char *name, int size)
{
  int i;
  int mask;

  mask = nc->table_size - 1;
  for (i = gt1_name_context_hash_func_size (name, size);
       nc->table[i & mask].name;
       i++)
    if (gt1_name_context_streq_size (nc->table[i & mask].name, name, size))
      return nc->table[i & mask].Gt1NameId;

  /* not found, allocate a new one */
  if (nc->num_entries >= nc->table_size >> 1)
    {
      gt1_name_context_double (nc);
      mask = nc->table_size - 1;
      for (i = gt1_name_context_hash_func_size (name, size);
	   nc->table[i & mask].name;
	   i++);
    }

  i &= mask;
  nc->table[i].name = gt1_name_context_strdup_size (name, size);
  nc->table[i].Gt1NameId = nc->num_entries;

  return nc->num_entries++;
}

/* This one is slow - it's intended for debugging only */
char *
gt1_name_context_string (Gt1NameContext *nc, Gt1NameId Gt1NameId)
{
  int j;

  for (j = 0; j < nc->table_size; j++)
    if (nc->table[j].name && nc->table[j].Gt1NameId == Gt1NameId)
      return nc->table[j].name;

  return NULL;
}
