#include <qse/cmn/oht.h>
#include <qse/hash.h>
#include "mem-prv.h"

#define DATA_PTR(oht,index) \
	((void*)(((qse_byte_t*)(oht)->data) + ((index) * (oht)->scale)))

static QSE_INLINE_ALWAYS qse_size_t default_hasher (qse_oht_t* oht, const void* data)
{
	qse_size_t h;
	QSE_HASH_BYTES (h, data, oht->scale);
	return h ; 
}

static QSE_INLINE_ALWAYS int default_comper (qse_oht_t* oht, const void* data1, const void* data2)
{
	return QSE_MEMCMP(data1, data2, oht->scale);
}

static QSE_INLINE_ALWAYS void default_copier (qse_oht_t* oht, void* dst, const void* src)
{
	QSE_MEMCPY (dst, src, oht->scale);
}

#define HASH_DATA(oht,data) \
		((oht)->hasher? (oht)->hasher ((oht), (data)): \
		                default_hasher (oht, data))

#define COMP_DATA(oht,d1,d2) \
		((oht)->comper? (oht)->comper ((oht), (d1), (d2)): \
		                QSE_MEMCMP ((d1), (d2), (oht)->scale))

#define COPY_DATA(oht,dst,src) \
	QSE_BLOCK ( \
		if ((oht)->copier) (oht)->copier ((oht), (dst), (src)); \
		else QSE_MEMCPY ((dst), (src), (oht)->scale); \
	) 

qse_oht_t* qse_oht_open (
	qse_mmgr_t* mmgr, qse_size_t xtnsize,
	int scale, qse_size_t capa, qse_size_t limit) 
{
	qse_oht_t* oht;

	oht = QSE_MMGR_ALLOC (mmgr, QSE_SIZEOF(qse_oht_t) + xtnsize);
	if (oht == QSE_NULL) return QSE_NULL;

	if (qse_oht_init (oht, mmgr, scale, capa, limit) <= -1)
	{
		QSE_MMGR_FREE (mmgr, oht);
		return QSE_NULL;
	}

	QSE_MEMSET (oht + 1, 0, xtnsize);
	return oht;
}

void qse_oht_close (qse_oht_t* oht)
{
	qse_oht_fini (oht);
	QSE_MMGR_FREE (oht->mmgr, oht);
}

int qse_oht_init (
	qse_oht_t* oht, qse_mmgr_t* mmgr,
	int scale, qse_size_t capa, qse_size_t limit) 
{
	qse_size_t i;

	if (scale <= 0) scale = 1;
	if (capa >= QSE_OHT_NIL - 1) capa = QSE_OHT_NIL - 1;
	if (limit > capa || limit <= 0) limit = capa;

	QSE_MEMSET (oht, 0, QSE_SIZEOF(*oht));

	oht->mmgr = mmgr;
	oht->capa.hard = capa;
	oht->capa.soft = limit;
	oht->scale = scale;
	oht->size = 0;

	/*oht->hasher = default_hasher;
	oht->comper = default_comper;
	oht->copier = default_copier;*/

	oht->mark = QSE_MMGR_ALLOC (mmgr, QSE_SIZEOF(qse_oht_mark_t) * capa);
	if (!oht->mark) return -1;

	oht->data = QSE_MMGR_ALLOC (mmgr, scale * capa);
	if (!oht->data)
	{
		QSE_MMGR_FREE (mmgr, oht->mark);
		return -1;
	}

	for (i = 0; i < capa; i++) oht->mark[i] = QSE_OHT_EMPTY;
	return 0;
}

void qse_oht_fini (qse_oht_t* oht)
{
	QSE_MMGR_FREE (oht->mmgr, oht->mark);
	QSE_MMGR_FREE (oht->mmgr, oht->data);
	oht->size = 0;
}

qse_mmgr_t* qse_oht_getmmgr (qse_oht_t* oht)
{
	return oht->mmgr;
}

void* qse_oht_getxtn (qse_oht_t* oht)
{
	return QSE_XTN (oht);
}

qse_oht_hasher_t qse_oht_gethasher (qse_oht_t* oht)
{
	return oht->hasher? oht->hasher: default_hasher;
}

void qse_oht_sethasher (qse_oht_t* oht, qse_oht_hasher_t hasher)
{
	oht->hasher = hasher;
}

qse_oht_comper_t qse_oht_getcomper (qse_oht_t* oht)
{
	return oht->comper? oht->comper: default_comper;
}

void qse_oht_setcomper (qse_oht_t* oht, qse_oht_comper_t comper)
{
	oht->comper = comper;
}

qse_oht_copier_t qse_oht_getcopier (qse_oht_t* oht)
{
	return oht->copier? oht->copier: default_copier;
}

void qse_oht_setcopier (qse_oht_t* oht, qse_oht_copier_t copier)
{
	oht->copier = copier;
}

static QSE_INLINE qse_size_t search (
	qse_oht_t* oht, const void* data, qse_size_t hash)
{
	qse_size_t i;

	for (i = 0; i < oht->capa.hard; i++)
	{
		qse_size_t index = (hash + i) % oht->capa.hard;
		if (oht->mark[index] == QSE_OHT_EMPTY) break;
		if (oht->mark[index] == QSE_OHT_OCCUPIED)
		{
			if (COMP_DATA (oht, data, DATA_PTR(oht,index)) == 0) 
				return index;
		}
	}

	return QSE_OHT_NIL;
}

qse_size_t qse_oht_search (qse_oht_t* oht, void* data)
{
	qse_size_t i = search (oht, data, HASH_DATA(oht,data));
	if (i != QSE_OHT_NIL && data) 
		COPY_DATA (oht, data, DATA_PTR(oht,i));
	return i;
}

qse_size_t qse_oht_update (qse_oht_t* oht, const void* data)
{
	qse_size_t i = search (oht, data, HASH_DATA(oht,data));
	if (i != QSE_OHT_NIL) 
		COPY_DATA (oht, DATA_PTR(oht,i), data);
	return i;
}

qse_size_t qse_oht_upsert (qse_oht_t* oht, const void* data)
{
	qse_size_t i, hash = HASH_DATA (oht, data);

	/* find the existing item */
	i = search (oht, data, hash);
	if (i != QSE_OHT_NIL)
	{
		COPY_DATA (oht, DATA_PTR(oht,i), data);
		return i;
	}

	/* check if there is a free slot to insert data into */
	if (oht->size >= oht->capa.soft) return QSE_OHT_NIL;

	/* get the unoccupied slot and insert the data into it.
	 * iterate at most 'the number of items (oht->size)' times + 1. */
	for (i = 0; i <= oht->size; i++) 
	{
		qse_size_t index = (hash + i) % oht->capa.hard;
		if (oht->mark[index] != QSE_OHT_OCCUPIED) 
		{
			oht->mark[index] = QSE_OHT_OCCUPIED;
			COPY_DATA (oht, DATA_PTR(oht,index), data);
			oht->size++;
			return index;
		}
	}

	return QSE_OHT_NIL;
}

qse_size_t qse_oht_insert (qse_oht_t* oht, const void* data)
{
	qse_size_t i, hash;

	/* check if there is a free slot to insert data into */
	if (oht->size >= oht->capa.soft) return QSE_OHT_NIL;

	hash = HASH_DATA (oht, data);

	/* check if the item already exits */
	i = search (oht, data, hash);
	if (i != QSE_OHT_NIL) return QSE_OHT_NIL;

	/* get the unoccupied slot and insert the data into it.
	 * iterate at most 'the number of items (oht->size)' times + 1. */
	for (i = 0; i <= oht->size; i++) 
	{
		qse_size_t index = (hash + i) % oht->capa.hard;
		if (oht->mark[index] != QSE_OHT_OCCUPIED) 
		{
			oht->mark[index] = QSE_OHT_OCCUPIED;
			COPY_DATA (oht, DATA_PTR(oht,index), data);
			oht->size++;
			return index;
		}
	}

	return QSE_OHT_NIL;
}

qse_size_t qse_oht_delete (qse_oht_t* oht, const void* data)
{
#if 0
	qse_size_t index;

	if (oht->size <= 0) return QSE_OHT_NIL; 

	index = search (oht, data, HASH_DATA(oht,data));
	if (index != QSE_OHT_NIL)
	{
		oht->mark[index] = QSE_OHT_DELETED;
		oht->size--;
	}

	return index;
#endif

	qse_size_t index, i, x, y, z;

	/* check if the oht is empty. if so, do nothing */
	if (oht->size <= 0) return QSE_OHT_NIL; 

	/* check if the item exists. otherwise, do nothing. */
	index = search (oht, data, HASH_DATA(oht,data));
	if (index == QSE_OHT_NIL) return QSE_OHT_NIL;

	/* compact the cluster */
	for (i = 0, x = index, y = index; i < oht->size; i++)
	{
		y = (y + 1) % oht->capa.hard;

		/* done if the slot at the current hash index is empty */
		if (oht->mark[y] == QSE_OHT_EMPTY) break;

		/* get the natural hash index for the data in the slot at 
		 * the current hash index */
		z = HASH_DATA(oht,DATA_PTR(oht,y)) % oht->capa.hard;
		
		/* move an element if necesary */
		if ((y > x && (z <= x || z > y)) ||
		    (y < x && (z <= x && z > y)))
		{
			COPY_DATA (oht, DATA_PTR(oht,x), DATA_PTR(oht,y));
			x = y;
		}
	}

	oht->mark[x] = QSE_OHT_EMPTY;
	oht->size--;

	return index;
}

void qse_oht_clear (qse_oht_t* oht)
{
	qse_size_t i;
	for (i = 0; i < oht->capa.hard; i++)
		oht->mark[i] = QSE_OHT_EMPTY;
	oht->size = 0;
}

void qse_oht_walk (qse_oht_t* oht, qse_oht_walker_t walker, void* ctx)
{
	qse_size_t i;

	for (i = 0; i < oht->capa.hard; i++)
	{
		if (oht->mark[i] == QSE_OHT_OCCUPIED)
		{
			if (walker (oht, DATA_PTR(oht,i), ctx) == QSE_OHT_WALK_STOP) 
				return;
		}
	}
}
