/*
 * fcs_hash.c - an implementation of a simplistic (keys only) hash. This
 * hash uses chaining and re-hashing and was found to be very fast. Not all
 * of the functions of the hash ADT are implemented, but it is useful enough
 * for Freecell Solver.
 *
 * Written by Shlomi Fish (shlomif@vipe.technion.ac.il), 2000
 *
 * This file is in the public domain (it's uncopyrighted).
 */

#include "fcs_config.h"

#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_INTERNAL_HASH) || (defined(INDIRECT_STACK_STATES) && (FCS_STACK_STORAGE == FCS_STACK_STORAGE_INTERNAL_HASH))

#include <stdlib.h>
#include <string.h>

#define DEBUG

#ifdef DEBUG
#include <stdio.h>
#endif

#include "fcs_hash.h"

#include "alloc.h"

#ifdef DMALLOC
#include "dmalloc.h"
#endif

static void SFO_hash_rehash(SFO_hash_t * hash);



SFO_hash_t * freecell_solver_hash_init(
    SFO_hash_value_t wanted_size,
    int (*compare_function)(const void * key1, const void * key2, void * context),
    void * context
    )
{
    int size;
    SFO_hash_t * hash;

    /* Find a prime number that is greater than the initial wanted size */
    size = 256;
    while (size < wanted_size)
    {
        size <<= 1;
    }

    hash = (SFO_hash_t *)malloc(sizeof(SFO_hash_t));

    hash->size = size;
    hash->size_bitmask = size-1;

    hash->num_elems = 0;

    /* Allocate a table of size entries */
    hash->entries = (SFO_hash_symlink_t *)malloc(
        sizeof(SFO_hash_symlink_t) * size
        );

    hash->compare_function = compare_function;
    hash->context = context;

    /* Initialize all the cells of the hash table to NULL, which indicate
       that the cork of the linked list is right at the start */
    memset(hash->entries, 0, sizeof(SFO_hash_symlink_t)*size);

    hash->allocator = freecell_solver_compact_allocator_new();

    return hash;
}

void * freecell_solver_hash_insert(
    SFO_hash_t * hash,
    void * key,
    SFO_hash_value_t hash_value,
    SFO_hash_value_t secondary_hash_value,
    int optimize_for_caching
    )
{
    int place;
    SFO_hash_symlink_t * list;
    SFO_hash_symlink_item_t * item, * last_item;

    /* Get the index of the appropriate chain in the hash table */
    place = hash_value & (hash->size_bitmask);

    list = &(hash->entries[place]);
    /* If first_item is non-existent */
    if (list->first_item == NULL)
    {
        /* Allocate a first item with that key */
        fcs_compact_alloc_into_var(item, hash->allocator, SFO_hash_symlink_item_t);
        list->first_item = item;
        item->next = NULL;
        item->key = key;
        item->hash_value = hash_value;
        item->secondary_hash_value = secondary_hash_value;

        goto rehash_check;
    }

    /* Initialize item to the chain's first_item */
    item = list->first_item;
    last_item = NULL;

    while (item != NULL)
    {
        /*
            We first compare the hash values, because it is faster than
            comparing the entire data structure.

        */
        if (
            (item->hash_value == hash_value) &&
            (item->secondary_hash_value == secondary_hash_value) &&
            (!(hash->compare_function(item->key, key, hash->context)))
           )
        {
            if (optimize_for_caching)
            {
                /*
                 * Place the item in the beginning of the chain.
                 * If last_item == NULL it is already the first item so leave
                 * it alone
                 * */
                if (last_item != NULL)
                {
                    last_item->next = item->next;
                    item->next = list->first_item;
                    list->first_item = item;
                }
            }
            return item->key;
        }
        /* Cache the item before the current in last_item */
        last_item = item;
        /* Move to the next item */
        item = item->next;
    }

    if (optimize_for_caching)
    {
        /* Put the new element at the beginning of the list */
        fcs_compact_alloc_into_var(item, hash->allocator, SFO_hash_symlink_item_t);
        item->next = list->first_item;
        item->key = key;
        item->hash_value = hash_value;
        list->first_item = item;
        item->secondary_hash_value = secondary_hash_value;
    }
    else
    {
        /* Put the new element at the end of the list */
        fcs_compact_alloc_into_var(item, hash->allocator, SFO_hash_symlink_item_t);
        last_item->next = item;
        item->next = NULL;
        item->key = key;
        item->hash_value = hash_value;
        item->secondary_hash_value = secondary_hash_value;
    }

rehash_check:

    hash->num_elems++;

    if (hash->num_elems > ((hash->size*3)>>2))
    {
        SFO_hash_rehash(hash);
    }

    return NULL;
}

void freecell_solver_hash_free_with_callback(
    SFO_hash_t * hash,
    void (*function_ptr)(void * key, void * context)
    )
{
    int i;
    SFO_hash_symlink_item_t * item, * next_item;

    for(i=0;i<hash->size;i++)
    {
        item = hash->entries[i].first_item;
        while (item != NULL)
        {
            function_ptr(item->key, hash->context);
            next_item = item->next;

            item = next_item;
        }
    }

    freecell_solver_hash_free(hash);
}

void freecell_solver_hash_free(
    SFO_hash_t * hash
    )
{
    freecell_solver_compact_allocator_finish(hash->allocator);

    free(hash->entries);

    free(hash);
}


/*
    This function "rehashes" a hash. I.e: it increases the size of its
    hash table, allowing for smaller chains, and faster lookup.

  */
static void SFO_hash_rehash(
    SFO_hash_t * hash
    )
{
    int old_size, new_size, new_size_bitmask;
    int i;
#if 0
    SFO_hash_t * new_hash;
#endif
    SFO_hash_symlink_item_t * item, * next_item;
    int place;
    SFO_hash_symlink_t * new_entries;

    old_size = hash->size;

#if 0
    /* Allocate a new hash with hash_init() */
    new_hash = freecell_solver_hash_init_proto(
        old_size * 2,
        hash->compare_function,
        hash->context
        );
#endif    

    old_size = hash->size;
    new_size = old_size << 1;
    new_size_bitmask = new_size - 1;

    new_entries = calloc(new_size, sizeof(SFO_hash_symlink_t));

    /* Copy the items to the new hash while not allocating them again */
    for(i=0;i<old_size;i++)
    {
        item = hash->entries[i].first_item;
        /* traverse the chain item by item */
        while(item != NULL)
        {
            /* The place in the new hash table */
            place = item->hash_value & new_size_bitmask;

            /* Store the next item in the linked list in a safe place,
               so we can retrieve it after the assignment */
            next_item = item->next;
            /* It is placed in front of the first element in the chain,
               so it should link to it */
            item->next = new_entries[place].first_item;

            /* Make it the first item in its chain */
            new_entries[place].first_item = item;

            /* Move to the next item this one. */
            item = next_item;
        }
    };

    /* Free the entries of the old hash */
    free(hash->entries);

    /* Copy the new hash to the old one */
#if 0
    *hash = *new_hash;
#endif
    hash->entries = new_entries;
    hash->size = new_size;
    hash->size_bitmask = new_size_bitmask;
}

#else

/* ANSI C doesn't allow empty compilation */
static void freecell_solver_hash_c_dummy(); 

#endif /* (FCS_STATE_STORAGE == FCS_STATE_STORAGE_INTERNAL_HASH) || defined(INDIRECT_STACK_STATES) */
