/*
//
// varstore.c
//
// Variable store functions (implemented with a simple hash table)
//
// Copyright (c) 1995-96 Jim Nelson.  Permission to distribute
// granted by the author.  No warranties are made on the fitness of this
// source code.
// Amiga version - 1997 - Geert Bevin
//
*/

#include "htp.h"

/* statistics for performance measurement */
#if DEBUG
uint variableLookups = 0;
uint variableCacheHits = 0;
uint variableStringCompares = 0;
uint variableMissedStringCompares = 0;
uint variableHashCalcs = 0;
#endif

/* !! dont primes contribute to a more evenly distributed hash table? */
/* need to check Knuth ... */
#define HASH_TABLE_SIZE             (53)

/*
// generates an index into the hash table from the string
*/
uint StringToHashTableIndex(const char *name)
{
    uint hash;
    uint ctr;

    if(name == NULL)
    {
        return 0;
    }

    #if DEBUG
    variableHashCalcs++;
    #endif

    hash = 0;
    ctr = 0;
    while(*name != NUL)
    {
        /* uppercase the chars before hashing because names are case-insensitive */
        /* shift left to give a broader distribution in the hash table */
        hash += (uint) toupper(*name++) << (ctr++ & 0x03);
    }

    return (hash % HASH_TABLE_SIZE);
}

BOOL InitializeVariableStore(VARSTORE *varstore)
{
    assert(varstore != NULL);

    /* allocate the hash table */
    varstore->variableHashTable = AllocMemory(sizeof(VARIABLE *) * HASH_TABLE_SIZE);
    if(varstore->variableHashTable == NULL)
    {
        DEBUG_PRINT(("out of memory trying to allocate variable hash table"));
        return FALSE;
    }
    memset(varstore->variableHashTable, 0, sizeof(VARIABLE *) * HASH_TABLE_SIZE);

    /* initialize the rest of the store */
    varstore->parent = NULL;
    varstore->child = NULL;
    varstore->lastVariable = NULL;

    return TRUE;
}

void DestroyVariableStore(VARSTORE *varstore)
{
    /* destroy all children as well ... this function doesn't simply "unlink" */
    /* the current store from the linked list */
    while(varstore != NULL)
    {
        /* unlink from parent */
        if(varstore->parent != NULL)
        {
            varstore->parent->child = NULL;
            varstore->parent = NULL;
        }

        /* destroy all variables in this store */
        ClearVariableList(varstore);

        /* free the hash table memory */
        FreeMemory(varstore->variableHashTable);
        varstore->variableHashTable = NULL;

        /* destroy all child scopes as well */
        varstore = varstore->child;
    }
}

BOOL StoreVariable(VARSTORE *varstore, const char *name, const char *value,
    uint type, uint flag, void *param, VAR_DESTRUCTOR destructor)
{
    VARIABLE *variable;
    VARIABLE *ptr;
    uint index;

    assert(varstore != NULL);
    assert(name != NULL);

    /* remove any variable with the same name in the store */
    RemoveVariable(varstore, name);

    /* allocate space for the variable structure */
    if((variable = AllocMemory(sizeof(VARIABLE))) == NULL)
    {
        DEBUG_PRINT(("unable to allocate memory for new variable"));
        return FALSE;
    }

    /* copy in the variable's name */
    StringCopy(variable->name, name, MAX_VARNAME_LEN);

    /* copy in the variable's value, if any */
    if(value != NULL)
    {
        StringCopy(variable->value, value, MAX_VARVALUE_LEN);
    }

    /* set flags and destructor pointer */
    variable->type = type;
    variable->flag = flag;
    variable->param = param;
    variable->destructor = destructor;
    variable->next = NULL;

    /* insert into the hash table */
    index = StringToHashTableIndex(name);
    ptr = varstore->variableHashTable[index];
    varstore->variableHashTable[index] = variable;
    if(ptr != NULL)
    {
        /* the slot has been previously hashed to a different variable, */
        /* inserted new item at beginning of list, add rest to end */
        variable->next = ptr;
    }

    /* put this new variable into the last-used cache */
    varstore->lastVariable = variable;

    return TRUE;
}   

static VARIABLE *FindVariable(VARSTORE *varstore, const char *name)
{
    VARIABLE *ptr;

    assert(varstore != NULL);
    assert(name != NULL);

    #if DEBUG
    variableLookups++;
    #endif

    /* check the last referenced variable first */
    if(varstore->lastVariable != NULL)
    {
        #if DEBUG
        variableStringCompares++;
        #endif

        if(stricmp(varstore->lastVariable->name, name) == 0)
        {
            #if DEBUG
            variableCacheHits++;
            #endif

            return varstore->lastVariable;
        }

        #if DEBUG
        variableMissedStringCompares++;
        #endif
    }

    ptr = varstore->variableHashTable[StringToHashTableIndex(name)];
    while(ptr != NULL)
    {
        #if DEBUG
        variableStringCompares++;
        #endif

        if(stricmp(ptr->name, name) == 0)
        {
            /* cache this variable for next time */
            varstore->lastVariable = ptr;

            return ptr;
        }

        #if DEBUG
        variableMissedStringCompares++;
        #endif

        ptr = ptr->next;
    }

    /* check all parent stores as well */
    if(varstore->parent != NULL)
    {
        return FindVariable(varstore->parent, name);
    }

    return NULL;
}   

const char *GetVariableValue(VARSTORE *varstore, const char *name)
{
    VARIABLE *variable;

    if(name != NULL)
    {
        if((variable = FindVariable(varstore, name)) != NULL)
        {
            return variable->value;
        }
    }

    return NULL;
}   

uint GetVariableType(VARSTORE *varstore, const char *name)
{
    VARIABLE *variable;

    if(name != NULL)
    {
        if((variable = FindVariable(varstore, name)) != NULL)
        {
            return variable->type;
        }
    }

    return VAR_TYPE_UNKNOWN;
}

uint GetVariableFlag(VARSTORE *varstore, const char *name)
{
    VARIABLE *variable;

    if(name != NULL)
    {
        if((variable = FindVariable(varstore, name)) != NULL)
        {
            return variable->flag;
        }
    }

    return VAR_FLAG_UNKNOWN;
}   

static void DestroyVariable(VARSTORE *varstore, VARIABLE *variable)
{
    /* if the variable has a destructor callback, do it */
    if(variable->destructor != NULL)
    {
        variable->destructor(variable->name, variable->value, variable->type,
            variable->flag, variable->param);
    }

    /* if this is the last referenced variable, uncache it */
    if(varstore->lastVariable == variable)
    {
        varstore->lastVariable = NULL;
    }

    /* free the structure */
    FreeMemory(variable);
}

void ClearVariableList(VARSTORE *varstore)
{
    VARIABLE *ptr;
    VARIABLE *next;
    uint ctr;

    assert(varstore != NULL);

    /* have to walk the entire hash table to destroy each item */
    for(ctr = 0; ctr < HASH_TABLE_SIZE; ctr++)
    {
        ptr = varstore->variableHashTable[ctr];
        while(ptr != NULL)
        {
            next = ptr->next;
            DestroyVariable(varstore, ptr);
            ptr = next;
        }
        varstore->variableHashTable[ctr] = NULL;
    }
}   

BOOL RemoveVariable(VARSTORE *varstore, const char *name)
{
    VARIABLE *ptr;
    VARIABLE *prev;
    uint index;

    assert(varstore != NULL);

    if(name == NULL)
    {
        return FALSE;
    }

    index = StringToHashTableIndex(name);

    prev = NULL;
    ptr = varstore->variableHashTable[index];
    while(ptr != NULL)
    {
        #if DEBUG
        variableStringCompares++;
        #endif

        if(stricmp(ptr->name, name) == 0)
        {
            /* found the variable */
            /* unlink from the list */
            if(prev != NULL)
            {
                prev->next = ptr->next;
            }
            else
            {
                varstore->variableHashTable[index] = ptr->next;
            }

            /* destroy the variable structure */
            DestroyVariable(varstore, ptr);

            return TRUE;
        }

        prev = ptr;
        ptr = ptr->next;
    }

    return FALSE;
}

BOOL VariableExists(VARSTORE *varstore, const char *name)
{
    return (FindVariable(varstore, name) != NULL) ? TRUE : FALSE;
}

void *GetVariableParam(VARSTORE *varstore, const char *name)
{
    VARIABLE *var;

    if(name != NULL)
    {
        if((var = FindVariable(varstore, name)) != NULL)
        {
            return var->param;
        }
    }

    return NULL;
}

static VARSTORE *FindTopmostContext(VARSTORE *varstore)
{
    VARSTORE *ptr;

    assert(varstore != NULL);

    ptr = varstore;
    while(ptr->child != NULL)
    {
        assert(ptr->child->parent == ptr);
        ptr = ptr->child;
    }

    return ptr;
}

void PushVariableStoreContext(VARSTORE *parent, VARSTORE *varstore)
{
    VARSTORE *ptr;

    assert(parent != NULL);
    assert(varstore != NULL);
    assert(varstore->parent == NULL);

    ptr = FindTopmostContext(parent);

    ptr->child = varstore;
    varstore->parent = ptr;
    varstore->child = NULL;
}

VARSTORE *PopVariableStoreContext(VARSTORE *varstore)
{
    VARSTORE *ptr;

    assert(varstore != NULL);

    ptr = FindTopmostContext(varstore);

    /* unlink from parent and return current context */
    ptr->parent->child = NULL;
    ptr->parent = NULL;

    return ptr;
}

VARSTORE *PeekVariableStoreContext(VARSTORE *varstore)
{
    return FindTopmostContext(varstore);
}

