/*
 *	aegis - project change supervisor
 *	Copyright (C) 1998, 2002, 2003 Peter Miller;
 *	All rights reserved.
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program 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 General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: functions to manipulate integer indexed tables
 */

#include <error.h> /* for assert */
#include <itab.h>
#include <mem.h>
#include <trace.h>


/*
 * NAME
 *	itab_alloc
 *
 * SYNOPSIS
 *	itab_ty *itabLalloc(int);
 *
 * DESCRIPTION
 *	The itab_alloc function is used to allocate a new integer table
 *	instance in dynamic memory.
 *
 * RETURNS
 *	itab_ty *; pointer to table
 *
 * CAVEAT
 *	Use itab_free when you are done with it.
 */

itab_ty *
itab_alloc(int size)
{
    itab_ty	    *itp;
    itab_key_ty     j;

    trace(("itab_alloc(size = %d)\n{\n", size));
    itp = (itab_ty *)mem_alloc(sizeof(itab_ty));
    itp->reap = 0;
    itp->hash_modulus = 1 << 5; /* MUST be a power of 2 */
    itp->hash_mask = itp->hash_modulus - 1;
    itp->load = 0;
    itp->hash_table =
        (itab_row_ty **)mem_alloc(itp->hash_modulus * sizeof(itab_row_ty *));
    for (j = 0; j < itp->hash_modulus; ++j)
	itp->hash_table[j] = 0;
    trace(("return %08lX;\n", (long)itp));
    trace(("}\n"));
    return itp;
}


/*
 * NAME
 *	itab_free
 *
 * SYNOPSIS
 *	void itab_free(itab_ty *);
 *
 * DESCRIPTION
 *	The itab_free function is used to release the resources held by
 *	an integer table.
 */

void
itab_free(itab_ty *itp)
{
    itab_key_ty	j;

    trace(("itab_free(itp = %08lX)\n{\n", (long)itp));
    for (j = 0; j < itp->hash_modulus; ++j)
    {
	itab_row_ty	**rpp;

	rpp = &itp->hash_table[j];
	while (*rpp)
	{
	    itab_row_ty     *rp;

	    rp = *rpp;
	    *rpp = rp->overflow;
	    if (itp->reap)
	       	itp->reap(rp->data);
	    mem_free(rp);
	}
    }
    mem_free(itp->hash_table);
    mem_free(itp);
    trace(("}\n"));
}


/*
 * NAME
 *	split - reduce symbol table load
 *
 * SYNOPSIS
 *	void split(itab_ty);
 *
 * DESCRIPTION
 *	The split function is used to split symbols in the bucket indicated by
 *	the split point.  The symbols are split between that bucket and the one
 *	after the current end of the table.
 *
 * CAVEAT
 *	It is only sensable to do this when the symbol table load exceeds some
 *	reasonable threshold.  A threshold of 80% is suggested.
 */

static void
split(itab_ty *itp)
{
    itab_row_ty     **new_hash_table;
    itab_key_ty     new_hash_modulus;
    itab_key_ty     new_hash_mask;
    itab_key_ty     idx;

    trace(("split(itp = %08lX)\n{\n", (long)itp));

    /*
     * double the modulus
     *
     * This is subtle.  If we only increase the modulus by one, the
     * load always hovers around 80%, so we have to do a split for
     * every insert.  I.e. the malloc burden os O(n) for the lifetime of
     * the itab.  BUT if we double the modulus, the length of time
     * until the next split also doubles, making the probablity of a
     * split halve, and sigma(2**-n)=1, so the malloc burden becomes O(1)
     * for the lifetime of the itab.
     */
    new_hash_modulus = itp->hash_modulus * 2;
    new_hash_mask = new_hash_modulus - 1;
    new_hash_table =
        (itab_row_ty **)mem_alloc(new_hash_modulus * sizeof(itab_row_ty *));

    /*
     * now redistribute the list elements
     */
    for (idx = 0; idx < itp->hash_modulus; ++idx)
    {
	itab_row_ty     *p;

	new_hash_table[idx] = 0;
	new_hash_table[idx + itp->hash_modulus] = 0;

	p = itp->hash_table[idx];
	while (p)
	{
	    itab_row_ty     *p2;
	    itab_key_ty     index;
	    itab_row_ty     **ipp;

	    p2 = p;
	    p = p2->overflow;
	    p2->overflow = 0;

	    /*
	     * It is important to preserve the order of the links because
	     * they can be push-down stacks, and to simply add them to the
	     * head of the list will reverse the order of the stack!
	     */
	    assert((p2->key & itp->hash_mask) == idx);
	    index = p2->key & new_hash_mask;
	    for (ipp = &new_hash_table[index]; *ipp; ipp = &(*ipp)->overflow)
		;
	    *ipp = p2;
	}
    }
    mem_free(itp->hash_table);
    itp->hash_table = new_hash_table;
    itp->hash_modulus = new_hash_modulus;
    itp->hash_mask = new_hash_mask;
    trace(("}\n"));
}


/*
 * NAME
 *	itab_query - search for a variable
 *
 * SYNOPSIS
 *	int itab_query(itab_ty *, string_ty *key);
 *
 * DESCRIPTION
 *	The itab_query function is used to reference a variable.
 *
 * RETURNS
 *	If the variable has been defined, the function returns a non-zero value
 *	and the value is returned through the 'value' pointer.
 *	If the variable has not been defined, it returns zero,
 *	and 'value' is unaltered.
 */

void *
itab_query(itab_ty *itp, itab_key_ty key)
{
    itab_key_ty     index;
    itab_row_ty     *p;
    void	    *result;

    trace(("itab_query(itp = %08lX, key = %ld)\n{\n", (long)itp, (long)key));
    result = 0;
    index = key & itp->hash_mask;
    for (p = itp->hash_table[index]; p; p = p->overflow)
    {
	if (key == p->key)
	{
    	    result = p->data;
    	    break;
	}
    }
    trace(("return %08lX;\n", (long)result));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *	itab_assign - assign a variable
 *
 * SYNOPSIS
 *	void itab_assign(itab_ty *, string_ty *key, void *data);
 *
 * DESCRIPTION
 *	The itab_assign function is used to assign
 *	a value to a given variable.
 *
 * CAVEAT
 *	The name is copied, the data is not.
 */

void
itab_assign(itab_ty *itp, itab_key_ty key, void *data)
{
    itab_key_ty     index;
    itab_row_ty     *p;

    trace(("itab_assign(itp = %08lX, key = %ld, data = %08lX)\n{\n",
	(long)itp, (long)key, (long)data));
    index = key & itp->hash_mask;

    for (p = itp->hash_table[index]; p; p = p->overflow)
    {
	if (key == p->key)
	{
	    trace(("modify existing entry\n"));
	    if (itp->reap)
	       	itp->reap(p->data);
	    p->data = data;
	    goto done;
	}
    }

    trace(("new entry\n"));
    p = (itab_row_ty *)mem_alloc(sizeof(itab_row_ty));
    p->key = key;
    p->overflow = itp->hash_table[index];
    p->data = data;
    itp->hash_table[index] = p;

    itp->load++;
    if (itp->load * 10 >= itp->hash_modulus * 8)
	split(itp);
    done:
    trace(("}\n"));
}


/*
 * NAME
 *	itab_delete - delete a variable
 *
 * SYNOPSIS
 *	void itab_delete(string_ty *name, itab_class_ty class);
 *
 * DESCRIPTION
 *	The itab_delete function is used to delete variables.
 *
 * CAVEAT
 *	The name is freed, the data is reaped.
 *	(By default, reap does nothing.)
 */

void
itab_delete(itab_ty *itp, itab_key_ty key)
{
    itab_key_ty	index;
    itab_row_ty	**pp;

    trace(("itab_delete(itp = %08lX, key = %ld)\n{\n",
	(long)itp, (long)key));
    index = key & itp->hash_mask;

    pp = &itp->hash_table[index];
    for (;;)
    {
	itab_row_ty	*p;

	p = *pp;
	if (!p)
	    break;
	if (key == p->key)
	{
	    if (itp->reap)
	       	itp->reap(p->data);
	    *pp = p->overflow;
	    mem_free(p);
	    itp->load--;
	    break;
	}
	pp = &p->overflow;
    }
    trace(("}\n"));
}


/*
 * NAME
 *	itab_walk
 *
 * SYNOPSIS
 *	void itab_walk(itab_ty *, void (*)(itab_ty *, itab_key_ty, void *,
 *		void *), void *);
 *
 * DESCRIPTION
 *	The itab_walk function is used to visit each element of an
 *	integer table, in no particular order.
 */

void
itab_walk(itab_ty *itp, void (*func)(itab_ty *, itab_key_ty, void *, void *),
    void *arg)
{
    long	    j;
    itab_row_ty     *rp;

    trace(("itab_walk(itp = %08lX)\n{\n"/*}*/, (long)itp));
    for (j = 0; j < itp->hash_modulus; ++j)
       	for (rp = itp->hash_table[j]; rp; rp = rp->overflow)
	    func(itp, rp->key, rp->data, arg);
    trace((/*{*/"}\n"));
}
