/* 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/str.h"
#include "libawk/associative.h"


/* __STDC__ prototypes for static functions */
static t_ulong hash_key (t_uchar * key);
static int str_key_eq (void * va, void * vb, struct hashtree_rules * r);
static void pointer_map_hashfree_fn (struct hashtree_item * it, struct hashtree_rules * rules);



static struct hashtree_rules assoc_rules = { str_key_eq, 0, 0, 0, 0, 0 };
struct hashtree_breakaround {
    struct hashtree_rules rules;
    void (*free) (void *);
};


/************************************************************************
 *(h1 "Associative Tables")
 * 
 * Tla makes heavy use of a simple data structure for storing an
 * associative array of keys and value which are string: the
 * `assoc_table' type.
 * 
 * In general, these functions will cause the process to exit with 
 * non-0 status and a error message to the standard error descriptor
 * if an allocation failure occurs.
 */
/*(menu)
 */


/************************************************************************
 *(h1 "Associative Table Types")
 * 
 * Associative tables should be declared to be of type `assoc_table' and
 * initialized to 0, as in:
 *  
 *    assoc_table assoc = 0;
 */

/*(c assoc_table :category type)
 * typedef <unspecified> assoc_table;
 * 
 * An associative table.   Although the definition of the typedef
 * is unspecified, `assoc_table' values should be initialized to 0.
 */


/************************************************************************
 *(h1 "Associative Table Functions")
 * 
 */

static void free (void *foo)
{
    lim_free (0, foo);
}

/*(c assoc_set)
 * void assoc_set (assoc_table * table,
 *                 t_uchar const * key,
 *                 t_uchar const * value);
 * 
 * Record a binding of `key' to `value' in `*table'.
 * 
 * The value of `*table' might change as a result.
 * 
 * New copies of `key' and `value' are allocated for the
 * table.
 */
void
assoc_set (assoc_table * vtable,
           t_uchar const * key,
           t_uchar const * value)
{
  pointer_map_insert ((pointer_map *)vtable, key, str_save (0, value), free);
}

/**
 * \brief insert a value into a mapping
 *
 * the value of mapping may change as a result
 * \param key - copied if needed
 * \param value the value to insert - ownership is transferred
 */
void
pointer_map_insert (pointer_map * vtable,
           t_uchar const * key,
           void * value,
	   void (*free_callback) (void *))
{
  struct hashtree * table;
  struct hashtree_item * item;

  table = *(struct hashtree **)vtable;

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

  item = hashtree_store (table, hash_key ((t_uchar *)key), (void *)key, &assoc_rules);
  if (item->key == key)
    {
      item->key = str_save (0, key);
    }

  if (item->binding)
    free_callback (item->binding);

  item->binding = value;
}


/*(c assoc_ref)
 * t_uchar * assoc_ref (assoc_table table,
 *                      t_uchar * key);
 * 
 * Return the value (if any, 0 otherwise) associated
 * with `key' in `table'.
 * 
 * The value retured is _not_ newly allocated.  Callers 
 * should neither free the value, nor presume that it 
 * will remain value after later `assoc_set' or `assoc_del'
 * operations on this table.
 */
t_uchar *
assoc_ref (assoc_table vtable,
           t_uchar const * key)
{
    return pointer_map_find ((pointer_map) vtable, key);
}

/**
 * \brief find a pointer in a map
 * \return NULL on failure (or if the pointer is NULL - don't put NULL in ;)
 */
void *
pointer_map_find (pointer_map vtable, t_uchar const * key)
{
  struct hashtree * table;
  struct hashtree_item * item;

  table = (struct hashtree *)vtable;

  if (!table)
    return 0;

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

  if (!item)
    return 0;

  return item->binding;
}


/*(c assoc_del)
 * void assoc_del (assoc_table table,
 *                 t_uchar * key);
 * 
 * Remove the binding of `key' (if any) in `table'.
 */
void
assoc_del (assoc_table vtable,
           t_uchar * key)
{
    pointer_map_remove ((pointer_map) vtable, key, free);
}

/**
 * \brief remove a mapping
 */
void
pointer_map_remove (pointer_map vtable, t_uchar const * key, void (*free_callback) (void *))
{
  struct hashtree * table;
  struct hashtree_item * item;

  table = (struct hashtree *)vtable;

  if (!table)
    return;

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

  if (!item)
    return;

  if (item->key)
    lim_free (0, item->key);

  if (item->binding)
    free_callback (item->binding);

  hashtree_delete (item, &assoc_rules);
}


/*(c free_assoc_table)
 * void free_assoc_table (assoc_table table);
 * 
 * Free all storage associated with `table'.
 */
void
free_assoc_table (assoc_table table)
{
    pointer_map_free ((pointer_map) table, free);
}

/**
 * \brief free a map, calling free_callback on each pointer
 */
void
pointer_map_free (pointer_map table, void (*callback) (void *))
{
  struct hashtree_breakaround breakaround;
  breakaround.rules = assoc_rules;
  breakaround.free = callback;
  if (table)
    hashtree_free ((struct hashtree *)table, pointer_map_hashfree_fn, &breakaround.rules);
}




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

  hash = 0;

  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
str_key_eq (void * va, void * vb, struct hashtree_rules * r)
{
  t_uchar * a;
  t_uchar * b;

  a = (t_uchar *)va;
  b = (t_uchar *)vb;

  return !str_cmp (a, b);
}

static void
pointer_map_hashfree_fn (struct hashtree_item * it, struct hashtree_rules * rules)
{
  struct hashtree_breakaround *breakaround = (struct hashtree_breakaround *)rules;
  if (it->key)
    lim_free (0, it->key);
  breakaround->free (it->binding);
}

struct intersection_state
{
    assoc_table right;
    rel_table answer;
};

static void
assoc_visit_intersection (struct hashtree_item * it,
			  void *user_data,
			  struct hashtree_rules * rules)
{
    struct intersection_state *state = (struct intersection_state *) user_data;
    if (assoc_ref (state->right, it->key))
      {
	rel_add_records (&state->answer, rel_make_record (it->key, 0), 0);
      }
}

rel_table
assoc_intersection(assoc_table left, assoc_table right)
{
    struct hashtree *h_left;
    struct intersection_state state;
    h_left = (struct hashtree *)left;
    state.right = right;
    state.answer = 0;
    hashtree_fold (h_left, assoc_visit_intersection, &state, &assoc_rules);
    return state.answer;
}


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


syntax highlighted by Code2HTML, v. 0.9.1