/**
 * \file
 * \brief Barrelfish collections library hash table
 */
/*
 * Copyright (c) 2010, ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
 */

#include "collections/hash_table.h"
#include "inttypes.h"

/******************************************************
 * a simple hash table implementation
 ******************************************************/

/*
 * Function to identify the right element from the
 * linked list.
 */
static int32_t match_key(void *data, void *arg)
{
	collections_hash_elem *elem = (collections_hash_elem *) data;
	uint64_t key  = *((uint64_t *)arg);

    return (elem->key == key);
}

/*
 * Create a hash table.
 */
static void collections_hash_create_core(collections_hash_table **t, int num_buckets, collections_hash_data_free data_free)
{
	int i;

	*t = (collections_hash_table *) malloc (sizeof(collections_hash_table));
	memset(*t, 0, sizeof(collections_hash_table));

	(*t)->num_buckets = num_buckets;

	// create a linked list node for each bucket
	(*t)->buckets = (collections_listnode **) malloc(sizeof(collections_listnode *) * num_buckets);
	for (i = 0; i < num_buckets; i ++) {
		collections_list_create(&(*t)->buckets[i], NULL);
	}

	(*t)->num_elems = 0;
    (*t)->data_free = data_free;

	// to keep track of traversing the hash table
	(*t)->cur_bucket_num = -1;
	return;
}

void collections_hash_create(collections_hash_table **t, collections_hash_data_free elem_free)
{
	collections_hash_create_core(t, NUM_BUCKETS, elem_free);
}

void collections_hash_create_with_buckets(collections_hash_table **t, int num_buckets, collections_hash_data_free elem_free)
{
	collections_hash_create_core(t, num_buckets, elem_free);
}

static int collections_hash_release_elem(void* elem, void * arg)
{
    collections_hash_table *t = (collections_hash_table *)arg;
    collections_hash_elem *he = (collections_hash_elem *)elem;
    if (t->data_free)
    {
        t->data_free(he->data);
    }
    free(he);

	t->num_elems--;

    return 1;
}

// delete the entire hash table
void collections_hash_release(collections_hash_table *t)
{
	int bucket_num;
	int bucket_size;
	collections_listnode *bucket;

	for (bucket_num = 0; bucket_num < t->num_buckets; bucket_num ++) {
        uint32_t before, after;
		bucket = t->buckets[bucket_num];
		bucket_size = collections_list_size(bucket);
        
        before = t->num_elems;
        collections_list_visit(bucket, collections_hash_release_elem, t);
        after = t->num_elems;
        assert(before - after == bucket_size);

        collections_list_release(bucket);
	}
    assert(t->num_elems == 0);

	free(t->buckets);
	free(t);
}

static collections_hash_elem* collections_hash_find_elem(collections_hash_table *t, uint64_t key)
{
	uint32_t bucket_num;	
	collections_listnode *bucket;	
	collections_hash_elem *elem;

	bucket_num = key % t->num_buckets;
	bucket = t->buckets[bucket_num];
	elem = (collections_hash_elem*) collections_list_find_if(bucket, match_key, &key);
    return elem;
}

/*
 * Inserts an element into the hash table.
 */
void collections_hash_insert(collections_hash_table *t, uint64_t key, void *data)
{
	uint32_t bucket_num;
	collections_listnode *bucket;
	collections_hash_elem *elem;

    elem = collections_hash_find_elem(t, key);
	if (elem != NULL) {
		printf("Error: key %" PRIu64 " already present in hash table %" PRIu64 "\n",
            key, elem->key);
		assert(0);
		return;
	}

	bucket_num = key % t->num_buckets;
	bucket = t->buckets[bucket_num];
	elem = (collections_hash_elem *) malloc(sizeof(collections_hash_elem));
	elem->key = key;
	elem->data = data;
	collections_list_insert(bucket, (void *)elem);
	t->num_elems ++;
}

/*
 * Retrieves an element from the hash table.
 */
void *collections_hash_find(collections_hash_table *t, uint64_t key)
{
    collections_hash_elem *he = collections_hash_find_elem(t, key);
    return (he) ? he->data : NULL;
}

/*
 * Removes a specific element from the table.
 */
void collections_hash_delete(collections_hash_table *t, uint64_t key)
{	
	uint32_t bucket_num;	
	collections_listnode *bucket;	
	collections_hash_elem *elem;

	bucket_num = key % t->num_buckets;
	bucket = t->buckets[bucket_num];
	elem = (collections_hash_elem*) collections_list_remove_if(bucket, match_key, &key);
	if (elem) {
        uint32_t n = t->num_elems;
        collections_hash_release_elem(elem, t);
        assert(1 == n - t->num_elems);
	}
    else
    {
	    printf("Error: cannot find the node with key %" PRIu64 " in collections_hash_release\n", key);
    }
}

/*
 * Returns the number of elements in the hash table.
 */
uint32_t collections_hash_size(collections_hash_table *t)
{
	return (t->num_elems);
}

static collections_listnode* collections_hash_get_next_valid_bucket(collections_hash_table* t)
{
	collections_listnode* bucket;

	do {
		t->cur_bucket_num ++;
		if (t->cur_bucket_num < t->num_buckets) {
			if (!t->buckets[t->cur_bucket_num]) {
				continue;
			}
		} else {
			return NULL;
		}
	} while (collections_list_size(t->buckets[t->cur_bucket_num]) <= 0);

	bucket = t->buckets[t->cur_bucket_num];
	collections_list_traverse_start(bucket);

	return bucket;
}

int32_t collections_hash_traverse_start(collections_hash_table *t)
{
	if (t->cur_bucket_num != -1) {
		// if the cur_bucket_num is valid, a
		// traversal is already in progress.
		printf("Error: collections_hash_table is already opened for traversal.\n");
		return -1;
	}

	collections_hash_get_next_valid_bucket(t);

	return 1;
}

/*
 * Returns the next element in the hash table. If
 * a valid element is found, the key is set to the
 * key of the element. If there is no valid element,
 * returns null and key is not modified.
 */
void* collections_hash_traverse_next(collections_hash_table* t, uint64_t *key)
{
	if (t->cur_bucket_num == -1) {
		// if the cur_bucket_num is invalid, 
		// hash traversal has not been started.
		printf("Error: collections_hash_table must be opened for traversal first.\n");
		return NULL;
	}
	
	if (t->cur_bucket_num >= t->num_buckets) {
		// all the buckets have been traversed.
		return NULL;
	} else {
		collections_listnode*	bucket;
		collections_hash_elem*	ret;

		if (t->buckets[t->cur_bucket_num]) {
			bucket = t->buckets[t->cur_bucket_num];
			ret = (collections_hash_elem*) collections_list_traverse_next(bucket);

			if (ret) {
				*key = ret->key;
				return ret->data;
			} else {
				// this list traversal is over.
				// let's close it.
				collections_list_traverse_end(bucket);
			}
		}

		bucket = collections_hash_get_next_valid_bucket(t);
		if (!bucket) {
			return NULL;
		} else {
			ret = (collections_hash_elem*) collections_list_traverse_next(bucket);
			assert(ret != NULL);
		}

		*key = ret->key;
		return ret->data;
	}
}

int32_t	collections_hash_traverse_end(collections_hash_table* t)
{
	if (t->cur_bucket_num == -1) {
		// if the cur_bucket_num is invalid, 
		// hash traversal has not been started.
		printf("Error: collections_hash_table must be opened for traversal first.\n");
		return -1;
	}

    // XXX The bucktes (list) are not reset here which may cause errors when the
    // hash table is only traversed half way. 

	t->cur_bucket_num = -1;
	return 1;
}

struct collections_hash_visitor_tuple
{
    collections_hash_visitor_func func;
    void *arg;
};

static int collections_hash_visit0(void* list_data, void* arg)
{
    struct collections_hash_visitor_tuple *t = (struct collections_hash_visitor_tuple *)arg;
    collections_hash_elem *he = (collections_hash_elem*)list_data;
    return t->func(he->key, he->data, t->arg);
}

int collections_hash_visit(collections_hash_table* t, collections_hash_visitor_func func, void* arg)
{
    struct collections_hash_visitor_tuple tuple = { func, arg };
    int i = 0;
    while (i < t->num_buckets)
    {
        if (collections_list_visit(t->buckets[i], collections_hash_visit0, &tuple) == 0) {
            break;
        }
        i++;
    }

    return (i == t->num_buckets);
}
