/*
 * This file is part of the Score-P software (http://www.score-p.org)
 *
 * Copyright (c) 2009-2012,
 * RWTH Aachen University, Germany
 *
 * Copyright (c) 2009-2012,
 * Gesellschaft fuer numerische Simulation mbH Braunschweig, Germany
 *
 * Copyright (c) 2009-2012,
 * Technische Universitaet Dresden, Germany
 *
 * Copyright (c) 2009-2012,
 * University of Oregon, Eugene, USA
 *
 * Copyright (c) 2009-2012,
 * Forschungszentrum Juelich GmbH, Germany
 *
 * Copyright (c) 2009-2012,
 * German Research School for Simulation Sciences GmbH, Juelich/Aachen, Germany
 *
 * Copyright (c) 2009-2012,
 * Technische Universitaet Muenchen, Germany
 *
 * This software may be modified and distributed under the terms of
 * a BSD-style license.  See the COPYING file in the package base
 * directory for details.
 *
 */

/**
 * @file
 * @ingroup         otf2_hash_table_module
 *
 * @brief           Implementation of a STL-like hash table.
 */

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

#include <UTILS_Error.h>
#include <UTILS_Debug.h>

#include "otf2_hash_table.h"

/*
 * --------------------------------------------------------------------------
 * otf2_hash_table
 * --------------------------------------------------------------------------
 */

/*--- Type definitions ----------------------------------------------------*/

typedef struct otf2_hash_table_list_item_struct otf2_hash_table_list_item;

/* Collision list entry */
struct otf2_hash_table_list_item_struct
{
    otf2_hash_table_entry      entry;          /* Table entry (key, value) */
    size_t                     hash_value;     /* hash value for entry.key, for fast comparison */
    otf2_hash_table_list_item* next_collision; /* Pointer to next entry in collision list */
    otf2_hash_table_list_item* next_insertion; /* Pointer to next entry in insertion order */
};

/* Actual hash table data type */
struct otf2_hash_table_struct
{
    size_t                       tabsize; /* Number of field elements */
    size_t                       size;    /* Number of items stored */
    otf2_hash_table_list_item*   insertions;
    otf2_hash_table_list_item**  insertions_tail;
    otf2_hash_table_hash_func    hash;    /* Hashing function */
    otf2_hash_table_compare_func kcmp;    /* Comparison function */
    otf2_hash_table_list_item*   table[]; /* Field elements */
};


/*--- Construction & destruction ------------------------------------------*/

otf2_hash_table*
otf2_hash_table_create( size_t                       size,
                        otf2_hash_table_hash_func    hashfunc,
                        otf2_hash_table_compare_func kcmpfunc )
{
    otf2_hash_table* instance;

    /* Validate arguments */
    UTILS_ASSERT( size > 0 && hashfunc && kcmpfunc );

    /* Create hash table data structure */
    instance = calloc( 1, sizeof( *instance ) + size * sizeof( *instance->table ) );
    if ( !instance )
    {
        UTILS_ERROR_POSIX();
        return NULL;
    }

    /* Initialization */
    instance->tabsize         = size;
    instance->size            = 0;
    instance->insertions_tail = &instance->insertions;
    instance->hash            = hashfunc;
    instance->kcmp            = kcmpfunc;

    return instance;
}


static void
iterate_all( otf2_hash_table*             instance,
             otf2_hash_table_process_func processFunc,
             void*                        arg )
{
    /* Validate arguments */
    UTILS_ASSERT( instance && processFunc );

    /* Execute function for each entry */
    otf2_hash_table_list_item* item = instance->insertions;
    while ( item )
    {
        processFunc( &item->entry, arg );
        item = item->next_insertion;
    }
}


struct process_key_value_funcs
{
    void (* key)( void* );
    void (* value)( void* );
};


static void
process_key_value( otf2_hash_table_entry* entry,
                   void*                  arg )
{
    struct process_key_value_funcs* funcs = arg;
    if ( funcs->key )
    {
        funcs->key( entry->key );
    }
    if ( funcs->value )
    {
        funcs->value( entry->value );
    }
}

void
otf2_hash_table_free( otf2_hash_table*            instance,
                      otf2_hash_table_delete_func deleteFunc )
{
    /* Validate arguments */
    UTILS_ASSERT( instance && deleteFunc );

    struct process_key_value_funcs funcs = {
        .value = deleteFunc
    };
    iterate_all( instance, process_key_value, &funcs );

    /* Execute function for each entry */
    while ( instance->insertions )
    {
        otf2_hash_table_list_item* next = instance->insertions->next_insertion;
        free( instance->insertions );
        instance->insertions = next;
    }
    free( instance );
}


/*--- Size operations -----------------------------------------------------*/


size_t
otf2_hash_table_size( const otf2_hash_table* instance )
{
    /* Validate arguments */
    UTILS_ASSERT( instance );

    return instance->size;
}


/*--- Inserting elements --------------------------------------------------*/

void
otf2_hash_table_insert( otf2_hash_table* instance,
                        void*            key,
                        void*            value,
                        size_t*          hashValPtr )
{
    otf2_hash_table_list_item* item;
    size_t                     hashval;
    size_t                     index;

    /* Validate arguments */
    UTILS_ASSERT( instance && key );

    /* Eventually calculate hash value */
    if ( hashValPtr )
    {
        hashval = *hashValPtr;
    }
    else
    {
        hashval = instance->hash( key );
    }
    index = hashval % instance->tabsize;

    /* Create new item */
    item = calloc( 1, sizeof( *item ) );
    if ( !item )
    {
        UTILS_ERROR_POSIX();
        return;
    }

    /* Initialize item */
    item->entry.key      = key;
    item->entry.value    = value;
    item->hash_value     = hashval;
    item->next_collision = instance->table[ index ];

    /* Add item to hash table */
    instance->table[ index ] = item;
    instance->size++;
    *instance->insertions_tail = item;
    instance->insertions_tail  = &item->next_insertion;
}

/*--- Algorithms ----------------------------------------------------------*/

otf2_hash_table_entry*
otf2_hash_table_find( const otf2_hash_table* instance,
                      const void*            key,
                      size_t*                hashValPtr )
{
    otf2_hash_table_list_item* item;
    size_t                     hashval;
    size_t                     index;

    /* Validate arguments */
    UTILS_ASSERT( instance && key );

    /* Calculate hash value */
    hashval = instance->hash( key );
    if ( hashValPtr )
    {
        *hashValPtr = hashval;
    }
    index = hashval % instance->tabsize;

    /* Search element */
    item = instance->table[ index ];
    while ( item )
    {
        if ( hashval == item->hash_value &&
             0 == instance->kcmp( key, item->entry.key ) )
        {
            return &item->entry;
        }

        item = item->next_collision;
    }

    return NULL;
}

void
otf2_hash_table_foreach( const otf2_hash_table*       instance,
                         otf2_hash_table_process_func processFunc,
                         void*                        arg )
{
    /* Validate arguments */
    UTILS_ASSERT( instance && processFunc );

    iterate_all( ( otf2_hash_table* )instance, processFunc, arg );
}


int32_t
otf2_hash_table_compare_strings( const void* key,
                                 const void* item_key )
{
    return strcmp( ( const char* )key, ( const char* )item_key );
}

int32_t
otf2_hash_table_compare_uint8( const void* key,
                               const void* item_key )
{
    return *( const uint8_t* )key == *( const uint8_t* )item_key ? 0 : 1;
}

int32_t
otf2_hash_table_compare_uint64( const void* key,
                                const void* item_key )
{
    return *( const uint64_t* )key != *( const uint64_t* )item_key;
}

size_t
otf2_hash_table_hash_uint8( const void* key )
{
    return ( uint32_t )( *( uint8_t* )key ) * UINT32_C( 2654435761l );
}

size_t
otf2_hash_table_hash_uint64( const void* key )
{
    return *( uint64_t* )key * UINT64_C( 11400714819323199488 );
}
