#include "pch.h"
#include "HashTable.h"

//..............................................................................

static
size_t
djb2(
	const void* _p,
	size_t size
) {
	const uint8_t* p = _p;
	const uint8_t* end = p + size;

	size_t hash = 5381;

	for (; p < end; p++)
		hash = ((hash << 5) + hash) + *p; // hash * 33 + c

	return hash;
}

inline
size_t
strdjb2(const char* string) {
	return djb2(string, string ? strlen(string) : 0);
}

static
inline
size_t
murmur(size_t x) {
#if BITS_PER_LONG == 64
	x ^= x >> 33;
	x *= 0xff51afd7ed558ccdULL;
	x ^= x >> 33;
	x *= 0xc4ceb9fe1a85ec53ULL;
	x ^= x >> 33;
#else
	x ^= x >> 16;
	x *= 0x7feb352d;
	x ^= x >> 15;
	x *= 0x846ca68b;
	x ^= x >> 16;
#endif
	return x;
}

//..............................................................................

void
HashTable_construct(
	HashTable* self,
	HashTableKeyType keyType,
	gfp_t kmallocFlags
) {
	INIT_LIST_HEAD(&self->m_entryList);
	self->m_entryCount = 0;
	self->m_bucketArray = NULL;
	self->m_bucketCount = 0;
	self->m_keyType = keyType;
	self->m_kmallocFlags = kmallocFlags;
}

void
HashTable_destruct(HashTable* self) {
	HashTable_clear(self);

	if (self->m_bucketArray)
		kfree(self->m_bucketArray);
}

inline
size_t
HashTable_getHash(
	HashTable* self,
	const void* key
) {
	return self->m_keyType == HashTableKeyType_String ? strdjb2(key) : murmur((size_t)key);
}

void
HashTable_clear(HashTable* self) {
	if (!self->m_entryCount)
		return;

	while (!list_empty(&self->m_entryList)) {
		struct list_head* link = self->m_entryList.next;
		HashTableEntry* entry = container_of(link, HashTableEntry, m_hashTableLink);
		list_del(link);
		kfree(entry);
	}

	memset(self->m_bucketArray, 0, self->m_bucketCount * sizeof(struct list_head));
	self->m_entryCount = 0;
}

HashTableEntry*
HashTable_find(
	HashTable* self,
	const void* key
) {
	size_t hash;
	struct list_head* bucket;

	if (!self->m_bucketCount)
		return NULL;

	hash = HashTable_getHash(self, key);
	bucket = &self->m_bucketArray[hash % self->m_bucketCount];
	return bucket->next ? HashTable_p_findEntryInBucket(self, bucket, key) : NULL;
}

void
HashTable_remove(
	HashTable* self,
	HashTableEntry* entry
) {
	list_del(&entry->m_hashTableLink);
	list_del(&entry->m_bucketLink);
	kfree(entry);
	self->m_entryCount--;
}

HashTableEntry*
HashTable_visit(
	HashTable* self,
	const void* key
) {
	bool result;

	size_t hash;
	size_t i;
	struct list_head* bucket;
	HashTableEntry* entry;

	result = HashTable_p_prepareBucketArray(self);
	if (!result)
		return NULL;

	hash = HashTable_getHash(self, key);
	i = hash % self->m_bucketCount;
	bucket = &self->m_bucketArray[i];
	if (!bucket->next)
		INIT_LIST_HEAD(bucket);
	else {
		entry = HashTable_p_findEntryInBucket(self, bucket, key);
		if (entry)
			return entry;
	}

	entry = kmalloc(sizeof(HashTableEntry), self->m_kmallocFlags);
	if (!entry)
		return NULL;

	entry->m_key = key;
	entry->m_value = NULL;

	list_add_tail(&entry->m_hashTableLink, &self->m_entryList);
	list_add_tail(&entry->m_bucketLink, bucket);
	self->m_entryCount++;
	return entry;
}

HashTableEntry*
HashTable_insert(
	HashTable* self,
	const void* key,
	void* value,
	void** prevValue
) {
	HashTableEntry* entry = HashTable_visit(self, key);
	if (!entry)
		return NULL;

	if (prevValue)
		*prevValue = entry->m_value;

	entry->m_value = value;
	return entry;
}

bool
HashTable_p_grow(HashTable* self) {
	size_t count = self->m_bucketCount * 2;
	size_t size = count * sizeof(struct list_head);
	struct list_head* link;
	struct list_head* bucketArray = kmalloc(size, self->m_kmallocFlags);
	if (!bucketArray)
		return false;

	memset(bucketArray, 0, size);

	ASSERT(self->m_bucketArray);
	kfree(self->m_bucketArray);
	self->m_bucketArray = bucketArray;
	self->m_bucketCount = count;

	link = self->m_entryList.next;
	for (; link != &self->m_entryList; link = link->next) {
		HashTableEntry* entry = container_of(link, HashTableEntry, m_hashTableLink);
		size_t hash = HashTable_getHash(self, entry->m_key);
		size_t i = hash % self->m_bucketCount;
		struct list_head* bucket = &bucketArray[i];

		if (!bucket->next)
			INIT_LIST_HEAD(bucket);

		list_add_tail(&entry->m_bucketLink, bucket);
	}

	return true;
}

bool
HashTable_p_prepareBucketArray(HashTable* self) {
	size_t size;

	if (self->m_bucketCount)
		return
			(uint64_t)self->m_entryCount * 100 <= (uint64_t)self->m_bucketCount * HashTableConst_ResizeThreshold ||
			HashTable_p_grow(self);

	size = HashTableConst_InitialBucketCount * sizeof(struct list_head);

	ASSERT(!self->m_bucketArray);
	self->m_bucketArray = kmalloc(size, self->m_kmallocFlags);
	if (!self->m_bucketArray)
		return false;

	memset(self->m_bucketArray, 0, size);
	self->m_bucketCount = HashTableConst_InitialBucketCount;
	return true;
}

HashTableEntry*
HashTable_p_findEntryInBucket(
	HashTable* self,
	const struct list_head* bucket,
	const void* key
) {
	struct list_head* link = bucket->next;
	ASSERT(link); // bucket must be initialized

	if (self->m_keyType == HashTableKeyType_String) {
		for (; link != bucket; link = link->next) {
			HashTableEntry* entry = container_of(link, HashTableEntry, m_bucketLink);
			if (strcmp(entry->m_key, key) == 0)
				return entry;
		}
	} else {
		for (; link != bucket; link = link->next) {
			HashTableEntry* entry = container_of(link, HashTableEntry, m_bucketLink);
			if (entry->m_key == key)
				return entry;
		}
	}

	return NULL;
}

//..............................................................................
