/* associative.c:
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/hash/hashtree.h"
#include "hackerlab/hash/hash-utils.h"
#include "hackerlab/char/dstr.h"
#include "hackerlab/char/str.h"
#include "tla/libawk/associative.h"


/* __STDC__ prototypes for static functions */
static t_ulong hash_key (rel_field k);
static int dstr_key_eq (void * va, void * vb, struct hashtree_rules * r);
static struct hashtree_item * dstr_item_alloc (void * vkey, struct hashtree_rules * rules);
static void assoc_table_hashfree_fn (struct hashtree_item * it, struct hashtree_rules * rules);



static struct hashtree_rules assoc_rules = { dstr_key_eq, 0, 0, dstr_item_alloc, 0, 0 };



void
assoc_set_taking (assoc_table * vtable,
                  rel_field key,
                  rel_field value)
{
  struct hashtree * table;
  struct hashtree_item * item;

  table = *(struct hashtree **)vtable;

  if (!table)
    {
      table = hashtree_alloc (&assoc_rules);
      *vtable = (assoc_table)table;
    }

  item = hashtree_store (table, hash_key (key), (void *)key._d, &assoc_rules);
  dstr_unref ((t_dstr)item->key);
  item->key = (void *)key._d;
  dstr_unref ((t_dstr)item->binding);
  item->binding = (void *)value._d;
}


rel_field
assoc_get_taking (assoc_table vtable,
                  rel_field key)
{
  struct hashtree * table;
  struct hashtree_item * item;
  rel_field answer;

  table = (struct hashtree *)vtable;

  if (!table)
    {
      dstr_unref (key._d);
      return rel_field_nil;
    }

  item = hashtree_find (table, hash_key (key), (void *)key._d, &assoc_rules);

  if (!item)
    {
      dstr_unref (key._d);
      return rel_field_nil;
    }

  if (key._d == (t_dstr)item->key)
    {
      dstr_unref (key._d);
    }
  else
    {
      if ((unsigned long)key._d < (unsigned long)(t_dstr)item->key)
        {
          dstr_unref ((t_dstr)item->key);
          item->key = key._d;
        }
      else
        {
          dstr_unref (key._d);
        }
    }

  answer._d = (t_dstr) item->binding;
  dstr_ref (answer._d);

  return answer;
}


const t_uchar *
assoc_get_str_taking (assoc_table vtable,
                      rel_field key)
{
  rel_field f;
  const t_uchar * answer;

  f = assoc_get_taking (vtable, key);
  answer = rel_field_str (f);
  dstr_unref (f._d);  
  return answer;
}


void
assoc_del_taking (assoc_table vtable,
                  rel_field key)
{
  struct hashtree * table;
  struct hashtree_item * item;

  table = (struct hashtree *)vtable;

  if (!table)
    return;

  item = hashtree_find (table, hash_key (key), (void *)key._d, &assoc_rules);

  dstr_unref (key._d);

  if (!item)
    return;

  dstr_unref ((t_dstr)item->key);
  dstr_unref ((t_dstr)item->binding);

  hashtree_delete (item, &assoc_rules);
}


void
free_assoc_table (assoc_table table)
{
  if (table)
    hashtree_free ((struct hashtree *)table, assoc_table_hashfree_fn, &assoc_rules);
}




static t_ulong
hash_key (rel_field k)
{
 /* From GNU Emacs via Stephen Turnbull.   Fair use?
  * (GPL, anyway....)
  */
  t_uchar c;
  t_ulong hash;
  const t_uchar * key;

  hash = 0;

  key = dstr_data (0, k._d);

  if (!key)
    return 0xde7a115L;

  while (*key)
    {
      c = *key;
      ++key;

      if (c >= 0140)
        c -= 40;                /* I dunno why either -tl */

      hash = ((hash << 3) + (hash >> 28) + c);
    }

  return hash;
}


static int
dstr_key_eq (void * va, void * vb, struct hashtree_rules * r)
{
  t_dstr a;
  t_dstr b;

  a = (t_dstr)va;
  b = (t_dstr)vb;

  return !dstr_cmp (a, b);
}

static struct hashtree_item *
dstr_item_alloc (void * vkey, struct hashtree_rules * rules)
{
  t_dstr key;
  struct hashtree_item * it;

  key = (t_dstr)vkey;

  it = (struct hashtree_item *)lim_malloc (0, sizeof (*it));
  dstr_ref (key);
  it->key = (void *)key;
  it->binding = 0;
  return it;
}

static void
assoc_table_hashfree_fn (struct hashtree_item * it, struct hashtree_rules * rules)
{
  dstr_unref ((t_dstr)it->key);
  dstr_unref ((t_dstr)it->binding);
}


/* tag: Tom Lord Mon May  5 17:29:01 2003 (associative.c)
 */
