module dlib.util;

import dlib.aliases;
import dlib.alloc;

import xxhash3;
import dlibincludes;

import std.stdio : write, writeln, writef, writefln, stderr;
import std.conv;
import std.string;
import std.traits;

import core.stdc.string : memset;
import core.simd;

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

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

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

string
ConvToStr(T)(T[] arr)
{
	return (cast(immutable(char)*)arr.ptr)[0 .. arr.length];
}

pragma(inline) bool
CondIncr(i64 step, T)(bool cond, T* val)
{
	if(cond)
	{
		*val += step;
	}

	return cond;
}

T[]
CastStr(T)(string str)
{
	T[] arr = (cast(T*)str.ptr)[0 .. str.length];
	return 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;
}

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

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

void
Errf(Args...)(string fmt, Args args)
{
	try
	{
		stderr.write("[ERROR]: ");
		stderr.writef(fmt, args, '\n');
	}
	catch(Exception e)
	{
		assert(false, "Incompatible format type");
	}
}

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

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

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

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

@nogc u64 
GB(u64 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(u64 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 DNode(T)
{
	DNode!(T)* next;
	DNode!(T)* prev;
	T         value;
}

struct DLList(T)
{
	DNode!(T)* first;
	DNode!(T)* 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)
{
	node.next = node.prev = nil;

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

struct Stack(T)
{
	Node!(T)* top;
	Node!(T)* free;
	Node!(T)* nil;
}

void
SPush(T)(Arena* arena, Stack!(T)* stack, T value)
{
	Node!(T)* node;
	if(!CheckNil(stack.nil, stack.free))
	{
		node = stack.free;
		stack.free = node.next;
	}
	else
	{
		node = Alloc!(Node!(T))(arena);
	}

	node.value = value;

	if(CheckNil(stack.nil, stack.top))
	{
		stack.top = node;
		node.next = stack.nil;
	}
	else
	{
		node.next = stack.top;
		stack.top = node;
	}
}

T
SPop(T)(Stack!(T)* stack)
{
	T result;

	if(!CheckNil(stack.nil, stack.top))
	{
		result = stack.top.value;
		
		Node!(T)* free_node = stack.top;
		stack.top = free_node.next;
		free_node.next = stack.free;
		stack.free = free_node;
	}

	return result;
}

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

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

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;
}

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;
	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)(u64 size)
{
	Arena arena = CreateArena(MB(4));
	auto nil = Alloc!(Node!(KVPair!(K, V)))(&arena);
	auto lists = Alloc!(SLList!(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) 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(!CheckNil(ht.nil, ht.free_lists.first))
	{
		node = SLLPop(&ht.free_lists, ht.nil);
	}
	else
	{
		node = Alloc!(N)(&ht.arena);
	}

	node.next = ht.nil;
	node.value.key = key;
	node.value.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.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;
}

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

			result.ok = true;
			result.value = node.value.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 xxh3_64bits_withSeed(value.ptr, (T.sizeof * value.length) / u8.sizeof, HASH_SEED);
}

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);
}

pragma(inline) u64
Hash(void* ptr_1, u64 len_1, void* ptr_2, u64 len_2)
{
	XXH3_state_t xxh;
	XXH3_INITSTATE(&xxh);
	xxh3_64bits_reset_withSeed(&xxh, HASH_SEED);
	xxh3_64bits_update(&xxh, ptr_1, len_1);
	xxh3_64bits_update(&xxh, ptr_2, len_2);

	return xxh3_64bits_digest(&xxh);
}

pragma(inline) u64
Hash(T, U)(T value_1, U value_2) if(isArray!(T) && isArray!(U))
{
	return Hash(value_1.ptr, value_1.length*T.sizeof, value_2.ptr, value_2.length*U.sizeof);
}

pragma(inline) u64
Hash(T, U)(T value_1, U value_2) if(isArray!(T) && !isArray!(U))
{
	return Hash(value_1.ptr, value_1.length*value_1[0].sizeof, &value_2, U.sizeof);
}

pragma(inline) u64
Hash(T, U)(T value_1, U value_2) if(!isArray!(T) && !isArray!(U))
{
	return Hash(&value_1, T.sizeof, &value_2, U.sizeof);
}

pragma(inline) u64
RDTSC()
{
	union u64_split
	{
		u64 full;
		struct 
		{
			u32 lower;
			u32 upper;
		};
	};

	u64_split val;
	u64_split* valp = &val;
	asm
	{
		cpuid;
		rdtsc;
		mov R8, valp;
		mov valp.upper.offsetof[R8], EDX;
		mov valp.lower.offsetof[R8], EAX;
	}

	return val.full;
}

pragma(inline) u64
OSTimeFreq()
{
	version (linux)
	{
		u64 freq = 1000000;
	}

	return freq;
}

pragma(inline) u64
OSTime()
{
	version(linux)
	{
		import core.sys.linux.sys.time;
		timeval value;
		gettimeofday(&value, null);

		u64 time = OSTimeFreq() * cast(u64)(value.tv_sec) + cast(u64)(value.tv_usec);
	}

	return time;
}

// TODO: probably needs improvement/testing
struct IntervalTimer
{
	u64 cpu_freq;
	u64 interval;
	u64 prev;
}

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

IntervalTimer
CreateTimer(u64 ms)
{
	IntervalTimer timer;

	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.cpu_freq = cpu_freq;
	timer.interval = cpu_freq*(ms/1000);
	timer.prev = RDTSC();

	return timer;
}

void
ResetTimer(IntervalTimer* t)
{
	t.prev = RDTSC();
}

pragma(inline) bool
CheckTimer(IntervalTimer* t)
{
	bool result = false;
	u64 time = RDTSC();
	if(time - t.prev > t.interval)
	{
		result = true;
		t.prev = time;
	}

	return result;
}

struct Timer
{
	u64 cpu_freq;
	u64 prev;
}

Timer
CreateTimer()
{
	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(),
	};

	return timer;
}

pragma(inline) f32
DeltaTime(Timer* t)
{
	u64 time = RDTSC();
	u64 step = time - t.prev;
	t.prev = time;
	return cast(f32)(step) / cast(f32)(t.cpu_freq);
}

static string
IntToStr(int n) nothrow pure @safe
{
	string result;

	static immutable string[] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
	if(n < table.length)
	{
		result = table[n];
	}
	else
	{
		result = to!string(n);
	}

	return result;
}

static string
GenerateLoop(string format_string, int N)() nothrow pure @safe
{
	string result;
	for (int i = 0; i < N; i++)
	{
		result ~= format_string.replace("@", IntToStr(i));
	}
	return result;
}

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

	u64 remaining = length;
	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];
	}
}

u8[]
Embed(string file_name)
{
	import std.file;
	return cast(u8[])read(file_name);
}

unittest
{
	{ // Singly Linked List
		SLList!(u32) list;
		Node!(u32)[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);

		Node!(u32)* n = list.first;

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

		void TestSLList(SLList!(u32)* list, u32[] result)
		{
			Node!(u32)* 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
		void TestDLList(DLList!(u32)* list, u32[] result)
		{
			DNode!(u32)* 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;
			}
		}

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

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

		TestDLList(&list, [0, 1, 2, 3, 4]);

		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
		import std.conv;

		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])
			{
				Logf("Failed %d %d %d", i, 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, arr_2);
		u64 v2 = Hash(arr_1, val_1);
		u64 v3 = Hash(val_1, val_2);

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

	{ // Stack
		Stack!(u32) stack;
		Arena arena = CreateArena(MB(1));

		u32 v1 = 1;
		u32 v2 = 2;
		u32 v3 = 3;

		Node!(u32) nil;

		stack.nil = &nil;

		SPush(&arena, &stack, v1);
		SPush(&arena, &stack, v2);
		SPush(&arena, &stack, v3);

		u32 count = 3;
		for (auto n = stack.top; !CheckNil(null, n); n = n.next, count -= 1)
		{
			assert(n.value == count);
		}

		count = 3;
		for (u32 n = SPop(&stack); n != 0; n = SPop(&stack), count -= 1)
		{
			assert(n == count);
		}

		assert(stack.top == &nil);
	}

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