/*
// Full copyright information is available in the file ../doc/CREDITS
*/

#include "defs.h"
#include "quickhash.h"

#define MALLOC_DELTA			 0
#define HASHTAB_STARTING_SIZE		 128

INTERNAL void double_hashtab_size(Hash * hash);
INTERNAL void insert_key(Hash * hash, Int i);

Hash *hash_new_with(cList * keys)
{
    Hash *cnew;
    Int i, j;

    /* Construct a new hash */
    cnew = EMALLOC(Hash, 1);
    cnew->keys = list_dup(keys);

    /* Calculate initial size of chain and hash table. */
    cnew->hashtab_size = HASHTAB_STARTING_SIZE;
    while (cnew->hashtab_size < keys->len)
        cnew->hashtab_size = cnew->hashtab_size * 2 + MALLOC_DELTA;

    /* Initialize chain entries and hash table. */
    cnew->links = EMALLOC(Int, cnew->hashtab_size);
    cnew->hashtab = EMALLOC(Int, cnew->hashtab_size);
    for (i = 0; i < cnew->hashtab_size; i++) {
        cnew->links[i] = -1;
        cnew->hashtab[i] = -1;
    }

    /* Insert the keys into the hash table, eliminating duplicates. */
    i = j = 0;
    while (i < cnew->keys->len) {
        if (i != j)
            cnew->keys->el[j] = cnew->keys->el[i];
        if (hash_find(cnew, &keys->el[i]) == F_FAILURE)
            insert_key(cnew, j++);
        else
            data_discard(&cnew->keys->el[i]);
        i++;
    }
    cnew->keys->len = j;

    cnew->refs = 1;
    return cnew;
}

Hash *hash_new(int size)
{
    cList *keys;
    Hash *out;

    keys = list_new(size);
    out = hash_new_with(keys);
    list_discard(keys);
    return out;
}

Hash *hash_dup(Hash * hash)
{
    hash->refs++;
    return hash;
}

void hash_discard(Hash * hash)
{
    hash->refs--;
    if (!hash->refs) {
        list_discard(hash->keys);
        efree(hash->links);
        efree(hash->hashtab);
        efree(hash);
    }
}

Hash *hash_add(Hash * hash, cData * key)
{
    Int pos;

#if 0
    hash = hash_prep(hash);
#endif

    pos = hash_find(hash, key);
    if (pos != F_FAILURE)
        return hash;

    /* Add the key */
    hash->keys = list_add(hash->keys, key);

    /* Check if we should resize the hash table. */
    if (hash->keys->len > hash->hashtab_size)
        double_hashtab_size(hash);
    else
        insert_key(hash, hash->keys->len - 1);
    return hash;
}

INTERNAL void insert_key(Hash * hash, Int i)
{
    Int ind;

    ind = data_hash(&hash->keys->el[i]) % hash->hashtab_size;
    hash->links[i] = hash->hashtab[ind];
    hash->hashtab[ind] = i;
}

Int hash_find(Hash * hash, cData * key)
{
    Int ind, i;

    ind = data_hash(key) % hash->hashtab_size;
    for (i = hash->hashtab[ind]; i != -1; i = hash->links[i]) {
        if (data_cmp(&hash->keys->el[i], key) == 0)
            return i;
    }

    return F_FAILURE;
}

INTERNAL void double_hashtab_size(Hash * hash)
{
    Int i;

    hash->hashtab_size = hash->hashtab_size * 2 + MALLOC_DELTA;
    hash->links = EREALLOC(hash->links, Int, hash->hashtab_size);
    hash->hashtab = EREALLOC(hash->hashtab, Int, hash->hashtab_size);
    for (i = 0; i < hash->hashtab_size; i++) {
        hash->links[i] = -1;
        hash->hashtab[i] = -1;
    }
    for (i = 0; i < hash->keys->len; i++)
        insert_key(hash, i);
}
