import aliases;
import xxhash3;
import includes;
import std.stdio;
import core.stdc.string : memset;
import a = alloc;

void
Logf(Args...)(string fmt, Args args)
{
	writefln(fmt, args);
}

void
Log(string str)
{
	writeln(str);
}

void
Log(char* str)
{
	writeln(str);
}

u64 
KB(u64 v)
{
	return v * 1024;
};

u64 
MB(u64 v)
{
	return KB(v) * 1024;
};

u64 
GB(u64 v)
{
	return MB(v) * 1024;
};

bool
BitEq(u64 l, u64 r)
{
	return (l & r) == r;
}

struct Node(T)
{
	Node!(T)* next;
	T				 value;
}

struct SLList(T)
{
	Node!(T)* first;
	Node!(T)* last;
}

pragma(inline): bool
CheckNil(T)(Node!(T)* nil, Node!(T)* node)
{
	return node == null || node == nil;
}

pragma(inline): void
ConcatInPlace(T)(SLList!(T)* list, SLList!(T)* to_concat)
{
	if (to_concat.first)
	{
		if (list.first)
		{
			list.last.next = to_concat.first;
			list.last = to_concat.last;
		}
		else
		{
			list.first = to_concat.first;
			list.last = to_concat.last;
		}

		memset(to_concat, 0, SLList!(T).sizeof);
	}
}

pragma(inline): Node!(T)*
Pop(T)(SLList!(T)*list, Node!(T)* nil)
{
	Node!(T)* node = list.first;

	if (list.first == list.last)
	{
		list.first = list.last = nil;
	}
	else
	{
		list.first = list.first.next;
	}

	return node;
}

pragma(inline): void
PushFront(T)(SLList!(T)*list, Node!(T)* node, Node!(T)* nil)
{
	if (CheckNil(nil, list.first))
	{
		list.first = list.last = node;
		node.next = nil;
	}
	else
	{
		node.next = list.first;
		list.first = node;
	}
}

pragma(inline): void
Push(T)(SLList!(T)*list, Node!(T)* node, Node!(T)* nil)
{
	if (CheckNil(nil, list.first))
	{
		list.first = list.last = node;
		node.next = nil;
	}
	else
	{
		list.last.next = node;
		list.last = node;
		node.next = nil;
	}
}

struct KVPair(K, V)
{
	K key;
	V value;
}

struct Result(V)
{
	V 	 value;
	bool ok;
}

struct HashTable(K, V)
{
	alias P = KVPair!(K, V);

	SLList!(P)   free_lists;
	SLList!(P)[] lists;
	Node!(P)*  	 nil;
	u64 						  			 node_count;
	u64							  			 list_count;

	void opIndexAssign(V value, K key)
	{
		Push(&this, key, value);
	}

	Result!(V) opIndex(K key)
	{
		P* pair = Search(&this, key);
		assert(pair != null, "HashTable key index failure: Result must be present");

		Result!(V) result = { ok: false };
		if (pair != null)
		{
			result.value = pair.value;
			result.ok = true;
		}

		return result;
	}

	Result!(V) opIndexUnary(string s: "~")(K key)
	{
		return Delete(&this, key);
	}
}

HashTable!(K, V)
CreateHashTable(K, V)(u64 size)
{
	auto nil = a.Alloc!(Node!(KVPair!(K, V)));
	auto lists = a.AllocArray!(SLList!(KVPair!(K, V)))(size);

	HashTable!(K, V) table = {
		lists: lists,
		list_count: size,
		nil: nil,
		free_lists: {
			first: nil,
			last: nil,
		},
	};

	foreach(list; table.lists)
	{
		list.first = nil;
		list.last = nil;
	}

	return table;
}

pragma(inline): void
Clear(K, V)(HashTable!(K, V)* ht)
{
	table.count = 0;
	foreach(i, list; ht.lists)
	{
		ConcatInPlace(&ht.free_lists, ht.lists.ptr + i);
	}
}

pragma(inline): Node!(KVPair!(K, V))*
Push(K, V)(HashTable!(K, V)* ht, K key, V value)
{
	alias P = KVPair!(K, V);
	alias N = Node!(P);

	N* node = ht.nil;

	if (ht.free_lists.first != ht.nil)
	{
		node = Pop(&ht.free_lists, ht.nil);
	}
	else
	{
		node = a.Alloc!(N);
	}

	node.next = ht.nil;
	node.value.key = key;
	node.value.value = value;

	Push(GetList(ht, key), node, ht.nil);

	ht.node_count += 1;

	return node;
}

pragma(inline): KVPair!(K, V)*
Search(K, V)(HashTable!(K, V)* ht, K key)
{
	KVPair!(K, V)* result = null;

	auto list = GetList(ht, key);
	for(auto node = list.first; node != ht.nil; node = node.next)
	{
		if (node.value.key == key)
		{
			result = &node.value;
			break;
		}
	}

	return result;
}

pragma(inline): SLList!(KVPair!(K, V))*
GetList(K, V)(HashTable!(K, V)* ht, K key)
{
	u64 hash = Hash(&key);
	u64 index = hash % ht.list_count;
	return ht.lists.ptr + index;
}

pragma(inline): Result!(V)
Delete(K, V)(HashTable!(K, V)* ht, K key)
{
	Result!(V) result = { ok: false };

	auto list = GetList(ht, key);
	auto prev = ht.nil;
	for(auto node = list.first; node != ht.nil; node = node.next)
	{
		if (node.value.key == key)
		{
			if (prev != ht.nil)
			{
				prev.next = node.next;
			}

			result.ok = true;
			result.value = node.value.value;

			memset(&node.value, 0, node.value.sizeof);

			Push(&ht.free_lists, node, ht.nil);

			break;
		}
	}

	return result;
}

const u64 HASH_SEED = 5995;

pragma(inline): u64
Hash(T)(T* value)
{
	return xxh3_64bits_withSeed(value, T.sizeof / u8.sizeof, HASH_SEED);
}

pragma(inline): u64
Hash(string str)
{
	return xxh3_64bits_withSeed(str.ptr, str.length, HASH_SEED);
}
