//
// tardy - a tar post-processor
// Copyright (C) 1998, 1999, 2002, 2008, 2009, 2011 Peter Miller
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//

#include <libtardy/ac/stdio.h>
#include <libexplain/output.h>

#include <libtardy/fstrcmp.h>
#include <libtardy/symtab.h>


symtab::symtab()
{
    chain = 0;
    reap = 0;
    hash_modulus = 1 << 4; // MUST be a power of 2
    hash_load = 0;
    hash_table = new row * [hash_modulus];
    for (str_hash_ty j = 0; j < hash_modulus; ++j)
        hash_table[j] = 0;
}


symtab::~symtab()
{
    for (str_hash_ty j = 0; j < hash_modulus; ++j)
    {
        row **rpp = &hash_table[j];
        while (*rpp)
        {
            row *rp = *rpp;
            *rpp = rp->overflow;
            if (reap)
                reap(rp->data);
            delete rp;
        }
    }
    delete [] hash_table;
    hash_table = 0;
    hash_modulus = 0;
}


//
// NAME
//      split - reduce symbol table load
//
// SYNOPSIS
//      void split(symtab_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.
//

void
symtab::split(void)
{
    size_t om = hash_modulus;

    //
    // increase the modulus by one order of 2-magnitude
    // sigma(2**-n) == 1
    //
    {
        size_t new_hash_modulus = 2 * hash_modulus;
        row **new_hash_table = new row * [new_hash_modulus];
        for (size_t j = 0; j < hash_modulus; ++j)
            new_hash_table[j] = hash_table[j];
        for (size_t j = hash_modulus; j < new_hash_modulus; ++j)
            new_hash_table[j] = 0;
        delete [] hash_table;
        hash_table = new_hash_table;
        hash_modulus = new_hash_modulus;
    }

    //
    // now redistribute the list elements
    //
    // 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!
    //
    size_t mask = hash_modulus - 1;
    for (size_t j = 0; j < om; ++j)
    {
        row *p = hash_table[j];
        hash_table[j] = 0;
        while (p)
        {
            row *p2 = p;
            p = p2->overflow;
            p2->overflow = 0;

            str_hash_ty index = p2->key.hash() & mask;
            row **ipp = &hash_table[index];
            for (; *ipp; ipp = &(*ipp)->overflow)
                ;
            *ipp = p2;
        }
    }
}


//
// NAME
//      symtab_query - search for a variable
//
// SYNOPSIS
//      int symtab_query(symtab_ty *, string_ty *key);
//
// DESCRIPTION
//      The symtab_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 *
symtab::query(const rcstring &key)
    const
{
    str_hash_ty mask = hash_modulus - 1;
    str_hash_ty index = key.hash() & mask;
    for (row *p = hash_table[index]; p; p = p->overflow)
    {
        if (key == p->key)
            return p->data;
    }
    return 0;
}


//
// NAME
//      symtab_query_fuzzy - search for a variable name
//
// SYNOPSIS
//      string_ty *symtab_query_fuzzy(symtab_ty *, string_ty *key);
//
// DESCRIPTION
//      The symtab_query_fuzzy function is used to search for a variable name.
//
// RETURNS
//      The closest match for the variable name given.
//      If no match is particularly close, it returns 0.
//

rcstring
symtab::query_fuzzy(const rcstring &key)
    const
{
    rcstring best_name;
    double best_weight = 0.6;
    for (str_hash_ty index = 0; index < hash_modulus; ++index)
    {
        for (row *p = hash_table[index]; p; p = p->overflow)
        {
            double weight =
                fmemcmp
                (
                    key.c_str(),
                    key.size(),
                    p->key.c_str(),
                    p->key.size()
                );
            if (weight > best_weight)
                best_name = p->key;
        }
    }
    return best_name;
}


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

void
symtab::assign(const rcstring &key, void *data)
{
    str_hash_ty mask = hash_modulus - 1;
    str_hash_ty index = key.hash() & mask;

    for (row *p = hash_table[index]; p; p = p->overflow)
    {
        if (key == p->key)
        {
            if (reap)
                reap(p->data);
            p->data = data;
            return;
        }
    }

    row *p = new row();
    p->key = key;
    p->overflow = hash_table[index];
    p->data = data;
    hash_table[index] = p;

    hash_load++;
    while (hash_load * 10 >= hash_modulus * 8)
        split();
}


//
// NAME
//      symtab_assign_push - assign a variable
//
// SYNOPSIS
//      void symtab_assign_push(symtab_ty *, string_ty *key, void *data);
//
// DESCRIPTION
//      The symtab_assign function is used to assign
//      a value to a given variable.
//      Any previous value will be obscured until this one
//      is deleted with symtab_delete.
//
// CAVEAT
//      The name is copied, the data is not.
//

void
symtab::assign_push(const rcstring &key, void *data)
{
    str_hash_ty mask = hash_modulus - 1;
    str_hash_ty index = key.hash() & mask;

    row *p = new row();
    p->key = key;
    p->overflow = hash_table[index];
    p->data = data;
    hash_table[index] = p;

    hash_load++;
    while (hash_load * 10 >= hash_modulus * 8)
        split();
}


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

void
symtab::remove(const rcstring &key)
{
    str_hash_ty mask = hash_modulus - 1;
    str_hash_ty index = key.hash() & mask;

    row **rpp = &hash_table[index];
    for (;;)
    {
        row *rp = *rpp;
        if (!rp)
            break;
        if (key == rp->key)
        {
            if (reap)
                reap(rp->data);
            *rpp = rp->overflow;
            delete rp;
            hash_load--;
            break;
        }
        rpp = &rp->overflow;
    }
}


//
// NAME
//      symtab_dump - dump id table
//
// SYNOPSIS
//      void symtab_dump(symtab_ty *stp, char *caption);
//
// DESCRIPTION
//      The symtab_dump function is used to dump the contents of the
//      symbol table.  The caption will be used to indicate why the
//      symbol table was dumped.
//
// CAVEAT
//      This function is only available when symbol DEBUG is defined.
//

void
symtab::dump(const char *caption)
    const
{
    explain_output_error("symbol table %s = {", caption);
    for (str_hash_ty j = 0; j < hash_modulus; ++j)
    {
        for (row *p = hash_table[j]; p; p = p->overflow)
        {
            explain_output_error
            (
                "key = \"%s\", data = %08lX",
                p->key.c_str(),
                (long)p->data
            );
        }
    }
    explain_output_error("}");
}


void
symtab::walk(void (*func)(const symtab &, const rcstring &, void *, void *),
    void *arg)
{
    for (str_hash_ty j = 0; j < hash_modulus; ++j)
        for (row *rp = hash_table[j]; rp; rp = rp->overflow)
            func(*this, rp->key, rp->data, arg);
}
