module dlib.util;

import dlib.aliases;
import dlib.alloc;

import std.traits : isIntegral, hasMember, isArray;

import core.stdc.string;

import std.format : sformat;
import std.stdio  : writeln, writef, writefln;

//import std.traits;

static if(NativeTarget)
{
	import dlib.platform;
}

enum bool StringType(T)  = (is(T == string) || is(T == u8[]) || is(T == char[]) || is(T == const(char)[]));
enum bool CStringType(T) = (is(T == char*) || is(T == u8*) || is(T == immutable(char)*) || is(T == const(char)*));

pragma(inline) void
Int3()
{
	static if(NativeTarget) 
	{
		debug asm 
		{
			db 0xCC;
		}
	}
}

pragma(inline) void
Pause()
{
	static if(NativeTarget) 
	{
		asm
		{
			rep;
			nop;
		}
	}
}

void
Assert(T)(T cond, string msg)
{
	if(!cond)
	{
		assert(false, msg);
	}
}

string
Str(T)(T arr) if(StringType!(T))
{
	return (cast(immutable(char)*)arr.ptr)[0 .. arr.length];
}

string
Str(T)(T ptr) if(CStringType!(T))
{
	return ptr ? Str(ptr[0 .. cast(usize)strlen(ptr)]) : null;
}

alias ConvToStr = Str;

T[]
Arr(T)(string str)
{
	T[] arr = (cast(T*)str.ptr)[0 .. str.length];
	return arr;
}

alias CastStr = Arr;

T[]
CastArr(T, U)(U[] input_array)
{
	static assert(T.sizeof == U.sizeof);
	T[] output_array = (cast(T*)input_array.ptr)[0 .. input_array.length];
	return output_array;
}

// TODO: remove exceptions here

void
Logf(string prefix = "INFO ", Args...)(string fmt, Args args)
{
	static if(NativeTarget)
	{
		try
		{
			writef("[%s]: ", prefix);
			writefln(fmt, args);
		}
		catch (Exception e)
		{
			assert(false, "Incompatible format type");
		}
	}
	else
	{
		writef("[%s]: ", prefix);
		writefln(fmt, args);
	}
}

void
Logif(Args...)(string fmt, Args args, string func = __FUNCTION__)
{
	static if(NativeTarget)
	{
		try
		{
			writef("FN: [%s] ", func);
			Logf(fmt, args);
		}
		catch (Exception e)
		{
			assert(false, "Incompatible format type");
		}
	}
	else
	{
		writef("FN: [%s] ", func);
		Logf(fmt, args);
	}
}

void
Warnf(Args...)(string fmt, Args args)
{
	Logf!("WARN ", Args)(fmt, args);
}

void
Errf(Args...)(string fmt, Args args)
{
	Logf!("ERROR", Args)(fmt, args);
}

void
Debugf(Args...)(string fmt, Args args)
{
	debug Logf(fmt, args, "[DEBUG]: ");
}

string
Scratchf(Args...)(string fmt, Args args)
{
	assert(g_scratch.init);
	char[] buf = ScratchAlloc!(char)(fmt.length < 16 ? 32 : 128);
	return Str(sformat(buf, fmt, args));
}

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

void
Log(char* str)
{
	writeln(str[0 .. cast(usize)(strlen(str))]);
}

@nogc pure @safe
{

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

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

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

}


u32
StrCharCount(T)(T str, u8 ch) if(is(T: string) || is(T: u8[]) || is(T: char[]))
{
	u32 count = 0;
	for(u64 i = 0; i < str.length; i += 1)
	{
		count += cast(u8)str[i] == ch;
	}
	return count;
}

pragma(inline) void
ConvertColor(Vec4 *dst, u32 src)
{
	if(src == 0)
	{
		dst.rgb = 0.0;
		dst.a = 1.0;
	}
	else
	{
		Convert(dst, src);
	}
}

string
GetFilePath(string file_name)
{
	string result = file_name;

	for(usize i = file_name.length-1; i64(i) >= 0; i -= 1)
	{
		version(Windows)
		{
			char ch = '\\';
		}
		else
		{
			char ch = '/';
		}

		if(file_name[i] == ch)
		{
			result = file_name[0 .. i+1];
			break;
		}
	}

	return result;
}

pragma(inline) void
Convert(Vec4* dst, u32 src)
{
	dst.r = cast(f32)((src >> 0)  & 0xFF) / 255.0;
	dst.g = cast(f32)((src >> 8)  & 0xFF) / 255.0;
	dst.b = cast(f32)((src >> 16) & 0xFF) / 255.0;
	dst.a = cast(f32)((src >> 24) & 0xFF) / 255.0;
}

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

struct ListBox(T, bool doubly_linked)
{
	alias U = ListBox!(T, doubly_linked);

	U* next;
	static if(doubly_linked)
	{
		U* prev;
	}
	T value;
}

struct LinkedList(T)
{
	T* first, last;
}

void
ConcatInPlace(T)(T* list, 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, T.sizeof);
	}
}

U* 
DLLPop(T, U)(T* list, U* nil)
{
	U* node = list.first;

	if (!CheckNil(nil, list.first))
	{
		if(list.first == list.last)
		{
			list.first = list.last = nil;
		}
		else
		{
			list.first = list.first.next;
			list.first.prev = nil;
		}
	}

	return node;
}

void
DLLRemove(T, U)(T* list, U* node, U* nil)
{
	if(list.first == list.last)
	{
		list.first = list.last = nil;
	}
	else if(list.first == node)
	{
		list.first = node.next;
		list.first.prev = nil;
	}
	else if(list.last == node)
	{
		node.prev.next = nil;
		list.last = node.prev;
	}
	else
	{
		node.next.prev = node.prev;
		node.prev.next = node.next;
	}
}

void
DLLPushFront(T, U)(T* list, U* node, U* nil)
{
	node.prev = node.next = nil;

	if(CheckNil(nil, list.first))
	{
		list.first = list.last = node;
	}
	else
	{
		node.next = list.first;
		node.prev = nil;
		list.first.prev = node;
		list.first = node;
	}
}

void
DLLPush(T, U)(T* list, U* node, U* nil)
{
	node.prev = node.next = nil;

	if(CheckNil(nil, list.first))
	{
		list.first = list.last = node;
	}
	else
	{
		list.last.next = node;
		node.prev = list.last;
		list.last = node;
		node.next = nil;
	}
}

void
DLLInsert(T, U)(T* list, U* node, U* prev, U* nil) if(hasMember!(T, "last") && hasMember!(U, "prev"))
{
	if(CheckNil(nil, list.first) && CheckNil(nil, list.last))
	{
		list.first = list.last = node;
		node.next = node.prev = nil;
	}
	else if(!CheckNil(nil, prev))
	{
		node.next       = list.first;
		list.first.prev = node;
		list.first      = node;
		node.prev       = nil;
	}
	else if(list.last == prev)
	{
		list.last.next = node;
		node.prev      = list.last;
		list.last      = node;
		node.next      = nil;
	}
	else if(!CheckNil(nil, prev) && CheckNil(nil, prev.next))
	{}
	else
	{
		node.prev      = prev;
		node.next      = prev.next;
		node.next.prev = node;
		prev.next      = node;
	}
}

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

pragma(inline) U*
SLLPop(T, U)(T* list, U* nil)
{
	U* node = list.first;

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

	return node;
}

pragma(inline) void
SLLRemove(T, U)(T* list, U* node, U* prev, U* nil)
{
	if(list.first == list.last)
	{
		list.first = list.last = nil;
	}
	else if(list.first == node)
	{
		list.first = node.next;
	}
	else if(list.last == node)
	{
		list.last = prev;
		prev.next = nil;
	}
	else
	{
		prev.next = node.next;
	}
}

pragma(inline) void
SLLPushFront(T, U)(T* list, U* node, U* nil)
{
	node.next = nil;

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

pragma(inline) void
SLLPush(T, U)(T* list, U* node, U* nil)
{
	node.next = nil;

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

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

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

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

	LinkedList!(P)   free_lists;
	LinkedList!(P)[] lists;
	P*							 nil;
	Arena            arena;
	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);

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

		return result;
	}
}

HashTable!(K, V)
CreateHashTable(K, V)(usize size)
{
	Arena arena = CreateArena(MB(4));
	auto nil    = Alloc!(KVPair!(K, V))(&arena);
	auto lists  = Alloc!(LinkedList!(KVPair!(K, V)))(&arena, size);

	HashTable!(K, V) table = {
		arena: arena,
		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) KVPair!(K, V)*
Push(K, V)(HashTable!(K, V)* ht, K key, V value)
{
	alias P = KVPair!(K, V);

	P* node = ht.nil;

	if(!CheckNil(ht.nil, ht.free_lists.first))
	{
		node = SLLPop(&ht.free_lists, ht.nil);
	}
	else
	{
		node = Alloc!(P)(&ht.arena);
	}

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

	SLLPush(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; !CheckNil(ht.nil, node); node = node.next)
	{
		if(node.key == key)
		{
			result = node;
			break;
		}
	}

	return result;
}

pragma(inline) LinkedList!(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;
}

KVPair!(K, V)*[]
GetAllNodes(K, V)(Arena* arena, HashTable!(K, V)* ht)
{
	KVPair!(K, V)*[] pairs = Alloc!(KVPair!(K, V)*)(arena, ht.node_count);

	if(ht.node_count > 0)
	{
		u64 count = 0;
		
		for(u64 i = 0; i < ht.lists.length; i += 1)
		{
			for(auto n = ht.lists[i].first; !CheckNil(ht.nil, n); n = n.next)
			{
				pairs[count] = n;
				count += 1;
			}
		}
	}

	return pairs;
}

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; !CheckNil(ht.nil, node); prev = node, node = node.next)
	{
		if(node.key == key)
		{
			SLLRemove(list, node, prev, ht.nil);

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

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

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

			ht.node_count -= 1;

			break;
		}
	}

	return result;
}

const u64 HASH_SEED = 5995;

pragma(inline) u64
Hash(T)(T[] value)
{
	return XXHash64(value.ptr, (T.sizeof * value.length) / u8.sizeof, HASH_SEED);
}

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

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

// TODO: probably needs improvement/testing
struct IntervalTimer
{
	Timer base;
	u64   interval;

	alias base this;
}

IntervalTimer
CreateFPSTimer(u64 fps)
{
	return CreateTimer(1000/fps);
}

IntervalTimer
CreateTimer(u64 ms)
{
	IntervalTimer timer = { base: CreateTimer()	};

	timer.interval = timer.cpu_freq * (ms/1000);

	return timer;
}

void
ResetTimer(IntervalTimer* t)
{
	static if(NativeTarget)
	{
		t.prev = RDTSC();
	}
	else
	{
		assert(false, NO_IMPL);
	}
}

pragma(inline) bool
CheckTimer(IntervalTimer* t)
{
	bool result = false;

	static if(NativeTarget)
	{
		u64 time = RDTSC();
		if(time - t.prev > t.interval)
		{
			result = true;
			t.prev = time;
		}
	}
	else
	{
		assert(false, NO_IMPL);
	}

	return result;
}

struct Timer
{
	u64 cpu_freq;
	u64 prev;
}

Timer
CreateTimer()
{
	static if(NativeTarget)
	{
		u64 ms_to_wait = 50;

		u64 os_freq    = OSTimeFreq();
		u64 cpu_start  = RDTSC();
		u64 os_start   = OSTime();
		u64 os_end     = 0;
		u64 os_elapsed = 0;

		u64 os_wait_time = os_freq * ms_to_wait / 1000;

		while (os_elapsed < os_wait_time)
		{
			os_end     = OSTime();
			os_elapsed = os_end - os_start;
		}

		u64 cpu_end     = RDTSC();
		u64 cpu_elapsed = cpu_end - cpu_start;
		u64 cpu_freq    = 0;
		if(os_elapsed)
		{
			cpu_freq = os_freq * cpu_elapsed / os_elapsed;
		}

		Timer timer = {
			cpu_freq: cpu_freq,
			prev:     RDTSC(),
		};
	}
	else
	{
		Timer timer;

		assert(false, NO_IMPL);
	}

	return timer;
}

pragma(inline) f32
DeltaTime(Timer* t)
{
	u64 time, step;

	static if(NativeTarget)
	{
		time   = RDTSC();
		step   = time - t.prev;
		t.prev = time;
	}
	else
	{
		assert(false, NO_IMPL);
	}

	return cast(f32)(step) / cast(f32)(t.cpu_freq);
}

static string
Replace(string target, u8 find, string replace)
{
	assert(__ctfe, "Function can only be run at compile time");

	u8[1024] result = '\0';

	usize count;
	foreach(ch; target)
	{
		if(ch == find)
		{
			for(usize i = 0; i < replace.length; i += 1)
			{
				result[count++] = replace[i];
			}
		}
		else
		{
			result[count++] = ch;
		}
	}

	return Str(result[0 .. count]);
}

static string
Str(T)(T v) if(isIntegral!(T))
{
	assert(__ctfe);

	if(v == 0)
	{
		return "0";
	}

	u8[] slice = new u8[32];

	enum base = 10;
	i32  i    = 30;

	for(; v && i; i--, v /= base)
	{
		slice[i] = r"0123456789"[v%base];
	}

	return Str(slice[i+1 .. $]);
}

void
MemSet(void* dst_p, i32 ch, u64 count) @nogc nothrow
{	
	u8[] slice = (cast(u8*)dst_p)[0 .. cast(usize)count];
	slice[]    =  cast(u8)(ch&0xFF);
}

void
MemCpy(void* dst_p, void* src_p, usize length)
{
	u8* dst = cast(u8*)dst_p;
	u8* src = cast(u8*)src_p;

	usize remaining = length;
	version(X86_64)
	{
		if(remaining >= 64)
		{
			for(u64 i = 0; i + 64 < length; i += 64)
			{
				asm
				{
					mov R8, src;
					mov R9, dst;

					add R8, i;
					movdqu XMM0, [R8+00];
					movdqu XMM1, [R8+16];
					movdqu XMM2, [R8+32];
					movdqu XMM3, [R8+48];

					add R9, i;
					movups [R9+00], XMM0;
					movups [R9+16], XMM1;
					movups [R9+32], XMM2;
					movups [R9+48], XMM3;

					sub remaining, 64;
				}
			}
		}

		if(remaining >= 32)
		{
			for(u64 i = length - remaining; i + 32 < length; i += 32)
			{
				asm
				{
					mov R8, src;
					mov R9, dst;

					add R8, i;
					movdqu XMM0, [R8+00];
					movdqu XMM1, [R8+16];

					add R9, i;
					movdqu [R9+00], XMM0;
					movdqu [R9+16], XMM1;

					sub remaining, 32;
				}
			}
		}
	}

	if(remaining > 0)
	{
		dst[length-remaining .. length] = src[length-remaining .. length];
	}
}

void
Assign(T, Args...)(T slice, Args args) if(isArray!(T))
{
	assert(slice.length == Args.length);

	static foreach(i; 0 .. Args.length)
	{
		slice[i] = cast(typeof(slice[0]))args[i];
	}
}

const u64 PRIME_1 = 11400714785074694791UL;
const u64 PRIME_2 = 14029467366897019727UL;
const u64 PRIME_3 = 1609587929392839161UL;
const u64 PRIME_4 = 9650029242287828579UL;
const u64 PRIME_5 = 2870177450012600261UL;

const u64 HASH_MAX_BUFFER_SIZE = 31+1;

struct XXHash
{
	u64[4]                   state;
	u8[HASH_MAX_BUFFER_SIZE] buffer;
	usize                    buffer_size;
	usize                    total_length;
}

static XXHash XXHASH;

u64
XXHash64(const(void)* input, u64 length, u64 seed)
{
	InitHash(seed);
	HashAdd(input, length);
	return GetHash();
}

void
InitHash(u64 seed)
{
	ref XXHash xxh = XXHASH;

	xxh.state[0] = seed + PRIME_1 + PRIME_2;
	xxh.state[1] = seed + PRIME_2;
	xxh.state[2] = seed;
	xxh.state[3] = seed - PRIME_1;

	xxh.buffer_size  = 0;
	xxh.total_length = 0;
}

bool
HashAdd(const(void)* input, u64 length)
{
	if(!input || length == 0) return false;

	ref XXHash xxh = XXHASH;

	xxh.total_length += length;

	const(u8)* data = cast(const(u8)*)input;

	if(xxh.buffer_size+length < HASH_MAX_BUFFER_SIZE)
	{
		for(;length--;)
		{
			xxh.buffer[xxh.buffer_size++] = *(data++);
		}

		return true;
	}

	const(u8)* stop       = data+length;
	const(u8)* stop_block = stop - HASH_MAX_BUFFER_SIZE;

	if(xxh.buffer_size > 0)
	{
		for(; xxh.buffer_size < HASH_MAX_BUFFER_SIZE;)
		{
			xxh.buffer[xxh.buffer_size++] = *data++;
		}

		ProcessHash(xxh.buffer.ptr, xxh.state[0], xxh.state[1], xxh.state[2], xxh.state[3]);
	}

	u64 s0 = xxh.state[0];
	u64 s1 = xxh.state[1];
	u64 s2 = xxh.state[2];
	u64 s3 = xxh.state[3];

	while(data <= stop_block)
	{
		ProcessHash(data, s0, s1, s2, s3);
		data += 32;
	}

	xxh.state[0] = s0;
	xxh.state[1] = s1;
	xxh.state[2] = s2;
	xxh.state[3] = s3;

	xxh.buffer_size = stop - data;
	for(u32 i = 0; i < xxh.buffer_size; i += 1)
	{
		xxh.buffer[i] = data[i];
	}

	return true;
}

u64
GetHash() 
{
	ref XXHash xxh = XXHASH;

	u64 result;
	if(xxh.total_length >= HASH_MAX_BUFFER_SIZE)
	{
		result = RotateLeft(xxh.state[0], 1)  + 
			       RotateLeft(xxh.state[1], 7)  +
						 RotateLeft(xxh.state[2], 12) +
						 RotateLeft(xxh.state[3], 18);

		result = (result ^ ProcessSingleHashValue(0, xxh.state[0])) * PRIME_1 + PRIME_4;
		result = (result ^ ProcessSingleHashValue(0, xxh.state[1])) * PRIME_1 + PRIME_4;
		result = (result ^ ProcessSingleHashValue(0, xxh.state[2])) * PRIME_1 + PRIME_4;
		result = (result ^ ProcessSingleHashValue(0, xxh.state[3])) * PRIME_1 + PRIME_4;
	}
	else
	{
		result = xxh.state[2] + PRIME_5;
	}

	result += xxh.total_length;

	const(u8)* data = xxh.buffer.ptr;
	const(u8)* stop = data + xxh.buffer_size;

	for(; data + 8 <= stop; data += 8)
	{
		result = RotateLeft(result ^ ProcessSingleHashValue(0, *cast(u64*)data), 27) * PRIME_1 + PRIME_4;
	}

	if(data+4 <= stop)
	{
		result = RotateLeft(result ^ (*cast(u32*)data) * PRIME_1, 23) * PRIME_2 + PRIME_3;
		data  += 4;
	}

	while(data != stop)
	{
		result = RotateLeft(result ^ (*data++) * PRIME_5, 11) * PRIME_1;
	}

	result ^= result >> 33;
	result *= PRIME_2;
	result ^= result >> 29;
	result *= PRIME_3;
	result ^= result >> 32;

	return result;
}

pragma(inline) u64
RotateLeft(u64 x, u8 bits)
{
	return (x << bits) | (x >> (64 - bits));
}

pragma(inline) u64
ProcessSingleHashValue(u64 previous, u64 input)
{
	return RotateLeft(previous + input*PRIME_2, 31) * PRIME_1;
}

pragma(inline) void
ProcessHash(const(void)* data, ref u64 state_0, ref u64 state_1, ref u64 state_2, ref u64 state_3)
{
	u64* block = cast(u64*)data;
	state_0 = ProcessSingleHashValue(state_0, block[0]);
	state_1 = ProcessSingleHashValue(state_1, block[1]);
	state_2 = ProcessSingleHashValue(state_2, block[2]);
	state_3 = ProcessSingleHashValue(state_3, block[3]);
}

version(DLIB_TEST) 
{
	void DLibTestUtil()
	{
		{ // Singly Linked List
			alias LT = ListBox!(u32, false);
			LinkedList!(LT) list;
			LT[5] nodes;
			foreach(u32 i, n; nodes)
			{
				nodes[i].value = i;
				SLLPush(&list, &nodes[i], null);
			}

			u32 count = 0;
			u32[3] res1 = [0, 2, 4];

			SLLRemove(&list, &nodes[1], &nodes[0], null);
			SLLRemove(&list, &nodes[3], &nodes[2], null);

			LT* n = list.first;

			assert(list.first != null && list.last != null);
			assert(n != null);
			assert(n.next != null);

			void TestSLList(LinkedList!(LT)* list, u32[] result)
			{
				LT* n = list.first;
				foreach(i, v; result)
				{
					assert(n != null);
					assert(v == n.value);

					if(i == result.length-1)
					{
						assert(n.next == null);
						assert(n == list.last);
					}

					n = n.next;
				}
			}

			TestSLList(&list, res1);

			count = 0;
			u32[5] res2 = [3, 0, 2, 4, 1];
			
			SLLPushFront(&list, &nodes[3], null);
			SLLPush(&list, &nodes[1], null);

			TestSLList(&list, res2);

			count = 0;

			SLLRemove(&list, &nodes[3], null, null);
			SLLRemove(&list, &nodes[1], &nodes[4], null);

			TestSLList(&list, res1);
		}

		{ // Doubly Linked List
			alias LT = ListBox!(u32, true);

			void TestDLList(T)(T* list, u32[] result)
			{
				LT* n = list.first;
				foreach(i, v; result)
				{
					assert(n != null);
					assert(v == n.value);

					if(i > 0)
					{
						assert(n.prev != null);
					}

					if(i == result.length-1)
					{
						assert(n.next == null);
						assert(n == list.last);
					}

					n = n.next;
				}

				n = list.last;
				foreach_reverse(i, v; result)
				{
					assert(n != null);
					assert(v == n.value);

					if(i == result.length-1)
					{
						assert(n.next == null);
					}

					if(i == 0)
					{
						assert(n.prev == null);
						assert(n == list.first);
					}

					n = n.prev;
				}
			}

			LinkedList!(LT) list;
			LT[5]           nodes;
			foreach(u32 i, n; nodes)
			{
				nodes[i].value = i;
				DLLPush(&list, &nodes[i], null);
			}

			assert(list.first != null && list.last != null);

			u32[5] test1 = [0, 1, 2, 3, 4];
			TestDLList(&list, test1);

			u32 count = 0;
			u32[3] res1 = [0, 2, 4];

			DLLRemove(&list, &nodes[1], null);
			DLLRemove(&list, &nodes[3], null);

			TestDLList(&list, res1);

			count = 0;
			u32[5] res2 = [3, 0, 2, 4, 1];

			DLLPushFront(&list, &nodes[3], null);
			DLLPush(&list, &nodes[1], null);

			TestDLList(&list, res2);

			DLLRemove(&list, &nodes[3], null);
			DLLRemove(&list, &nodes[1], null);

			TestDLList(&list, res1);

			DLLInsert(&list, &nodes[1], &nodes[0], null);
		}

		{ // MemCpy

			u8[777] bytes;
			u8[777] test_bytes;

			bytes[0 .. 123] = 123;
			bytes[123 .. 333] = 133;
			bytes[333 .. 655] = 155;
			bytes[655 .. $] = 199;

			test_bytes[0 .. $] = bytes[0 .. $];

			assert(test_bytes == bytes);

			test_bytes[0 .. $] = 0;

			MemCpy(test_bytes.ptr, bytes.ptr, 777);

			assert(test_bytes == bytes);

			test_bytes[0 .. $] = 0;
			
			MemCpy(test_bytes.ptr+100, bytes.ptr, 32);

			u32 count = 0;
			foreach(i, v; test_bytes[100 .. 132])
			{
				if(v != bytes[count])
				{
					assert(false);
				}

				count += 1;
			}

			assert(test_bytes[100 .. 132] == bytes[0 .. 32]);

			test_bytes[0 .. $] = 0;

			MemCpy(test_bytes.ptr, bytes.ptr, 33);

			assert(test_bytes[0 .. 33] == bytes[0 .. 33]);

			test_bytes[0 .. $] = 0;

			MemCpy(test_bytes.ptr, bytes.ptr, 65);

			assert(test_bytes[0 .. 65] == bytes[0 .. 65]);

			test_bytes[0 .. $] = 0;

			MemCpy(test_bytes.ptr, bytes.ptr, 96);

			foreach(i, v; test_bytes[0 .. 96])
			{
				if(v != bytes[i])
				{
					assert(false);
				}
			}

			assert(test_bytes[0 .. 96] == bytes[0 .. 96]);
		}

		{ // Hash Table
			auto table = CreateHashTable!(u64, u64)(10);

			table[100] = 100;
		}

		{ // Hash
			u8[10]  arr_1 = 5;
			u64[10] arr_2 = 555;
			u64 val_1     = 555555;
			u32 val_2     = 33333;

			u64 v1 = Hash(arr_1);
			u64 v2 = Hash(arr_2);
			u64 v3 = Hash(&val_1);

			assert(v1 > 0);
			assert(v2 > 0);
			assert(v3 > 0);
		}

		{ // Casts
			u8[] arr = CastStr!(u8)("Test");
			char[2] char_arr = ['a', 'b'];
			arr = CastArr!(u8)(char_arr);
		}

		{
			ResetScratch(MB(4));
			string str = Scratchf("%s testing %s", 55, "testing");
			assert(str == "55 testing testing");
		}

		{
			u64[555] slice = 53321;

			MemSet(slice.ptr, 0, u64.sizeof*slice.length);

			for(usize i = 0; i < slice.length; i += 1)
			{
				assert(slice[i] == 0);
			}

			MemSet(slice.ptr, 5, u64.sizeof*slice.length);

			u64 cmp = cast(u64)(
				cast(u64)(5) << 56 |
				cast(u64)(5) << 48 |
				cast(u64)(5) << 40 |
				cast(u64)(5) << 32 |
				cast(u64)(5) << 24 |
				cast(u64)(5) << 16 |
				cast(u64)(5) << 8  |
				cast(u64)(5) << 0  
			);

			for(usize i = 0; i < slice.length; i += 1)
			{
				assert(slice[i] == cmp);
			}
		}
	}

	unittest
	{
		DLibTestUtil();
	}
}
