// Copyright (C) 2004-2009 Christoph Kubisch & Eike Decker
// This file is part of the "Luxinia Engine".
// For conditions of distribution and use, see copyright notice in luxiniapublicsdk.txt

#include <luxcore/conthash.h>
#include <luxcore/memorypool.h>
#include <luxcore/memorygeneric.h>

#include "cont_defs.h"

#define CONT_HASH_FUNC(cv,k)	(k & (cv->mask))
#define CONT_HASH_SIZE(cv)		(cv->numBins)

LUX_INLINE size_t lxContHash_sizeof(uint numBins)
{
	return (sizeof(lxContHash_t)+(sizeof(lxContHashEntry_t*)*(numBins-1)));
}

void lxContHash_init(lxContHash_t *cv, uint numBins, uint valueSize)
{
	uint extra = (valueSize > sizeof(void*) ? valueSize-(sizeof(void*)) : 0);
	uint itemsize = sizeof(lxContHashEntry_t)+ extra;

	LUX_DEBUGASSERT((numBins&(numBins-1))==0);
	
	itemsize += extra ? (4-(extra%4)) : 0;

	memset(cv,0,lxContHash_sizeof(numBins));
	cv->mask = (numBins-1);
	cv->numBins	  = numBins;
	cv->valueSize = valueSize;
	
	MemoryPool_init(&cv->mempool,itemsize,CONT_HASH_PAGEBYTES/itemsize,4,LUX_TRUE);
}

void lxContHash_deinit(lxContHash_t *cv)
{
	MemoryPool_deinit(&cv->mempool);
}

lxContHashPTR	lxContHash_new(uint numBins,uint valueSize)
{
	lxContHashPTR cv = (lxContHashPTR) lxMemGenAlloc(lxContHash_sizeof(numBins));
	lxContHash_init(cv,numBins,valueSize);
	return cv;
}

void	lxContHash_delete(lxContHashPTR cv)
{
	lxContHash_deinit(cv);
	lxMemGenFree(cv,lxContHash_sizeof(cv->numBins));
}

booln	lxContHash_set(lxContHashPTR cv, uint32 key, void *val)
{
	uint idx = CONT_HASH_FUNC(cv,key);
	uint valueSize = cv->valueSize;

	lxContHashEntry_t* entry = cv->table[idx];
	lxContHashEntry_t* oentry = entry;

	while (entry){
		if (entry->key == key){
			if (!valueSize){
				entry->data = val;
			}
			else{
				memcpy(&entry->data,val,valueSize);
			}
			return LUX_TRUE;
		}
		entry = entry->next;
	}
	
	entry = lxMemoryPool_allocItem(&cv->mempool);
	
	if (!valueSize){
		entry->data = val;
	}
	else{
		memcpy(&entry->data,val,valueSize);
	}
	entry->key = key;
	entry->next = oentry;
	cv->table[idx] = entry;
	
	return LUX_FALSE;
}
booln	lxContHash_remove(lxContHashPTR cv, uint32 key)
{
	uint idx = CONT_HASH_FUNC(cv,key);
	lxContHashEntry_t* entry = cv->table[idx];
	lxContHashEntry_t** lastentry = &cv->table[idx];

	while (entry){
		if (entry->key == key){
			*lastentry = entry->next;
			lxMemoryPool_freeItem(&cv->mempool,entry);
			return LUX_TRUE;
		}
		lastentry = &entry->next;
		entry = entry->next;
	}
	

	return LUX_FALSE;
}
booln	lxContHash_get(const lxContHashPTR cv, uint32 key, void** outval)
{
	uint idx = CONT_HASH_FUNC(cv,key);
	booln valueSize = cv->valueSize;
	lxContHashEntry_t* entry = cv->table[idx];
		
	while (entry){
		if (entry->key == key){
			*outval = valueSize ? &entry->data : entry->data;
			return LUX_TRUE;
		}
		entry = entry->next;
	}
	
	return LUX_FALSE;
}

booln	lxContHash_isEmpty(lxContHashPTR cv)
{
	uint tabsize = CONT_HASH_SIZE(cv);
	uint idx;
	for (idx = 0; idx < tabsize; idx++){
		if (cv->table[idx])	return LUX_FALSE;
	}
	return LUX_TRUE;
}


void	lxContHash_iterate(lxContHashPTR cv, lxContHash_Iterator_fn *itfunc, void *fnData)
{
	uint tabsize = CONT_HASH_SIZE(cv);
	uint idx;
	for (idx = 0; idx < tabsize; idx++){
		lxContHashEntry_t* entry = cv->table[idx];
		while (entry){
			lxContHashEntry_t* next = entry->next;
			fnData = itfunc(fnData,entry->key,entry->data);
			entry = next;
		}
	}
}

uint32	lxContHash_getFirstKey(const lxContHashPTR cv)
{
	uint idx;
	uint tabsize = CONT_HASH_SIZE(cv);

	for (idx = 0; idx < tabsize; idx++){
		lxContHashEntry_t* entry = cv->table[idx];
		if (entry){
			return entry->key;
		}
	}
	return 0;
}
uint32	lxContHash_getNextKey(const lxContHashPTR cv, uint32 key)
{
	uint tabsize = CONT_HASH_SIZE(cv);
	uint idx = CONT_HASH_FUNC(cv,key);
	lxContHashEntry_t* entry = cv->table[idx];

	while (entry){
		if (entry->key != key){
			return entry->key;
		}
		entry = entry->next;
	}

	for (idx = 0; idx < tabsize; idx++){
		entry = cv->table[idx];
		while (entry){
			if (entry->key != key){
				return entry->key;
			}
			entry = entry->next;
		}
	}
	return 0;
}

uint	lxContHash_getCount(const lxContHashPTR cv)
{
	uint tabsize = CONT_HASH_SIZE(cv);
	uint idx;
	uint cnt = 0;

	for (idx = 0; idx < tabsize; idx++){
		lxContHashEntry_t* entry = cv->table[idx];
		while (entry){
			cnt++;
			entry = entry->next;
		}
	}
	return cnt;
}
void*	lxContHash_getNth(const lxContHashPTR cv, uint n)
{
	uint tabsize = CONT_HASH_SIZE(cv);
	uint idx;
	uint cnt = 0;
	booln valueSize = cv->valueSize;

	for (idx = 0; idx < tabsize; idx++){
		lxContHashEntry_t* entry = cv->table[idx];
		while (entry){
			if (cnt == n){
				return valueSize ? &entry->data : entry->data;
			}
			cnt++;
			entry = entry->next;
		}
	}
	return NULL;
}

uint lxContHash_shrink(lxContHash_t *cv)
{
	return lxMemoryPool_shrink(&cv->mempool);
}

float lxContHash_memRatio(lxContHash_t *cv)
{
	return lxMemoryPool_memRatio(&cv->mempool);
}

//////////////////////////////////////////////////////////////////////////
// TODO should use template instead for both

#undef	CONT_HASH_FUNC	
#define CONT_HASH_FUNC(cv,k)	(((uint)(size_t)k) % (cv->mask))

LUX_INLINE size_t lxContPtrHash_sizeof(uint numBins)
{
	return (sizeof(lxContPtrHash_t)+(sizeof(lxContPtrHashEntry_t*)*(numBins-1)));
}

void lxContPtrHash_init(lxContPtrHash_t *cv, uint numBins, uint valueSize)
{
	uint extra = (valueSize > sizeof(void*) ? valueSize-(sizeof(void*)) : 0);
	uint itemsize = sizeof(lxContPtrHashEntry_t)+ extra;

	LUX_DEBUGASSERT((numBins&(numBins-1))==0);

	itemsize += extra ? (4-(extra%4)) : 0;

	memset(cv,0,lxContPtrHash_sizeof(numBins));
	cv->numBins	  = numBins;
	cv->valueSize = valueSize;
	cv->mask = ((numBins-1)|1);

	MemoryPool_init(&cv->mempool,itemsize,CONT_HASH_PAGEBYTES/itemsize,4,LUX_TRUE);
}

void lxContPtrHash_deinit(lxContPtrHash_t *cv)
{
	MemoryPool_deinit(&cv->mempool);
}

lxContPtrHashPTR	lxContPtrHash_new(uint numBins,uint valueSize)
{
	lxContPtrHashPTR cv = (lxContPtrHashPTR) lxMemGenAlloc(lxContPtrHash_sizeof(numBins));
	lxContPtrHash_init(cv,numBins,valueSize);
	return cv;
}

void	lxContPtrHash_delete(lxContPtrHashPTR cv)
{
	lxContPtrHash_deinit(cv);
	lxMemGenFree(cv,lxContPtrHash_sizeof(cv->numBins));
}

booln	lxContPtrHash_set(lxContPtrHashPTR cv, void* key, void *val)
{
	uint idx = CONT_HASH_FUNC(cv,key);
	uint valueSize = cv->valueSize;

	lxContPtrHashEntry_t* entry = cv->table[idx];
	lxContPtrHashEntry_t* oentry = entry;

	while (entry){
		if (entry->key == key){
			if (!valueSize){
				entry->data = val;
			}
			else{
				memcpy(&entry->data,val,valueSize);
			}
			return LUX_TRUE;
		}
		entry = entry->next;
	}

	entry = lxMemoryPool_allocItem(&cv->mempool);

	if (!valueSize){
		entry->data = val;
	}
	else{
		memcpy(&entry->data,val,valueSize);
	}
	entry->key = key;
	entry->next = oentry;
	cv->table[idx] = entry;

	return LUX_FALSE;
}
booln	lxContPtrHash_remove(lxContPtrHashPTR cv, void* key)
{
	uint idx = CONT_HASH_FUNC(cv,key);
	lxContPtrHashEntry_t* entry = cv->table[idx];
	lxContPtrHashEntry_t** lastentry = &cv->table[idx];

	while (entry){
		if (entry->key == key){
			*lastentry = entry->next;
			lxMemoryPool_freeItem(&cv->mempool,entry);
			return LUX_TRUE;
		}
		lastentry = &entry->next;
		entry = entry->next;
	}


	return LUX_FALSE;
}
booln	lxContPtrHash_get(const lxContPtrHashPTR cv, void* key, void** outval)
{
	uint idx = CONT_HASH_FUNC(cv,key);
	booln valueSize = cv->valueSize;
	lxContPtrHashEntry_t* entry = cv->table[idx];

	while (entry){
		if (entry->key == key){
			*outval = valueSize ? &entry->data : entry->data;
			return LUX_TRUE;
		}
		entry = entry->next;
	}

	return LUX_FALSE;
}

booln	lxContPtrHash_isEmpty(lxContPtrHashPTR cv)
{
	uint tabsize = CONT_HASH_SIZE(cv);
	uint idx;
	for (idx = 0; idx < tabsize; idx++){
		if (cv->table[idx])	return LUX_FALSE;
	}
	return LUX_TRUE;
}

void	lxContPtrHash_iterate(lxContPtrHashPTR cv, lxContPtrHash_Iterator_fn *itfunc, void *fnData)
{
	uint tabsize = CONT_HASH_SIZE(cv);
	uint idx;
	for (idx = 0; idx < tabsize; idx++){
		lxContPtrHashEntry_t* entry = cv->table[idx];
		while (entry){
			lxContPtrHashEntry_t* next = entry->next;
			fnData = itfunc(fnData,entry->key,entry->data);
			entry = next;
		}
	}
}