/* Generic hash table
**
** Copyright 2001, Travis Geiselbrecht. All rights reserved.
** Distributed under the terms of the NewOS License.
*/

#include <KernelExport.h>
#include <malloc.h>
#include <debug.h>
#include <Errors.h>
#include <string.h>
#include <khash.h>

#define TRACE_HASH 0
#if TRACE_HASH
#	define TRACE(x) dprintf x
#else
#	define TRACE(x) ;
#endif

// ToDo: the hashtable is not expanded when necessary (no load factor, no nothing)
//		Could try to use pools instead of malloc() for the elements - might be
//		faster than the current approach.

struct hash_table {
	struct hash_element **table;
	int		next_ptr_offset;
	uint32	table_size;
	int		num_elements;
	int		flags;
	int		(*compare_func)(void *e, const void *key);
	uint32	(*hash_func)(void *e, const void *key, uint32 range);
};

// XXX gross hack
#define NEXT_ADDR(t, e) ((void *)(((unsigned long)(e)) + (t)->next_ptr_offset))
#define NEXT(t, e) ((void *)(*(unsigned long *)NEXT_ADDR(t, e)))
#define PUT_IN_NEXT(t, e, val) (*(unsigned long *)NEXT_ADDR(t, e) = (long)(val))


static inline void *
next_element(hash_table *table, void *element)
{
	// ToDo: should we use this instead of the NEXT() macro?
	return (void *)(*(unsigned long *)NEXT_ADDR(table, element));
}


struct hash_table *
hash_init(uint32 table_size, int next_ptr_offset,
	int compare_func(void *e, const void *key),
	uint32 hash_func(void *e, const void *key, uint32 range))
{
	struct hash_table *t;
	unsigned int i;

	if (compare_func == NULL || hash_func == NULL) {
		dprintf("hash_init() called with NULL function pointer\n");
		return NULL;
	}

	t = (struct hash_table *)malloc(sizeof(struct hash_table));
	if (t == NULL)
		return NULL;

	t->table = (struct hash_element **)malloc(sizeof(void *) * table_size);
	if (t->table == NULL) {
		free(t);
		return NULL;
	}

	for (i = 0; i < table_size; i++)
		t->table[i] = NULL;

	t->table_size = table_size;
	t->next_ptr_offset = next_ptr_offset;
	t->flags = 0;
	t->num_elements = 0;
	t->compare_func = compare_func;
	t->hash_func = hash_func;

	TRACE(("hash_init: created table %p, next_ptr_offset %d, compare_func %p, hash_func %p\n",
		t, next_ptr_offset, compare_func, hash_func));

	return t;
}


int
hash_uninit(struct hash_table *table)
{
	ASSERT(table->num_elements == 0);

	free(table->table);
	free(table);

	return 0;
}


status_t
hash_insert(struct hash_table *table, void *element)
{
	uint32 hash;

	ASSERT(table != NULL && element != NULL);
	TRACE(("hash_insert: table 0x%x, element 0x%x\n", table, element));

	hash = table->hash_func(element, NULL, table->table_size);
	PUT_IN_NEXT(table, element, table->table[hash]);
	table->table[hash] = (struct hash_element *)element;
	table->num_elements++;

	// ToDo: resize hash table if it's grown too much!

	return 0;
}


status_t
hash_remove(struct hash_table *table, void *_element)
{
	uint32 hash = table->hash_func(_element, NULL, table->table_size);
	void *element, *lastElement = NULL;

	for (element = table->table[hash]; element != NULL; lastElement = element, element = NEXT(table, element)) {
		if (element == _element) {
			if (lastElement != NULL) {
				// connect the previous entry with the next one
				PUT_IN_NEXT(table, lastElement, NEXT(table, element));
			} else
				table->table[hash] = (struct hash_element *)NEXT(table, element);
			table->num_elements--;

			return B_OK;
		}
	}

	return B_ERROR;
}


void *
hash_find(struct hash_table *table, void *searchedElement)
{
	uint32 hash = table->hash_func(searchedElement, NULL, table->table_size);
	void *element;

	for (element = table->table[hash]; element != NULL; element = NEXT(table, element)) {
		if (element == searchedElement)
			return element;
	}

	return NULL;
}


void *
hash_lookup(struct hash_table *table, const void *key)
{
	uint32 hash = table->hash_func(NULL, key, table->table_size);
	void *element;

	for (element = table->table[hash]; element != NULL; element = NEXT(table, element)) {
		if (table->compare_func(element, key) == 0)
			return element;
	}

	return NULL;
}


struct hash_iterator *
hash_open(struct hash_table *table, struct hash_iterator *iterator)
{
	if (iterator == NULL) {
		iterator = (struct hash_iterator *)malloc(sizeof(struct hash_iterator));
		if (iterator == NULL)
			return NULL;
	}

	hash_rewind(table, iterator);

	return iterator;
}


void
hash_close(struct hash_table *table, struct hash_iterator *iterator, bool freeIterator)
{
	if (freeIterator)
		free(iterator);
}


void
hash_rewind(struct hash_table *table, struct hash_iterator *iterator)
{
	iterator->current = NULL;
	iterator->bucket = -1;
}


void *
hash_next(struct hash_table *table, struct hash_iterator *iterator)
{
	uint32 index;

restart:
	if (iterator->current == NULL) {
		// get next bucket
		for (index = (uint32)(iterator->bucket + 1); index < table->table_size; index++) {
			if (table->table[index]) {
				iterator->bucket = index;
				iterator->current = table->table[index];
				break;
			}
		}
	} else {
		iterator->current = NEXT(table, iterator->current);
		if (!iterator->current)
			goto restart;
	}

	return iterator->current;
}


uint32
hash_hash_string(const char *string)
{
	uint32 hash = 0;
	char c;

	// we assume hash to be at least 32 bits
	while ((c = *string++) != 0) {
		hash ^= hash >> 28;
		hash <<= 4;
		hash ^= c;
	}

	return hash;
}

