#include <string.h>
#include <KeHashTable.h>

using namespace Knight;

struct KeHashNode
{
	KeHashNode *next;
	uint32_t key_hash;
	const void *key;
	void *value;
};

namespace Knight
{
	class KeHashTable
	{
	public:
		KeHashNode **buckets;
		uint32_t num_buckets;
		uint32_t shift;
		uint32_t num_entries;
		KeHashGenerator key_gen;
		KeHashMarshal key_m;
		KeHashMarshal val_m;
		ke_allocator_t *node_alloc;
		size_t key_offs;
		size_t val_offs;
		size_t node_size;
		uint32_t grow_limit;
		KeHashNode *free_list;
		bool keep_free_list;
	};
}

void *ke_DefHashMalloc(ke_allocator_t *alloc, size_t amt)
{
	return malloc(amt);
}

void ke_DefHashFree(ke_allocator_t *alloc, void *addr)
{
	free(addr);
}

ke_allocator_t s_DefHashAllocator = 
{
	ke_DefHashMalloc,
	ke_DefHashFree,
	NULL
};

KeHashTable *Knight::KE_CreateHashTable(
	uint32_t bits,
	KeHashGenerator key_gen,
	const KeHashMarshal *key_marshal,
	const KeHashMarshal *val_marshal,
	ke_allocator_t *nodeAlloc,
	bool keep_free_list)
{
	KeHashTable *table;

	if (bits >= 27)
	{
		bits = 26;
	}
	else if (bits < 4)
	{
		bits = 4;
	}

	/* Validate marshals. */
	if ((key_marshal->bytes != 0
		 && key_marshal->ctor == NULL)
		|| (val_marshal->bytes != 0
			&& val_marshal->ctor == NULL))
	{
		return NULL;
	}

	table = new KeHashTable;
	table->key_gen = key_gen;
	table->key_m = *key_marshal;
	table->val_m = *val_marshal;
	table->num_entries = 0;
	table->shift = 32 - bits;
	table->node_alloc = nodeAlloc == NULL ? &s_DefHashAllocator : nodeAlloc;
	table->num_buckets = (1 << bits);
	table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
	table->keep_free_list = keep_free_list;
	table->free_list = NULL;
	table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
	memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);

	table->key_offs = sizeof(KeHashNode);
	if (table->key_m.bytes != 0 && table->key_m.bytes % 8 != 0)
	{
		table->key_m.bytes += 8;
		table->key_m.bytes -= (table->key_m.bytes % 8);
	}
	
	table->val_offs = table->key_offs + table->key_m.bytes;
	table->node_size = table->val_offs + table->val_m.bytes;

	return table;
}

#define KE_GET_BUCKET(tbl, hsh) (&(tbl)->buckets[((hsh) * 0x9E3779B9) >> (tbl)->shift])

KeHashNode **ke_HashInternalFind(KeHashTable *table, uint32_t key_hash, const void *key)
{
	KeHashNode *node;
	KeHashNode **bucket;

	bucket = KE_GET_BUCKET(table, key_hash);

	/* :TODO: move to the front once found? */

	while ((node = *bucket) != NULL)
	{
		if (node->key_hash == key_hash
			&& ((table->key_m.cmp != NULL && table->key_m.cmp(node->key, key))
				 || node->key == key))
		{
			return bucket;
		}
		bucket = &node->next;
	}

	return bucket;
}

void ke_ResizeHashTable(KeHashTable *table, uint32_t new_shift)
{
	uint32_t entries;
	KeHashNode *next;
	KeHashNode *node;
	KeHashNode **rbucket;
	KeHashNode **old_buckets;
	uint32_t old_num_buckets;

	/* Save old data */
	old_num_buckets = table->num_buckets;
	old_buckets = table->buckets;
	entries = table->num_entries;

	/* Save new data */
	table->num_buckets = (1 << new_shift);
	table->shift = 32 - new_shift;
	table->grow_limit = (uint32_t)(0.9f * table->num_buckets);

	table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
	memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);

	/* For each old bucket... */
	for (uint32_t i = 0;
		 i < old_num_buckets && entries != 0;
		 i++)
	{
		node = old_buckets[i];
		/* Get each item in its list... */
		while (node != NULL)
		{
			next = node->next;
			
			/* Find the new replacement bucket it needs to go in. */
			rbucket = KE_GET_BUCKET(table, node->key_hash);

			/* Link this node to the next node in the new bucket. */
			if (*rbucket == NULL)
			{
				node->next = NULL;
			}
			else
			{
				node->next = *rbucket;
			}

			/* Add us to the front of that bucket's list. */
			*rbucket = node;

			node = next;
		}
	}

	free(old_buckets);
}

void Knight::KE_AddToHashTable(KeHashTable *table, const void *key, void *val)
{
	KeHashNode *node;
	uint32_t key_hash;
	KeHashNode **bucket;

	key_hash = table->key_gen(key);
	bucket = ke_HashInternalFind(table, key_hash, key);

	if ((node = *bucket) != NULL)
	{
		/* Already in the table */
		if ((table->val_m.cmp != NULL && table->val_m.cmp(node->value, val))
			 || node->value == val)
		{
			return;
		}

		/* Destroy old value if it's set. */
		if (node->value != NULL && table->val_m.dtor != NULL)
		{
			table->val_m.dtor(node->value);
		}
		
		/* Construct or set the new value. */
		if (table->val_m.bytes != 0)
		{
			table->val_m.ctor(node->value, val);
		}
		else
		{
			node->value = val;
		}

		return;
	}

	/* If we're overloaded, we may need to resize. 
	 * Right now, we do this if we hit a .9 entry:buckets ratio.
	 */
	if (table->num_entries >= table->grow_limit)
	{
		ke_ResizeHashTable(table, table->shift << 1);
		bucket = ke_HashInternalFind(table, key_hash, key);
	}

	if (table->free_list != NULL)
	{
		node = table->free_list;
		table->free_list = node->next;
	}
	else
	{
		node = (KeHashNode *)table->node_alloc->alloc(table->node_alloc, table->node_size);
	}
	
	if (table->key_m.bytes == 0)
	{
		node->key = key;
	}
	else
	{
		node->key = (char *)node + table->key_offs;
		table->key_m.ctor((void *)node->key, key);
	}

	if (table->val_m.bytes == 0)
	{
		node->value = val;
	}
	else
	{
		node->value = (char *)node + table->val_offs;
		table->val_m.ctor(node->value, val);
	}

	node->next = *bucket;
	node->key_hash = key_hash;
	*bucket = node;
}

inline void ke_CleanUpHashNode(KeHashTable *table, KeHashNode *node)
{
	/* Destroy old value if it's set. */
	if (node->value != NULL && table->val_m.dtor != NULL)
	{
		table->val_m.dtor(node->value);
	}

	/* Destroy the key. */
	if (table->key_m.dtor != NULL)
	{
		table->key_m.dtor(node->key);
	}

	/* Deallocate us as appropriate. */
	if (table->keep_free_list)
	{
		node->next = table->free_list;
		table->free_list = node;
	}
	else
	{
		table->node_alloc->dealloc(table->node_alloc, node);
	}
}

void Knight::KE_RemoveFromHashTable(KeHashTable *table, const void *key)
{
	KeHashNode *node;
	uint32_t key_hash;
	KeHashNode **bucket;

	key_hash = table->key_gen(key);
	bucket = ke_HashInternalFind(table, key_hash, key);

	if ((node = *bucket) == NULL)
	{
		return;
	}

	/* Link the bucket to its next (removing us). */
	*bucket = node->next;

	ke_CleanUpHashNode(table, node);
}

bool Knight::KE_FindInHashTable(KeHashTable *table, const void *key, void **value)
{
	KeHashNode *node;
	uint32_t key_hash;
	KeHashNode **bucket;

	key_hash = table->key_gen(key);
	bucket = ke_HashInternalFind(table, key_hash, key);

	if ((node = *bucket) == NULL)
	{
		return false;
	}

	if (value != NULL)
	{
		*value = node->value;
	}

	return true;
}

void Knight::KE_DestroyHashTable(KeHashTable *table)
{
	KeHashNode *node, *next;

	/* Turn off this caching! */
	table->keep_free_list = false;

	/* Find entries in buckets that need to be freed. */
	for (uint32_t i = 0; i < table->num_buckets; i++)
	{
		node = table->buckets[i];

		while (node != NULL)
		{
			next = node->next;
			ke_CleanUpHashNode(table, node);
			node = next;
		}
	}

	/* Free the free list */
	while (table->free_list != NULL)
	{
		next = table->free_list->next;
		ke_CleanUpHashNode(table, table->free_list);
		table->free_list = next;
	}

	/* Destroy everything now. */
	free(table->buckets);
	delete table;
}

void Knight::KE_ClearHashTable(KeHashTable *table)
{
	KeHashNode *node, *next;

	/* Free every entry in the table. */
	for (uint32_t i = 0; i < table->num_buckets; i++)
	{
		node = table->buckets[i];

		while (node != NULL)
		{
			next = node->next;
			ke_CleanUpHashNode(table, node);
			node = next;
		}
	}
}

#if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
#pragma intrinsic(_rotl)
#endif

uint32_t Knight::KE_HashString(const void *str)
{
	uint32_t h;
	const unsigned char *us;

	h = 0;

	for (us = (const unsigned char *)str; *us != 0; us++)
	{
#if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
		h = _rotl(h, 4) ^ *us;
#else
		h = ((h << 4) | (h >> 28)) ^ *us;
#endif
	}

	return h;
}

bool Knight::KE_AreStringsEqual(const void* str1, const void* str2)
{
    return (strcmp((const char*)str1, (const char*)str2) == 0) ? true : false;
}

