#pragma once

#define STB_SPRINTF_IMPLEMENTATION

#include "stb/stb_sprintf.h"
#include "xxhash/xxhash.h"

#include "xxhash/xxhash.c"

#include <stdint.h>

#ifdef __linux__
#include <sys/types.h>
#endif

// ::SharedTypes::Attributes::Header::

#if COMPILER_MSVC || (COMPILER_CLANG && _WIN32)
# pragma section(".rdata$", read)
# define read_only __declspec(allocate(".rdata$"))
#elif (COMPILER_CLANG && __linux__)
# define read_only __attribute__((section(".rodata")))
#elif (COMPILER_GCC && __linux__)
# define read_only
#else
# error read_only not implemented for compiler/target
#endif

// ::SharedTypes::Types::Header::

typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;

typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

typedef char c8;

typedef intptr_t intptr;
typedef uintptr_t uintptr;

typedef float f32;
typedef double f64;

typedef uint8_t b8;
typedef uint32_t b32;

typedef void * rawptr;

typedef struct DataPtr
{
	rawptr ptr;
	u64 	 len;
} DataPtr;

typedef struct String8
{
	c8  *value;
	u64 len;
} String8;

#ifdef __linux__

typedef ssize_t isize;
typedef size_t usize;

#elif _WIN32

#	if defined ( _WIN64 )

typedef int64_t isize;
typedef u64 usize;

#	elif defined ( _WIN32 )

typedef i32 isize;
typedef u32 usize;

#	endif

#endif

typedef union
{
	struct { u32 upper, lower; };
	u64 full;
} U64Split;

typedef union
{
	struct { f32 r, g; };
	struct { f32 x, y; };
} Vec2;

typedef union
{
	struct { f32 x, y, z; };
	struct { f32 r, g, b; };
} Vec3;

typedef union
{
	struct { f32 x, y, z, w; };
	struct { f32 r, g, b, a; };
} Vec4;

typedef union
{
	struct { u16 x, y; };
	struct { u16 r, g; };
	struct { u16 w, h; };
} u16Vec2;

typedef union
{
	struct { i32 x, y; };
	struct { i32 r, g; };
} iVec2;

typedef union
{
	struct { i32 x, y, z; };
	struct { i32 r, g, b; };
} iVec3;

typedef union
{
	struct { i32 x, y, z, w; };
	struct { i32 r, g, b, a; };
} iVec4;

typedef union
{
	struct { i16 x, y; };
	struct { i16 r, g; };
	struct { i16 w, h; };
} i16Vec2;

typedef f32 Mat2[4];
typedef f32 Mat3[9];
typedef f32 Mat4[16];

typedef f64 dMat2[4];
typedef f64 dMat3[9];
typedef f64 dMat4[16];

// ::Util::Types::Header::

typedef struct TicketMut
{
	u32 volatile ticket;
	u32 volatile next_ticket;
} TicketMut;

typedef struct Mut
{
	b32 volatile lock;
} Mut;

typedef struct JobQueue
{
	u32 volatile queued;
	u32 volatile remaining;
} JobQueue;

// ::DataStructures::Types::Header::

#define HTQueuePop(f, l) SLLQueuePop(P_HT_NIL, f, l)
#define HTQueuePush(f, l, n) SLLQueuePush(P_HT_NIL, f, l, n)
#define HTQueuePushFront(f, l, n) SLLQueuePush(P_HT_NIL, f, l, n)

typedef struct KeyValuePair 
{
	union
	{
		u64 	  		key_u64;
		rawptr  		key_rawptr;
	};
	union
	{
		String8 		value_string;
		rawptr 			value_rawptr;
		u32    			value_u32;
		u64    			value_u64;
		U64Split 		value_u64_split;
	};
} KeyValuePair;

typedef struct HashNode
{
	struct HashNode *next;
	KeyValuePair 		v;
} HashNode;

typedef struct HashList
{
	HashNode *first;
	HashNode *last;
} HashList;
                                                                   
typedef struct HashTable                                        
{                                                                  
	HashList *lists;
	HashList free_lists;
	u64 count;
	u32 cap;
} HashTable;

typedef enum RBNodeColor_e
{
	RB_RED,
	RB_BLACK,
} RBNodeColor;

typedef enum RBNodeDir_e
{
	RB_LEFT,
	RB_RIGHT,
} RBNodeDir;

typedef struct RBDataNode
{
	rawptr 						 data;
	struct RBDataNode *next;
} RBDataNode;

typedef struct RBBucket
{
	RBDataNode *first;
	RBDataNode *last;
} RBBucket;

typedef struct RBNode
{
	union 
	{
		struct 
		{
			struct RBNode *left;
			struct RBNode *right;
		};
		struct RBNode *child[2];
	};
	struct RBNode *parent;
	u64 key;
	RBBucket bucket;
	RBNodeColor color;
} RBNode;

typedef struct RBTree
{
	RBNode *root;
	RBNode *nil;
} RBTree;

#define ARENA_HEADER_SIZE 32

typedef struct Arena
{
	u8		*buffer;
	usize length;
	usize pos;
	u32   init_line_no;
} Arena;

typedef struct TempArena
{
	Arena *arena;
	u64   pos;
} TempArena;

typedef struct AllocInfo
{
	rawptr 						ptr;
	struct AllocInfo *next;
} AllocInfo;

typedef struct Allocator
{
	RBTree 				 *tree;
	RBTree				 *used_tree;
	HashTable			 *hash_table;
	rawptr				 buffer;
	u64						 size;
	u64						 free_size;
	u64	           grow_size;
} Allocator;

typedef struct FLAllocHeader
{
	usize size;
	usize padding;
} FLAllocHeader;

typedef struct FLNode
{
	struct FLNode *next;
	usize  				size;
} FLNode;

typedef struct FreeList
{
	rawptr 		data;
	FLNode 	 *head;
	usize  		size;
	usize  		used;
	struct FreeList *next;
} FreeList;

typedef struct FLAlloc
{
	FreeList 	*list_head;
	TicketMut mut;
	FLNode   	*nil;
	usize    	grow_size;
} FLAlloc;

// *********************
// ***** PLATFORM ******
// *********************

// ::Platform::Types::Header::

typedef struct pLibrary pLibrary;
typedef struct pFunction pFunction;

// ::Platform::Declarations::Header::

typedef struct pGameInput pGameInput;

typedef enum KeyboardInput_e
{
	KB_NONE, 
	KB_A, KB_B, KB_C, KB_D, KB_E, KB_F, KB_G, KB_H, KB_I, KB_J, KB_K, KB_L, KB_M, 
	KB_N, KB_O, KB_P, KB_Q, KB_R, KB_S, KB_T, KB_U, KB_V, KB_W, KB_X, KB_Y, KB_Z, 
	KB_0, KB_1, KB_2, KB_3, KB_4, KB_5, KB_6, KB_7, KB_8, KB_9,
	KB_NUM_0, KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_4, KB_NUM_5, KB_NUM_6, KB_NUM_7, KB_NUM_8, KB_NUM_9,
	KB_NUM_LOCK, KB_NUM_SLASH, KB_NUM_STAR, KB_NUM_MINUS, KB_NUM_PLUS, KB_NUM_ENTER, KB_NUM_PERIOD,
	KB_INSERT, KB_DELETE, KB_HOME, KB_END, KB_PAGE_UP, KB_PAGE_DOWN, 
	KB_PRINT_SCREEN, KB_SCROLL_LOCK, KB_PAUSE,
	KB_COMMA, KB_PERIOD, KB_BACK_SLASH, KB_BACKSPACE, KB_FORWARD_SLASH, KB_MINUS, KB_PLUS,
	KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6, KB_F7, KB_F8, KB_F9, KB_F10, KB_F11, KB_F12, 
	KB_UP, KB_DOWN, KB_LEFT, KB_RIGHT,
	KB_LEFT_CTRL, KB_LEFT_ALT, KB_LEFT_SHIFT, KB_LEFT_SUPER,
	KB_TAB, KB_CAPS_LOCK,
	KB_RIGHT_CTRL, KB_RIGHT_ALT, KB_RIGHT_SUPER, KB_RIGHT_SHIFT,
	KB_ENTER, KB_SPACE,
	KB_TILDE, KB_ESC,
	KB_SEMICOLON, KB_QUOTE, KB_LEFT_BRACE, KB_RIGHT_BRACE, KB_BACK_SPACE,

	KB_MAX
} pKeyboardInput;

// ***********************
// ******** LINUX ********
// ***********************

#if __linux__

#	include <pthread.h>
#	include <signal.h>
#	include <limits.h>
#	include <stdint.h>
#	include <stdbool.h>
#	include <xcb/xcb.h>
#	include <X11/XKBlib.h>
#	include <X11/Xlib-xcb.h>
#	include <X11/Xlib.h>
#	include <X11/keysym.h>
#	include <sys/mman.h>
#	include <sys/types.h>
#	include <assert.h>
#	include <unistd.h>
#	include <errno.h>
#	include <stdarg.h>
#	include <stdlib.h>
#	include <dlfcn.h>
#	include <nmmintrin.h>
#	include <immintrin.h>
#	include <sched.h>
#	include <unistd.h>
#	include <dirent.h>
#	include <x86intrin.h>
#	include <sys/time.h>
#	include <fcntl.h>
# include <errno.h>

	// ::Platform::Linux::Defines::Header::

#	define SYS_ERR -1

#	define STDIN 0
#	define STDOUT 1
#	define STDERR 2

	// ::Platform::Linux::Macros::Header::

#define XCB_CHECK_CURRENT_ERROR(window, error, message) do {				\
		error = xcb_request_check(window->connection, cookie);						\
		if (error != NULL) {																							\
			EPrintf("%s ERROR CODE: %d\n", message, error->error_code);	\
			free(error);																										\
			return false;																										\
		}																																	\
	} while (0) 

#define XCB_CHECK_ERROR(window, cookie, error, message) do {				\
		error = xcb_request_check(window->connection, cookie);						\
		XCB_CHECK_CURRENT_ERROR(window, error, message);									\
	} while (0) 

	// ::Platform::Linux::Types::Header

	typedef int pFile;

	typedef struct pThread
	{
		pthread_t			  handle;
		pthread_cond_t  cond;
		pthread_mutex_t mut;
	} pThread;

	typedef struct pPlatformWindow
	{
		Display *display;
		xcb_connection_t *connection;
		xcb_window_t window;
		xcb_atom_t close_event;
		xcb_atom_t minimize_event;
		u16 w, h;
	} pPlatformWindow;

	typedef struct pLibrary
	{
		void *lib;
	} pLibrary;

	typedef struct pFunction
	{
		void *fn;
	} pFunction;

	typedef int pFile;

	// ::Platform::Linux::Globals::Start::

	static pPlatformWindow 
	linux_window = 
	{
		.w = 1920,
		.h = 1080,
	};

	b32 
	g_Global_Quit = false;

	// ::Platform::Linux::Globals::End::



	// ::Platform::Linux::Print::Functions::Header::

	i32 pWrite(int fd, void const *str, isize count);

	// ::Platform::Linux::Window::Functions::Header::

	void pWindowEventsGet(pGameInput *inputs, u32 *i_count);
	b32 pWindowEventWaitFor(pGameInput *input);
	void pWindowEventHandle(pGameInput *inputs, u32 *input_count, b32 wait_for_event);
	pKeyboardInput pInputEventConvert(u32 x_key);

	// ::Platform::Linux::Utils::Functions::Header::

	b32 pSyscallErrCheck(void *ptr);

#endif

// **************************
// ******** WINDOWS *********
// **************************

#if _WIN32

#	include <windows.h>
#	include <stdint.h>
#	include <stdbool.h>
#	include <immintrin.h>
#	include <stddef>


	// ::Platform::Windows::Defines::Header::

#	define WINDOW_CLASS_NAME "GearsWindowClass"

	// ::Platform::Windows::Types::Header::

	typedef ptrdiff_t ssize_t;

	typedef struct pPlatformWindow
	{
		HINSTANCE instance;
		HWND handle;
		u16 h, w;
		b32 resize_requested;
	} pPlatformWindow;

	typedef struct pLibrary
	{
		HMODULE module;
	} pLibrary;

	typedef struct pFunction
	{
		FARPROC fn;
	} pFunction;

	typedef struct pThread
	{
		HANDLE handle;
	} pThread;

	// ::Platform::Windows::Functions::::Header::

	void pWindowEventsGet();
	void pWindowEventWaitFor();

#endif // _WIN32

// ::Platform::Enum::Header::

typedef enum MouseInput_e
{
	M_NONE,
	M_LEFT_CLICK, 
	M_MIDDLE_CLICK, 
	M_RIGHT_CLICK,
} pMouseInput;

typedef enum GameInputType_e
{
	GI_NONE,
	GI_KEYBOARD, 
	GI_MOUSE, 
	GI_MOTION, 
	GI_GAMEPAD,
} pGameInputType;

// ::Platform::Types::Header::

typedef struct pWindowSize
{
	union
	{
		struct { u16 x, y; };
		struct { u16 w, h; };
	};
} pWindowSize;

typedef struct pMouseMotion
{
	union
	{
		struct { i16 x, y; };
		struct { i16 w, h; };
	};
} pMouseMotion;

typedef struct pGameInput
{
	union 
	{
		pKeyboardInput kb_code;
		pMouseInput m_code;
		pMouseMotion motion_ev;
	};
	b32 pressed;
	pGameInputType type;
}	pGameInput; // TODO: add gamepad input

typedef struct pThread pThread;

// ::Platform::ConsoleOut::Functions::Header::

i32 pWriteStdOut(void *buf, i32 len);
i32 pWriteStdErr(void *buf, i32 len);

// ::Platform::pLibrary::Functions::Header::

b32 pLibraryLoad(const char *name, pLibrary *out_lib);
b32 pFunctionLoad(const char *name, pLibrary *lib, pFunction *out_fn);

// ::Platform::Memory::Functions::Header::

void * pMemAlloc(usize size);
void * pMemAllocZeroed(usize size);
void * pMemRealloc(void * ptr, usize old_size, usize new_size);
void pMemFree(void * ptr, usize size);
usize pPageSize();

// ::Platform::Window::Functions::Header::

b32 pWindowInit(const char *window_name);
pWindowSize pWindowGetSize();
b32 pWindowShouldQuit();
pPlatformWindow *pWindowGet();

// ::Platform::FileSystem::Functions::Header::

typedef enum e_pFSAccess
{
	pFS_READ 	 = 0x01,
	pFS_WRITE  = 0x02,
	pFS_TRUNC  = 0x04,
	pFS_APPEND = 0x08,
	pFS_CREATE = 0x10,
} pFSAccess;

b32 pDirNavigate(char *dir);
char **pDirGetFileNames(Arena *arena, u32 *count);
static pFile pFileOpen(char *file_name, pFSAccess access);
static void pFileClose(pFile file);
static u64 pFileRead(pFile file, u64 offset, void * buf, u64 len);
static u64 pFileWrite(pFile file, u64 offset, void * buf, u64 len);
static u64 pFileSeek(pFile file, u64 pos);
static u64 pFileLength(pFile file);
static b32 pFSIsVisible(char *name, b32 is_dir);
static b32 pDirIsVisible(char *dir_name);
static b32 pFileIsVisible(char *file_name);
static b32 pFileCanAccess(char *file_name, pFSAccess access);

// ::Platform::Profiling::Functions::Header::

static u64 pOSTimerFreq();
static u64 pOSTimerRead();
static inline u64 pCPUTimerRead();

// ::Platform::Async::Functions::Header::

static pThread pThreadInit(void * proc, void * param);
static void pThreadSuspend(pThread *thread);
static void pThreadWake(pThread *thread);
static void pThreadKill();

// ::Platform::Atomics::Header::

static inline void pAtomicSignalFenceSeqCst();
static inline u32 pAtomicFetchSubU32(u32 volatile *ptr, u32 count);
static inline u32 pAtomicFetchIncrU32(u32 volatile *ptr);
static inline void pAtomicIncrU8(uint8_t volatile *ptr);
static inline void pAtomicIncrU32(u32 volatile *ptr);
static inline u32 pAtomicLoadU32(u32 volatile *ptr);
static inline void pAtomicStoreB32(b32 volatile *ptr, b32 value);
static inline b32 pAtomicCompareExchangeB32(b32 volatile *ptr, b32 *expect, b32 desired);

// ::Platform::ConsoleOut::Header::

i32 EPrint(void *str);
i32 Printf(char *fmt, ...);
i32 Printfln(char *fmt, ...);
i32 EPrintf(char *fmt, ...);
i32 _EPrint(void *str);
i32 _Printf(char *fmt, va_list arg);
i32 _Printfln(char *fmt, va_list arg);
i32 _EPrintf(char *fmt, va_list arg);

// ***********************
// ******** UTIL *********
// ***********************

// ::Util::Macros::Header::

#define Assert(condition, message) do { assert((condition) && (message)); } while(0)

#define KB(n) n			* 1024LL
#define MB(n) KB(n) * 1024LL
#define GB(n) MB(n) * 1024LL
#define TB(n) GB(n) * 1024LL

#define BitEq(var, bits) (((var) & (bits)) == (bits))
#define AlignPow2(x, b)	(((x) + (b) - 1) & (~((b) - 1)))	
#define IsPow2(x)				((x) != 0 && ((x) &((x) - 1)) == 0)
#define PtrAdd(ptr, add) ((rawptr)(((uintptr)ptr) + ((uintptr)add)))
#define PtrAddAdjustLen(ptr, len, add) \
ptr = PtrAdd(ptr, add); \
len -= add

#define Diff(value, sub) (value - sub)
#define MakeArray(arena, type, count) ArenaAlloc(arena, (isize)(sizeof(type)) * (isize)(count))
#define Len(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))

#define MakeString(x) #x

#define i8(x) ((i8)(x))
#define i16(x) ((i16)(x))
#define i32(x) ((i32)(x))
#define i64(x) ((i64)(x))

#define u8(x) ((u8)(x))
#define u16(x) ((u16)(x))
#define u32(x) ((u32)(x))
#define u64(x) ((u64)(x))

#define c8(x) ((c8)(x))
#define c8ptr(x) ((c8 *)(x))
#define u8ptr(x) ((u8 *)(x))
#define i8ptr(x) ((i8 *)(x))

#define intptr(x) ((intptr)(x))
#define uintptr(x) ((uintptr)(x))

#define f32(x) ((f32)(x))
#define f64(x) ((f64)(x))

#define rawptr(x) ((rawptr)(x))

#define cast(T, x) ((T)(x))

#define DefScalarSig(def) \
	DefSig##def(i8); \
	DefSig##def(i16); \
	DefSig##def(i32); \
	DefSig##def(i64); \
	DefSig##def(u8); \
	DefSig##def(u16); \
	DefSig##def(u32); \
	DefSig##def(u64); \
	DefSig##def(b8); \
	DefSig##def(b32)

#define DefScalarImpl(def) \
	Def##def(i8); \
	Def##def(i16); \
	Def##def(i32); \
	Def##def(i64); \
	Def##def(u8); \
	Def##def(u16); \
	Def##def(u32); \
	Def##def(u64); \
	Def##def(b8); \
	Def##def(b32)

#define ArrayType(T) \
typedef struct T##Array \
{ \
	T 	*data; \
	u64 length; \
} T##Array

#define PtrArrayType(T) \
typedef struct T##PtrArray \
{ \
 	T 	**data; \
	u64 	length; \
} T##PtrArray

#define InitArrayType(arr, arena, T, len) \
arr.data = MakeArray(arena, T, len); \
arr.length = len

// ::Util::LinkedList::Macros::

#define CheckNil(nil, p) ((p) == 0 || (p) == nil)
#define SetNil(nil, p) ((p) = nil)

// ::Util::DoubleHeadedLinkedList::Macros::

#define SLLConcatInPlaceNoCount(list, to_concat) do { \
	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; \
		} \
		MemZero(to_concat, sizeof(*to_concat)); \
	} \
} while (0)
#define _SLLQueuePop(nil, f, l, next) ((f) == (l) ? \
	(SetNil(nil, f), SetNil(nil, l)) : 								\
	((f) = (f)->next))
#define _SLLQueuePushFront(nil, f, l, n, next) (CheckNil(nil, f) ?  \
	((f) = (l) = (n), SetNil(nil, (n)->next)) : 											\
	((n)->next = (f), (f) = (n)))
#define _SLLQueuePush(nil, f, l, n, next) (CheckNil(nil, f) ? \
	((f) = (l) = (n), SetNil(nil, (n)->next)) :									\
	((l)->next = (n), (l) = (n), SetNil(nil, (n)->next)))

#define SLLQueuePop(nil, f, l) _SLLQueuePop(nil, f, l, next)
#define SLLQueuePush(nil, f, l, n) _SLLQueuePush(nil, f, l, n, next)
#define SLLQueuePushFront(nil, f, l, n) _SLLQueuePushFront(nil, f, l, n, next)

// ::Util::String8::Macros::

#define String8Array(v, c) MakeString8((c8 *)(v), sizeof(*(v))*(c)) 
#define String8Struct(v) MakeString8((c8 *)(v), sizeof(*(v)))
#define String8CStr(v) MakeString8((c8 *)v, StrLen(v))

// ::Util::Defines::Header::

constexpr u32 HM_MAX_SYMBOLS = 256;
constexpr usize DEFAULT_ALIGNMENT = (2*sizeof(rawptr));
constexpr u64 HASH_SEED = 5995;

// ::Util::Strings::Functions::Header::

static b32 String8Eq(String8 l, String8 r);
static u32 StrLen(const char *str);
static b32 StrEqL(const char *l, const char *r, u64 len);
static b32 StrEq(const char *l, const char *r);
static i32 SPrintf(char *buf, int len, rawptr fmt, ...);
static void StrConcat(c8 *str, c8 *append);
static String8 MakeString8(c8 *str, u64 len);
static String8 String8Concat(Arena *arena, String8 string, String8 append);
static void String8TrimSuffix(String8 *str, c8 delimiter);
static i64 String8FindLast(String8 str, c8 delimiter);
static String8 PreSplitString8(String8 string, String8 delimiter);
static String8 PreSplitNewString8(Arena *arena, String8 string, String8 delimiter);

// ::Util::Memory::Functions::Header::

void MemZero(void *ptr, isize size);
void MemCpy(rawptr dst, rawptr src, usize len);

// ::Util::Math::Functions::Header::

#define DefIntegerImpl(def) \
	Def##def(i8); \
	Def##def(i16); \
	Def##def(i32); \
	Def##def(i64); \
	Def##def(u8); \
	Def##def(u16); \
	Def##def(u32); \
	Def##def(u64)

#define DefFloatImpl(def) \
	Def##def(f32); \
	Def##def(f64)

#define DefMathImpl(def) \
	DefIntegerImpl(def); \
	DefFloatImpl(def)

#define DefMin(T) \
T Min##T(T l, T r) \
{ \
	return l < r ? l : r; \
}

#define DefMax(T) \
T Max##T(T l, T r) \
{ \
	return l > r ? l : r; \
}

#define DefClamp(T) \
T Clamp##T(T v, T min, T max) \
{ \
	return Min##T(max, Max##T(v, min)); \
}

#define DefAbs(T) \
T Abs##T(T v) \
{ \
	return v < (T)0 ? -v : v; \
}

// ::Util::Hashing::Functions::Header::

u64 static inline HashFromString(String8 string);

// ::Util::Profiling::Header::

#define NameConcat2(a, b) a##b

#define NameConcat(a, b) NameConcat2(a, b)

#define TimeBlockStart(name) ProfileBlock NameConcat(Block, name) = {0}; \
StartProfileBlock(&NameConcat(Block, name), #name, __COUNTER__ + 1)

#define TimeBlockEnd(name) EndProfileBlock(&NameConcat(Block, name))

#define TimeBlockFuncStart(name) ProfileBlock NameConcat(Block, function) = {0}; \
StartProfileBlock(& NameConcat(Block, function), name, __COUNTER__ + 1)

#define TimeFunctionStart TimeBlockFuncStart(__func__)

#define TimeFunctionEnd TimeBlockEnd(function)

typedef struct ProfileAnchor
{
	u64 tsc_elapsed;
	u64 hit_count;
	c8 *label;
} ProfileAnchor;

typedef struct ProfileBlock
{
	c8 *label;
	u64 start_tsc;
	u32 anchor_index;
} ProfileBlock;

typedef struct Profiler
{
	ProfileAnchor anchors[4096];
	u64 start_tsc;
	u64 end_tsc;
} Profiler;

static inline void StartProfileBlock(ProfileBlock *block, c8 *label, u32 anchor_index);
static inline void EndProfileBlock(ProfileBlock *block);


// ::Util::Async::Header::

static inline b32 MutTryLock(Mut *mut);
static inline void MutUnlock(Mut *mut);
static inline void TicketMutLock(TicketMut *mut);
static inline void TicketMutUnlock(TicketMut *mut);
static inline u32 JobQueueAdd(JobQueue *queue, u32 count);
static inline u32 JobQueueGetCount(JobQueue *queue);
static inline void JobQueueMarkUnqueued(JobQueue *queue, u32 count);
static inline void JobQueueMarkCompleted(JobQueue *queue, u32 count);
static inline void JobQueueReset(JobQueue *queue);
static inline b32 JobQueueCompleted(JobQueue *queue);

// **************************
// ******* ALLOCATORS *******
// **************************

// ::Allocator::Util::Header::

static inline usize CalcPaddingWithHeader(uintptr ptr, uintptr alignment, usize header_size);
static inline usize CalcPadding(uintptr ptr, uintptr alignment);

// ::Allocator::Arena::Header::


static Arena *ArenaInit(rawptr buffer, usize size);
static Arena *ArenaCreate(usize size);
static Arena *ArenaCreateDebug(usize size, u32 init_line_no);
static Arena *ArenaInitDebug(rawptr buffer, usize size, u32 init_line_no);
static rawptr ArenaAllocAlign(Arena *arena, usize size, usize align);
static rawptr ArenaAlloc(Arena *arena, usize size);
static void ArenaFree(Arena *arena);
static void ArenaFreeZeroed(Arena *arena);
static void DeallocArena(Arena *arena);

// ::Allocator::GlobalAlloc::Header::

static void InitAllocator(usize init_size);
static void DeinitAlloc();
static void AllocGrow(usize size);
static rawptr Alloc(usize size);
static rawptr AllocAlign(usize size, usize alignment);
static void Free(rawptr ptr);

// ::Allocator::FreeList::Header::

static void GlobalFreeListInit(usize size);
static rawptr FLMemAlloc(usize size);
static rawptr FLMemAllocZeroed(usize size);
static rawptr FLMemRealloc(rawptr old_ptr, usize size);
static void FLMemFree(rawptr ptr);
static usize FreeListPtrSize(FLAlloc *alloc, rawptr ptr);
static void FreeListInit(FLAlloc *alloc, usize size);
static rawptr FreeListAllocAlign(FLAlloc *alloc, usize size, usize alignment);
static rawptr FreeListAlloc(FLAlloc *alloc, usize size);
static void FreeListFree(FLAlloc *alloc, rawptr ptr);
static void FreeListFreeAll(FLAlloc *alloc);
static FreeList *FreeListGrow(FLAlloc *alloc, usize alloc_size);
static rawptr FreeListRealloc(FLAlloc *alloc, rawptr old_ptr, usize size);
static FLNode *FreeListSearch(FreeList *alloc, usize size, usize alignment, usize *out_padding, FLNode **prev_node);
static void FreeListCoalescence(FreeList *alloc, FLNode *prev_node, FLNode *free_node);
static void FreeListRemove(FLNode **head, FLNode *prev_node, FLNode *del_node);
static void FreeListInsert(FLNode **head, FLNode *prev_node, FLNode *new_node);
static FreeList *_FreeListFindList(FLAlloc *alloc, rawptr ptr);
static void _FreeListInit(FreeList **alloc, usize size);
static rawptr _FreeListAllocAlign(FreeList *alloc, usize size, usize alignment);
static void _FreeListFree(FreeList *alloc, rawptr ptr);

// ****************************
// ***** DATA STRUCTURES ******
// ****************************

// ::DataStructures::RedBlackTree::Header::

#define NodeDir(node) (node == node->parent->left ? RB_LEFT : RB_RIGHT)
#define RBQueuePop(f, l) SLLQueuePop(P_RB_DN_NIL, f, l)
#define RBQueuePush(f, l, n) SLLQueuePush(P_RB_DN_NIL, f, l, n)
#define RBQueuePushFront(f, l, n) SLLQueuePush(P_RB_DN_NIL, f, l, n)

static void RBTreeInit     (RBTree *tree);
static inline RBNode *RBTreeInitNode(u64 key, rawptr value);
static inline void RBTreePushDataNode(RBDataNode *first, RBDataNode *last, rawptr value);
static void RBTreeInsert(RBTree *tree, u64 key, rawptr value);
static b32 RBTreeSearchNearest(RBTree *tree, u64 key, RBNode **node);
static b32  RBTreeSearch     (RBTree *tree, u64 key, RBNode **node);
static void RBTreeDelete     (RBTree *tree, u64 key, rawptr value);
static void RBTreeLeftRotate (RBTree *tree, RBNode *node);
static void RBTreeRightRotate(RBTree *tree, RBNode *node);
static void RBTreeRotate     (RBTree *tree, RBNode *node, RBNodeDir dir);
static void RBTreeCorrect    (RBTree *tree, RBNode *node);
static void RBTreeTransplant (RBTree *tree, RBNode *node, RBNode *placed_node);

// ::DataStructures::HashTable::Functions::Header::
                                                                   
static void HashTableInit(HashTable *table, u32 init_size); 
static void HashTableClear(HashTable *table);
static void HashTableConcatInPlace(HashList *list, HashList *to_concat);
static u64 HashTableHash(String8 str);
static HashNode *HashListPop(HashList *list);
static KeyValuePair *HashTableSearchU64(HashTable *table, u64 key);
static KeyValuePair *HashTableSearchRawptr(HashTable *table, rawptr key);
static HashNode *HashTablePush(HashTable *table, u64 hash, KeyValuePair value);
static HashNode *HashTablePushU64U32(HashTable *table, u64 key, u32 value);
static HashNode *HashTablePushU64U64(HashTable *table, u64 key, u64 value);
static HashNode *HashTablePushU64String8(HashTable *table, u64 key, String8 value);
static HashNode *HashTablePushU64Rawptr(HashTable *table, u64 key, rawptr value);
static HashNode *HashTablePushU64U64Split(HashTable *table, u64 key, u32 upper, u32 lower);
static rawptr HashTableDeleteU64Rawptr(HashTable *table, u64 key);
static void HashTableDeleteU64(HashTable *table, u64 key);

#ifdef STG_IMPLEMENTATION

#ifdef __linux__

	// ::Platform::Functions::pLibrary::Start::

	b32 
	pLibraryLoad(const char *name, pLibrary *out_lib) 
	{
		if (!name) {
			return false;
		}

		out_lib->lib = dlopen(name, RTLD_NOW);
		if (!out_lib->lib) {
			return false;
		}

		return true;
	}

	b32 
	pFunctionLoad(const char *name, pLibrary *lib, pFunction *out_fn) 
	{
		if (!name) {
			return false;
		}

		out_fn->fn = dlsym(lib->lib, name);
		if (!out_fn->fn) {
			Printf("unable to find function\n");
			return false;
		}

		return true;
	}

	// ::Platform::Functions::pLibrary::End::



	// ::Platform::Functions::Print::Start::


	// ::Platform::Functions::Print::End::



	// ::Platform::Functions::Memory::Start::

	void * 
	pMemAlloc(size_t size) 
	{
		void * addr = mmap(
			NULL, 
			size, 
			PROT_READ | PROT_WRITE, 
			MAP_ANON | MAP_PRIVATE,
			-1,
			0
		);

		if (pSyscallErrCheck(addr)) addr = NULL;

		return addr;
	}

	void * 
	pMemAllocZeroed(size_t size)
	{
		void * ptr = pMemAlloc(size);
		MemZero(ptr, size);
		return ptr;
	}

	void * 
	pMemRealloc(void * ptr, size_t old_size, size_t new_size)
	{
		void * addr = mremap(
			ptr,
			old_size,
			new_size,
			MAP_ANON | MAP_PRIVATE
		);

		if (pSyscallErrCheck(addr)) addr = NULL;

		return addr;
	}

	void 
	pMemFree(void * ptr, size_t size)
	{
		Assert(munmap(ptr, size) == 0, "munmap failed");
	}

	size_t 
	pPageSize()
	{
		return (size_t)sysconf(_SC_PAGESIZE);
	}

	// ::Platform::Functions::Memory::End::



	// ::Platform::Functions::Window::Start::

	b32 
	pWindowInit(const char *window_name) 
	{
		pPlatformWindow *window = &linux_window;

		window->display = XOpenDisplay(NULL);
		if (!window->display)
			return false;

		window->connection = XGetXCBConnection(window->display);

		if (!window->connection) 
			return false;

		xcb_void_cookie_t cookie;
		xcb_generic_error_t *error;

		const xcb_setup_t *setup = xcb_get_setup(window->connection);
		xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
		xcb_screen_t *screen = iter.data;

		const int event_mask = XCB_EVENT_MASK_EXPOSURE				|
													 XCB_EVENT_MASK_KEY_PRESS				|
													 XCB_EVENT_MASK_KEY_RELEASE			|
													 XCB_EVENT_MASK_BUTTON_PRESS		|
													 XCB_EVENT_MASK_BUTTON_RELEASE	|
													 XCB_EVENT_MASK_POINTER_MOTION	|
													 XCB_EVENT_MASK_STRUCTURE_NOTIFY;

		const int val_win[] = {screen->black_pixel, event_mask};
		const int val_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;

		window->window = xcb_generate_id(window->connection);

		cookie = xcb_create_window(
			window->connection,
			XCB_COPY_FROM_PARENT,
			window->window,
			screen->root,
			0, // x pos
			0, // y pos
			window->w, // width
			window->h, // height
			0,
			XCB_WINDOW_CLASS_INPUT_OUTPUT,
			screen->root_visual,
			val_mask,
			val_win
		);
		XCB_CHECK_ERROR(window, cookie, error, "Failed to create window.");

		cookie = xcb_map_window_checked(window->connection, window->window);
		XCB_CHECK_ERROR(window, cookie, error, "Failed to map window.");

		cookie = xcb_change_property_checked(
			window->connection,
			XCB_PROP_MODE_REPLACE,
			window->window,
			XCB_ATOM_WM_NAME,
			XCB_ATOM_STRING,
			8,
			StrLen(window_name),
			window_name
		);
		XCB_CHECK_ERROR(window, cookie, error, "Failed to rename window.");

		xcb_intern_atom_cookie_t c_proto = xcb_intern_atom(window->connection, 1, 12, "WM_PROTOCOLS");
		xcb_intern_atom_reply_t *r_proto = xcb_intern_atom_reply(window->connection, c_proto, &error);
		XCB_CHECK_CURRENT_ERROR(window, error, "Failed to get WM_PROTOCOLS.");

		xcb_intern_atom_cookie_t c_close = xcb_intern_atom(window->connection, 0, 16, "WM_DELETE_WINDOW");
		xcb_intern_atom_reply_t *r_close = xcb_intern_atom_reply(window->connection, c_close, &error);
		XCB_CHECK_CURRENT_ERROR(window, error, "Failed to get WM_DELETE_WINDOW.");

		xcb_intern_atom_cookie_t c_minimize = xcb_intern_atom(window->connection, 0, 20, "_NET_WM_STATE_HIDDEN");
		xcb_intern_atom_reply_t *r_minimize = xcb_intern_atom_reply(window->connection, c_minimize, &error);
		XCB_CHECK_CURRENT_ERROR(window, error, "Failed to get _NET_WM_STATE_HIDDEN");

		cookie = xcb_change_property_checked(
			window->connection,
			XCB_PROP_MODE_REPLACE,
			window->window,
			r_proto->atom,
			XCB_ATOM_ATOM,
			32,
			1,
			&r_close->atom
		);
		XCB_CHECK_ERROR(window, cookie, error, "Failed to set window close event.");

		window->close_event = r_close->atom;
		window->minimize_event = r_minimize->atom;

		free(r_proto);
		free(r_close);
		free(r_minimize);

		xcb_map_window(window->connection, window->window);

		i32 stream_result = xcb_flush(window->connection);
		if (stream_result <= 0)
		{
			Printfln("Error flushing the stream: %d", stream_result);
			return false;
		}

		return true;
	}

	pWindowSize 
	pWindowGetSize()
	{
		return (pWindowSize) {
			.w = linux_window.w,
			.h = linux_window.h,
		};
	}

	pPlatformWindow 
	*pWindowGet() 
	{
		return &linux_window;
	}

	b32 
	pWindowShouldQuit()
	{
		return false;
	}

	// ::Platform::Functions::Window::End::



	// ::Platform::FileSystem::Functions::Start::

	b32 
	pDirNavigate(char *dir)
	{
		return chdir(dir);
	}

	static pFile 
	pFileOpen(char *file_name, pFSAccess acc)
	{
		int flags = 0;
		if (BitEq(acc, pFS_READ) && BitEq(acc, pFS_WRITE))
			flags = O_RDWR;
		else if (BitEq(acc, pFS_READ))
			flags = O_RDONLY;
		else if (BitEq(acc, pFS_WRITE))
			flags = O_WRONLY;

		if (BitEq(acc, pFS_TRUNC))
		{
			flags |= O_TRUNC;
		}
		else if (BitEq(acc, pFS_APPEND))
			flags |= O_APPEND;

		if (BitEq(acc, pFS_CREATE))
			flags |= O_CREAT;
		
		i64 file = open(file_name, flags, S_IRUSR|S_IWUSR|S_IRGRP);
		if (file < 0)
		{
			Printfln("errno: %d", errno);
			Assert(false, "failed to open file");
		}	

		return file;
	}

	static void 
	pFileClose(pFile file)
	{
		fsync(file);
		close(file);
	}

	static u64 
	pFileRead(pFile file, u64 offset, void * buf, u64 len)
	{
		i64 new_offset = lseek(file, offset, SEEK_SET);
		if (new_offset < 0)
		{
			Printfln("errno: %d", errno);
			Assert(false, "failed to read file");
		}
		return read(file, buf, (size_t)len);
	}

	static u64 
	pFileWrite(pFile file, u64 offset, void * buf, u64 len)
	{
		lseek(file, offset, SEEK_SET);
		return write(file, buf, (size_t)len);
	}

	static u64 
	pFileSeek(pFile file, u64 pos)
	{
		return (u64)lseek(file, (ssize_t)pos, SEEK_SET);
	}

	static u64 
	pFileLength(pFile file)
	{
		ssize_t offset = lseek(file, 0, SEEK_CUR);
		ssize_t size = lseek(file, 0, SEEK_END);
		lseek(file, offset, SEEK_SET);

		if (size == -1)
			size = UINT64_MAX;

		return (u64)size;
	}

	char **
	pDirGetFileNames(Arena *arena, u32 *count)
	{
		struct dirent *dir;

		DIR *d = opendir(".");

		*count = 0;
		if (d)
		{
			while ((dir = readdir(d)) != NULL)
			{
				if (!StrEq(dir->d_name, ".") && !StrEq(dir->d_name, ".."))
					*count += 1;
			}
		}

		char **file_names = MakeArray(arena, u8*, *count);

		d = opendir(".");
		*count = 0;
		if (d)
		{
			while ((dir = readdir(d)) != NULL)
			{
				if (!StrEq(dir->d_name, ".") && !StrEq(dir->d_name, ".."))
				{
					i32 str_len = StrLen(dir->d_name);
					file_names[*count] = MakeArray(arena, u8, str_len);
					MemCpy(file_names[*count], dir->d_name, str_len);
					*count += 1;
				}
			}
		}

		return (char **)file_names;
	}

	static b32 
	pFSIsVisible(char *name, b32 is_dir)
	{
		b32 found = false;

		struct dirent *dir;
		DIR *d = opendir(".");
		u8 type = is_dir ? DT_DIR : DT_REG;

		if (d)
		{
			while ((dir = readdir(d)) != NULL)
			{
				if (StrEq(name, dir->d_name) && dir->d_type == type)
				{
					found = true;
					break;
				}
			}
		}

		return found;
	}

	static b32 
	pDirIsVisible(char *dir_name)
	{
		return pFSIsVisible(dir_name, true);
	}

	static b32 
	pFileIsVisible(char *file_name)
	{
		return pFSIsVisible(file_name, false);
	}

	static b32 
	pFileCanAccess(char *file_name, pFSAccess file_access)
	{
		int a = 0;
		if (BitEq(file_access, pFS_READ))
			a |= R_OK;
		if (BitEq(file_access, pFS_WRITE))
			a |= W_OK;

		return access(file_name, a) == 0;	
	}

	// ::Platform::FileSystem::Functions::End::



	// ::Platform::Profiling::Functions::Start::

	static u64 
	pOSTimerFreq()
	{
		return 1000000;
	}

	static u64 
	pOSTimerRead()
	{
		struct timeval value;
		gettimeofday(&value, 0);

		return pOSTimerFreq() * u64(value.tv_sec) + u64(value.tv_usec);
	}

	static inline u64 
	pCPUTimerRead()
	{
		return __rdtsc();
	}

	// ::Platform::Profiling::Functions::End::



	// ::Platform::Atomics::Functions::Start::

	static inline void 
	pAtomicSignalFenceSeqCst()
	{
		__atomic_signal_fence(__ATOMIC_SEQ_CST);
	}

	static inline u32 
	pAtomicFetchSubU32(u32 volatile *ptr, u32 count)
	{
		return __atomic_fetch_sub(ptr, count, __ATOMIC_ACQUIRE);
	}

	static inline u32 
	pAtomicFetchIncrU32(u32 volatile *ptr)
	{
		return __atomic_fetch_add(ptr, (u32)1, __ATOMIC_ACQUIRE);
	}

	static inline void 
	pAtomicIncrU32(u32 volatile *ptr)
	{
		__atomic_fetch_add(ptr, (u32)1, __ATOMIC_RELEASE);
	}

	static inline u32 
	pAtomicLoadU32(u32 volatile *ptr)
	{
		return __atomic_load_n(ptr, __ATOMIC_ACQUIRE);
	}

	static inline void 
	pAtomicStoreB32(b32 volatile *ptr, b32 value)
	{
		__atomic_store_n(ptr, value, __ATOMIC_RELEASE);
	}

	static inline b32 
	pAtomicCompareExchangeB32(b32 volatile *ptr, b32 *expected, b32 desired)
	{
		return __atomic_compare_exchange_n(ptr, expected, desired, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED); 
	}

	// ::Platform::Atomics::Functions::End::



	// ::Platform::Linux::Print::Functions::Start::

	i32 
	pWriteStdOut(void * buf, i32 len)
	{
		return (i32)write(STDOUT, buf, len);
	}

	i32 
	pWriteStdErr(void * buf, i32 len)
	{
		return (i32)write(STDERR, buf, len);
	}

	// ::Platform::Linux::Window::Functions::Start::

	void 
	pWindowEventsGet(pGameInput *inputs, u32 *i_count)
	{
		pWindowEventHandle(inputs, i_count, false);
	}

	b32 
	pWindowEventWaitFor(pGameInput *input)
	{
		u32 i_count;
		pWindowEventHandle(input, &i_count, true);
		return i_count > 0;
	}

	void 
	pWindowEventHandle(pGameInput *inputs, u32 *i_count, b32 wait_for_event)
	{
		b32 has_max_inputs = false;
		*i_count = 0;

		do 
		{
			xcb_generic_event_t *e;
			if (wait_for_event)
				e = xcb_wait_for_event(linux_window.connection);
			else 
				e = xcb_poll_for_event(linux_window.connection);
			
			// XCB_UNMAP_NOTIFY
			if (e != NULL)
			{
				switch (e->response_type & ~0x80)
				{
					case XCB_CLIENT_MESSAGE:
						{
							xcb_client_message_event_t *msg = (xcb_client_message_event_t *)e;
							if (msg->window != linux_window.window)
								break;

							if (msg->data.data32[0] == linux_window.close_event)
								g_Global_Quit = true;
						} break;
					case XCB_CONFIGURE_NOTIFY:
						{
							xcb_configure_notify_event_t *configure_event = (xcb_configure_notify_event_t *)e;

							if (linux_window.w != configure_event->width || linux_window.h != configure_event->height)
							{
								linux_window.w = configure_event->width;
								linux_window.h = configure_event->height;
							}
						} break;
					case XCB_KEY_RELEASE:
					case XCB_KEY_PRESS:
						{
							xcb_key_press_event_t *keyboard_e = (xcb_key_press_event_t *)e;
							b8 pressed = e->response_type == XCB_KEY_PRESS;
							xcb_keycode_t code = keyboard_e->detail;
							KeySym keysym = XkbKeycodeToKeysym(linux_window.display, (KeyCode)code, 0, 0);
							pKeyboardInput input = pInputEventConvert(keysym);
							if (input != KB_NONE)
							{
								inputs[*i_count].kb_code = input;
								inputs[*i_count].pressed = pressed;
								inputs[*i_count].type = GI_KEYBOARD;
								*i_count += 1;

								if (*i_count == 10)
									has_max_inputs = true;
							}

						} break;
					case XCB_BUTTON_PRESS: 
					case XCB_BUTTON_RELEASE:
						{
							xcb_button_press_event_t *mouse_ev = (xcb_button_press_event_t *)e;
							b8 pressed = e->response_type == XCB_BUTTON_PRESS;
							pMouseInput input = M_NONE;
							
							if (mouse_ev->detail == XCB_BUTTON_INDEX_1)
								input = M_LEFT_CLICK;
							else if (mouse_ev->detail == XCB_BUTTON_INDEX_2)
								input = M_MIDDLE_CLICK;
							else if (mouse_ev->detail == XCB_BUTTON_INDEX_3)
								input = M_RIGHT_CLICK;

							if (input != M_NONE)
							{
								inputs[*i_count].m_code = input;
								inputs[*i_count].pressed = pressed;
								inputs[*i_count].type = GI_MOUSE;
								*i_count += 1;

								if (*i_count == 10)
									has_max_inputs = true;
							}
						} break;
					case XCB_MOTION_NOTIFY: 
						{
							xcb_motion_notify_event_t *move_ev = (xcb_motion_notify_event_t *)e;
							
							if (move_ev->event_x > 0 || move_ev->event_y > 0)
							{
								inputs[*i_count].motion_ev.x = move_ev->event_x;
								inputs[*i_count].motion_ev.y = move_ev->event_y;
								inputs[*i_count].type = GI_MOTION;

								*i_count += 1;
								if (*i_count == 10)
									has_max_inputs = true;
							}
						}
					default:
						break;
				}

				free(e);
			}
			else
			{
				break;
			}
		} while(!wait_for_event && !has_max_inputs);
	}

	pKeyboardInput 
	pInputEventConvert(u32 x_key)
	{
		switch (x_key)
		{
			case XK_BackSpace: 		return KB_BACKSPACE;
			case XK_Return: 			return KB_ENTER;
			case XK_Tab: 					return KB_TAB;
			case XK_Pause:				return KB_PAUSE;
			case XK_Caps_Lock: 		return KB_CAPS_LOCK;
			case XK_Escape:				return KB_ESC;
			case XK_space: 				return KB_SPACE;
			case XK_Prior:				return KB_PAGE_UP;
			case XK_Next:					return KB_PAGE_DOWN;
			case XK_End:					return KB_END;
			case XK_Home:					return KB_HOME;
			case XK_Left:					return KB_LEFT;
			case XK_Up:						return KB_UP;
			case XK_Right:				return KB_RIGHT;
			case XK_Down:					return KB_DOWN;
			case XK_Print:				return KB_PRINT_SCREEN;
			case XK_Insert:				return KB_INSERT;
			case XK_Delete: 			return KB_DELETE;
			case XK_Meta_L:
			case XK_Super_L:			return KB_LEFT_SUPER;
			case XK_Meta_R:
			case XK_Super_R: 			return KB_RIGHT_SUPER;
			case XK_KP_0:					return KB_NUM_0;
			case XK_KP_1: 				return KB_NUM_1;
			case XK_KP_2: 				return KB_NUM_2;
			case XK_KP_3: 				return KB_NUM_3;
			case XK_KP_4: 				return KB_NUM_4;
			case XK_KP_5: 				return KB_NUM_5;
			case XK_KP_6: 				return KB_NUM_6;
			case XK_KP_7: 				return KB_NUM_7;
			case XK_KP_8: 				return KB_NUM_8;
			case XK_KP_9: 				return KB_NUM_9;
			case XK_multiply:			return KB_NUM_STAR;
			case XK_KP_Subtract:	return KB_NUM_MINUS;
			case XK_KP_Decimal:		return KB_NUM_PERIOD;
			case XK_KP_Divide:		return KB_NUM_SLASH;
			case XK_KP_Add:				return KB_NUM_PLUS;
			case XK_F1:						return KB_F1;
			case XK_F2:						return KB_F2;
			case XK_F3:						return KB_F3;
			case XK_F4:						return KB_F4;
			case XK_F5:						return KB_F5;
			case XK_F6:						return KB_F6;
			case XK_F7:						return KB_F7;
			case XK_F8:						return KB_F8;
			case XK_F9:						return KB_F9;
			case XK_F10:					return KB_F10;
			case XK_F11:					return KB_F11;
			case XK_F12:					return KB_F12;
			case XK_Num_Lock:			return KB_NUM_LOCK;
			case XK_Scroll_Lock:	return KB_SCROLL_LOCK;
			case XK_Shift_L:			return KB_LEFT_SHIFT;
			case XK_Shift_R: 			return KB_RIGHT_SHIFT;
			case XK_Control_L:		return KB_LEFT_CTRL;
			case XK_Control_R:		return KB_RIGHT_CTRL;
			case XK_Alt_L: 				return KB_LEFT_ALT;
			case XK_Alt_R:				return KB_RIGHT_ALT;
			case XK_semicolon:		return KB_SEMICOLON;
			case XK_bracketleft:	return KB_LEFT_BRACE;
			case XK_bracketright:	return KB_RIGHT_BRACE;
			case XK_plus:					return KB_PLUS;
			case XK_comma: 				return KB_COMMA;
			case XK_minus:				return KB_MINUS;
			case XK_backslash:		return KB_BACK_SLASH;
			case XK_slash:				return KB_FORWARD_SLASH;
			case XK_grave: 				return KB_TILDE;
			case XK_0:						return KB_0;
			case XK_1:						return KB_1;
			case XK_2:						return KB_2;
			case XK_3:						return KB_3;
			case XK_4:						return KB_4;
			case XK_5:						return KB_5;
			case XK_6:						return KB_6;
			case XK_7:						return KB_7;
			case XK_8:						return KB_8;
			case XK_9:						return KB_9;
			case XK_a:
			case XK_A:						return KB_A;
			case XK_b:
			case XK_B:						return KB_B;
			case XK_c:
			case XK_C:						return KB_C;
			case XK_d:
			case XK_D:						return KB_D;
			case XK_e:
			case XK_E:						return KB_E;
			case XK_f:
			case XK_F:						return KB_F;
			case XK_g:
			case XK_G:						return KB_G;
			case XK_h:
			case XK_H:						return KB_H;
			case XK_i:
			case XK_I:						return KB_I;
			case XK_j:
			case XK_J:						return KB_J;
			case XK_k:
			case XK_K:						return KB_K;
			case XK_l:
			case XK_L:						return KB_L;
			case XK_m:
			case XK_M:						return KB_M;
			case XK_n:
			case XK_N:						return KB_N;
			case XK_o:
			case XK_O:						return KB_O;
			case XK_p:
			case XK_P:						return KB_P;
			case XK_q:
			case XK_Q:						return KB_Q;
			case XK_r:
			case XK_R:						return KB_R;
			case XK_s:
			case XK_S:						return KB_S;
			case XK_t:
			case XK_T:						return KB_T;
			case XK_u:
			case XK_U:						return KB_U;
			case XK_v:
			case XK_V:						return KB_V;
			case XK_w:
			case XK_W:						return KB_W;
			case XK_x:
			case XK_X:						return KB_X;
			case XK_y:
			case XK_Y:						return KB_Y;
			case XK_z:
			case XK_Z:						return KB_Z;
			default: 							return KB_NONE;
		}
	}

	// ::Platform::Linux::Window::Functions::Start::



	// ::Platform::Linux::Utils::Functions::Start::

	b32 
	pSyscallErrCheck(void *ptr) 
	{
		return (isize)ptr == SYS_ERR ? true : false;
	}

	// ::Platform::Linux::Utils::Functions::End::



	// ::Platform::Linux::Async::Start::

	static pThread 
	pThreadInit(void * proc, void * param)
	{
		pThread thread = {0};
		Assert(pthread_mutex_init(&thread.mut, NULL) == 0, "pthread_mutex_init failure");
		Assert(pthread_cond_init(&thread.cond, NULL) == 0, "pthread_cond_init failure");
		Assert(pthread_create(&thread.handle, NULL, proc, param) == 0, "pthread_create failure");

		return thread;
	}

	static void 
	pThreadSuspend(pThread *thread)
	{
		pthread_mutex_lock(&thread->mut);
		pthread_cond_wait(&thread->cond, &thread->mut);
		pthread_mutex_unlock(&thread->mut);
	}

	static void 
	pThreadWake(pThread *thread)
	{
		pthread_cond_signal(&thread->cond);
	}

	static void 
	pThreadKill()
	{
		pthread_exit(NULL);
	}

	// ::Platform::Linux::Async::Start::

#endif // __linux__

#ifdef _WIN32

	// ::Platform::Windows::Memory::Start::

	rawptr 
	pMemAlloc(usize size)
	{
		return (rawptr)VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
	}

	rawptr 
	pMemAllocZeroed(usize size)
	{
		rawptr mem = pMemAlloc(size);
		MemZero(mem, size);
		return mem;
	}

	void 
	pMemFree(rawptr ptr, usize size)
	{
		Assert(VirtualFree(ptr, size, MEM_RELEASE), "pMemFree failure");
	}

	// ::Platform::Windows::Memory::End::



	// ::Platform::Windows::Print::Start::

	i32 
	pWriteStdOut(rawptr buf, i32 len)
	{
		return WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), buf, len, NULL, NULL);
	}

	i32 
	pWriteStdErr(rawptr buf, i32 len)
	{
		return WriteConsole(GetStdHandle(STD_ERROR_HANDLE), buf, len, NULL, NULL);
	}

	// ::Platform::Windows::Print::End::



	// ::Platform::Functions::Directory::Start::

	b32 
	pDirNavigate(c8 *dir)
	{
		return !(b32)SetCurrentDirectory(dir);
	}

	// ::Platform::Functions::Directory::End::



	// ::Platform::Windows::Profiling::Functions::Start::

	static inline u64 
	pCPUTimerRead()
	{
		return __rdtsc();
	}

	// ::Platform::Windows::Profiling::Functions::End::



	// ::Platform::Windows::Async::Start::

	static pThread 
	pThreadInit(rawptr proc, rawptr param)
	{
		pThread thread = {0};
		CreateThread(NULL, 0, proc, param, 0, NULL);

		return thread;
	}

	static void 
	pThreadSuspend(pThread *thread)
	{
		SuspendThread(thread->handle);
	}

	static void 
	pThreadWake(pThread *thread)
	{
		ResumeThread(thread->handle);
	}

	static void 
	pThreadKill()
	{
		ExitThread(0);
	}

	// ::Platform::Windows::Async::End::



	// ::Platform::Windows::Atomics::Start::


	static inline void 
	pAtomicSignalFenceSeqCst()
	{
		_ReadWriteBarrier();
	}

	static inline u32 
	pAtomicFetchSubU32(u32 volatile *ptr, u32 count)
	{
		LONG decrement = (LONG)count;
		return (u32)InterlockedAddAcquire((LONG volatile *)ptr, -decrement) + decrement; 
	}

	static inline u32 
	pAtomicFetchIncrU32(u32 volatile *ptr)
	{
		return (u32)InterlockedIncrementAcquire((LONG volatile *)ptr) - 1;
	}

	static inline void 
	pAtomicIncrU32(u32 volatile *ptr)
	{
		InterlockedIncrementRelease((LONG volatile *)ptr);
	}

	static inline u32 
	pAtomicLoadU32(u32 volatile *ptr)
	{
		return (u32)InterlockedOrAcquire((LONG volatile *)ptr, 0);
	}

	static inline void 
	pAtomicStoreB32(b32 volatile *ptr, b32 value)
	{
		_InterlockedExchange_HLERelease((LONG volatile *)ptr, (LONG)value);
	}

	static inline b32 
	pAtomicCompareExchangeB32(b32 volatile *ptr, b32 *expect, b32 desired)
	{
		return (b32)InterlockedCompareExchangeAcquire((LONG volatile *)ptr, (LONG)desired, (LONG)*expect);
	}

	// ::Platform::Windows::Atomics::End::



	// ::Platform::Windows::Files::Start::

	static b8 
	pDirIsVisible(c8 *dir_name)
	{
		WIN32_FIND_DATA find_data;
		HANDLE handle;
		return (handle = FindFirstFile(dir_name, &find_data)) != INVALID_HANDLE_VALUE;
	}

	// ::Platform::Windows::Files::End::

	// ::Platform::Windows::Globals::Start::

	HINSTANCE 
	g_Win32_Instance = {0};
	pPlatformWindow 
	g_Win32_Window = {0};
	b32 
	g_Global_Quit = false;

	// ::Platform::Windows::Globals::End::

	LRESULT CALLBACK 
	WindowProc(HWND window, UINT message, WPARAM w_param, LPARAM l_param)
	{
		LRESULT result = 0;

		switch (message)
		{
			case WM_SIZE: 
				{
					Printfln("Window resizing");
					g_Win32_Window.w = LOWORD(l_param);
					g_Win32_Window.h = HIWORD(l_param);
					rResolutionSet(g_Win32_Window.w, g_Win32_Window.h);
				}	break;
			case WM_DESTROY: // TODO(MA): Probably handle these separately but for now, they're together
			case WM_CLOSE:
				{
					Printf("quitting");
					g_Global_Quit = true;
				} break;
			case WM_ACTIVATEAPP:
				{
					OutputDebugStringA("WM_ACTIVATEAPP\n");
				} break;
			default:
				{
					result = DefWindowProc(window, message, w_param, l_param);
				} break;
		}

		return result;
	}

	b32 
	pLibraryLoad(const char *name, pLibrary *out_lib)
	{
		b32 success = true;

		out_lib->module = LoadLibraryA("vulkan-1.dll");
		if (!out_lib->module)
			success = false;

		return success;
	}

	b32 
	pFunctionLoad(const char *name, pLibrary *lib, pFunction *out_fn)
	{
		b32 success = true;

		out_fn->fn = GetProcAddress(lib->module, name);
		if (!out_fn->fn)
			success = false;

		return success;
	}

	usize 
	pPageSize()
	{
		return 0;
	}

	i32 
	_Write(void const *str, DWORD std_handle)
	{
		DWORD written;
		BOOL success = WriteFile(GetStdHandle(std_handle), str, StrLen(str), &written, NULL);
		return success ? (i32)written : -1;
	}

	b32 
	pWindowInit(const char *window_name)
	{
		b32 success = true;

		WNDCLASS window_class = {
			.style = CS_OWNDC|CS_HREDRAW|CS_VREDRAW,
			.lpfnWndProc = WindowProc,
			.hInstance = g_Win32_Instance,
			.lpszClassName = WINDOW_CLASS_NAME,
		};

		ATOM r_class_atom = RegisterClass(&window_class);
		if (!r_class_atom)
			success = false;

		if (success)
		{
			HWND window_handle = CreateWindowExA(
				0,
				window_class.lpszClassName,
				window_name,
				WS_OVERLAPPEDWINDOW|WS_VISIBLE,
				CW_USEDEFAULT,
				CW_USEDEFAULT,
				CW_USEDEFAULT,
				CW_USEDEFAULT,
				0,
				0,
				g_Win32_Instance,
				0
			);

			if (!window_handle)
				success = false;
			else
				g_Win32_Window.handle = window_handle;
		}

		return success;
	}

	pPlatformWindow *
	pWindowGet()
	{
		return &g_Win32_Window;
	}

	void 
	pWindowEventsGet()
	{
		BOOL has_msg = false;
		MSG message;
		do 
		{
			has_msg = PeekMessage(&message, 0, 0, 0, PM_REMOVE);
			if (has_msg > 0)
			{
				TranslateMessage(&message);
				DispatchMessage(&message);
			}
		}
		while (has_msg);
	}

	void 
	pWindowEventWaitFor()
	{
		MSG message;
		BOOL message_result = GetMessageA(&message, 0, 0, 0);
		if (message_result > 0)
		{
			TranslateMessage(&message);
			DispatchMessage(&message);
		}
	}

	b32 
	pWindowShouldQuit()
	{
		return g_Global_Quit;
	}

	pWindowSize 
	pWindowGetSize()
	{
		return (pWindowSize){ .w = g_Win32_Window.w, .h = g_Win32_Window.h };
	}

#endif // _WIN32

// ::Platform::Functions::Start::

static i32 
SPrintf(char *buf, int len, void *fmt, ...) 
{
	va_list arg;

	va_start(arg, fmt);
	int sprf_res = stbsp_vsnprintf(buf, len, fmt, arg);
	va_end(arg);

	return sprf_res;
}

i32 
Printf(char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	i32 result = _Printf(fmt, arg);
	va_end(arg);

	return result;
}

i32 
Printfln(char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	i32 result = _Printfln(fmt, arg);
	va_end(arg);

	return result;
}

i32 
EPrintf(char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	i32 result = _EPrintf(fmt, arg);
	va_end(arg);

	return result;
}

i32 
EPrint(void *str) 
{
	return pWriteStdErr(str, StrLen(str));
}

i32 
_EPrintf(char *fmt, va_list arg) 
{
	char buffer[1024];

	int sprf_res = stbsp_vsnprintf(&buffer[0], 1024, fmt, arg);

	if (sprf_res < 0) return sprf_res;
	
	return EPrint(&buffer);
}

i32 
_Printf(char *fmt, va_list arg) 
{
	char buffer[1024];
	
	int sprf_res = stbsp_vsnprintf(&buffer[0], 1024, fmt, arg);
	
	i32 pr_res;
	if (sprf_res < 0) 
		pr_res = sprf_res;
	else 
		pr_res = pWriteStdOut(&buffer, sprf_res);

	return pr_res;
}

i32 
_Printfln(char *fmt, va_list arg)
{
	char buffer[1024];

	int sprf_res = stbsp_vsnprintf(&buffer[0], 1023, fmt, arg);

	i32 pr_res;
	if (sprf_res < 0)
	{
		pr_res = sprf_res;
	}
	else
	{
		buffer[sprf_res] = '\n';
		buffer[sprf_res+1] = '\0';
		pr_res = pWriteStdOut(&buffer, sprf_res+1);
	}

	return pr_res;
}

// ::Platform::Functions::End::


// ::Util::Globals::Start::

static Profiler 
g_Global_Profiler;

// ::Util::Globals::End::



// ::Util::Strings::Functions::Start::


static void 
StrConcat(c8 *str, c8 *append)
{
	u32 len = StrLen(str);
	u32 i = 0;
	for (; append[i] != '\0'; i += 1)
	{
		str[i] = append[i];
	}

	str[i] = '\0';
}

static b32 
StrEqL(const char *l, const char *r, u64 len)
{
	for (u64 i = 0; *l == *r && *l && i < len; l++, r++, i++);
	return *l == *r;
}

static b32 
StrEq(const char *l, const char *r) 
{
	for (; *l == *r && *l; l++, r++);
	return *l == '\0' && *r == '\0';
}

static u32
StrLen(const char *str) 
{
	const char *start;
	
	for (start = str; *str != '\0'; ++str)
		/* Iterate only */;

	return (u32)(str - start);
}

static String8 
MakeString8(c8 *str, u64 len)
{
	return (String8){ .len = len, .value = str };
}

static String8 
String8Concat(Arena *arena, String8 string, String8 append)
{
	u64 str_len = string.len + append.len;
	String8 str = {
		.value = MakeArray(arena, c8, str_len + 1),
		.len = str_len,
	};

	MemCpy(str.value, string.value, string.len);
	MemCpy(str.value+string.len, append.value, append.len);

	str.value[str_len] = '\0';

	return str;
}

static void 
String8TrimSuffix(String8 *str, c8 delimiter)
{
	if (!str || str->len == 0) return;

	for (i64 i = i64(str->len)-1; i > 0; i -= 1)
	{
		if (str->value[i] == delimiter)
		{
			str->value[i] = '\0';
			str->len = i == 0 ? 0 : u64(i - 1);
			break;
		}
	}
}

static i64 
String8FindLast(String8 str, c8 delimiter)
{
	if (str.len == 0) return -1;

	i64 result = 1;

	for (i64 i = i64(str.len)-1; i >= 0; i -= 1)
	{
		if (str.value[i] == delimiter)
		{
			result = i;
			break;
		}
	}

	return result;
}

static b32 
String8Eq(String8 l, String8 r)
{
	return StrEqL(l.value, r.value, l.len);
}

static b32
String8StartsWith(String8 string, String8 cmp)
{
	return StrEqL(string.value, cmp.value, cmp.len);
}

static String8 
PreSplitString8(String8 string, String8 delimiter)
{
	if (string.len == 0 || delimiter.len == 0) return string;

	u32 new_len = 0;
	u32 matched_chars = 0;

	for (u32 i = 0; i < string.len; i++)
	{

		if (string.value[i] == delimiter.value[matched_chars])
			matched_chars += 1;
		else
			matched_chars = 0;

		if (matched_chars == delimiter.len || delimiter.value[matched_chars+1] == '\0') 
		{
			new_len = i;
			break;
		}
	}

	return (String8){ .len = new_len, .value = string.value };
}

static String8 
PreSplitNewString8(Arena *arena, String8 string, String8 delimiter)
{
	String8 result = PreSplitString8(string, delimiter);
	
	if (result.len > 0)
	{
		result.value = MakeArray(arena, char, result.len+1);
		MemCpy(result.value, string.value, result.len);
		result.value[result.len] = '\0';
	}

	return result;
}

// ::Util::Strings::Functions::End::



// ::Util::Memory::Functions::Start::

void 
MemZero(void *ptr, isize size)
{
	if (!size || !ptr) return;

	isize iter_size = size > sizeof(u64) ? 8 : 1;
	iter_size *= 4;
	isize iter_len = size / iter_size;

	u64 *mem = (u64 *)ptr;

	if (iter_size > sizeof(u64))
	{
		while (iter_len > 0)
		{
			mem[0] = 0;
			mem[1] = 0;
			mem[2] = 0;
			mem[3] = 0;
			mem += 4;
			iter_len--;
		}
	}

	u8 *byte_mem = (u8 *)mem;
	isize rem_len = size % iter_size;
	while (rem_len > 0)
	{
		byte_mem[0] = 0;
		byte_mem++;
		rem_len--;
	}
}

void 
MemCpy(rawptr dst, rawptr src, usize size)
{
	if (size == 0) return;

	isize iter_size = size > sizeof(u64) ? 8 : 1;
	iter_size *= 4;
	isize iter_len = size / iter_size;
	isize rem_len = 0;

	u64 *mem_dst = (u64 *)dst;
	u64 *mem_src = (u64 *)src;


	if (iter_size > sizeof(size))
	{
		while (iter_len > 0)
		{
			mem_dst[0] = mem_src[0];
			mem_dst[1] = mem_src[1];
			mem_dst[2] = mem_src[2];
			mem_dst[3] = mem_src[3];
			mem_dst += 4;
			mem_src += 4;
			iter_len--;
		}

		rem_len = size % iter_size;
	}
	else
		rem_len = size;


	u8 *byte_dst = (u8 *)mem_dst;
	u8 *byte_src = (u8 *)mem_src;
	while (rem_len > 0)
	{
		byte_dst[0] = byte_src[0];
		byte_dst++;
		byte_src++;
		rem_len--;
	}
}

// ::Util::Memory::Functions::End::



// ::Util::Math::Functions::Start::

DefMathImpl(Min);
DefMathImpl(Max);
DefMathImpl(Clamp);
DefMathImpl(Abs);

// ::Util::Math::Functions::End::



// ::Util::Hashing::Functions::Start::

u64 static inline 
HashFromString(String8 string)
{
	return XXH3_64bits_withSeed(string.value, string.len, HASH_SEED);
}

// ::Util::Hashing::Functions::End::



// ::Util::Print::Functions::Start::


// ::Util::Print::Functions::End::



// ::Util::Profiling::Functions::Start::

static inline void 
StartProfileBlock(ProfileBlock *block, c8 *label, u32 anchor_index)
{
	block->anchor_index = anchor_index;
	block->label = label;
	block->start_tsc = pCPUTimerRead();
}

static inline void 
EndProfileBlock(ProfileBlock *block)
{
	u64 elapsed = pCPUTimerRead() - block->start_tsc;

	ProfileAnchor *anchor = g_Global_Profiler.anchors + block->anchor_index;
	anchor->tsc_elapsed += elapsed;
	anchor->hit_count += 1;
	anchor->label = block->label;
}

// ::Util::Profiling::Functions::End::



// ::Util::Async::Functions::Start::

static inline b32 
MutTryLock(Mut *mut)
{
	b32 lock = false;
	return pAtomicCompareExchangeB32(&mut->lock, &lock, 1);
}

static inline void 
MutUnlock(Mut *mut)
{
	pAtomicStoreB32(&mut->lock, 0);
}

static inline void 
TicketMutInit(TicketMut *mut)
{
	mut->ticket = 0;
	mut->next_ticket = 1;
}

static inline void 
TicketMutLock(TicketMut *mut)
{
	u32 ticket = pAtomicFetchIncrU32(&mut->ticket);
	while (ticket != mut->next_ticket);
}

static inline void 
TicketMutUnlock(TicketMut *mut)
{
	pAtomicIncrU32(&mut->next_ticket);
}

static inline u32 
JobQueueAdd(JobQueue *queue, u32 count)
{
	u32 job_idx = pAtomicFetchIncrU32(&queue->queued);
	pAtomicFetchIncrU32(&queue->remaining);

	return job_idx;
}

static inline u32 
JobQueueGetCount(JobQueue *queue)
{
	return pAtomicLoadU32(&queue->queued);
}

static inline void 
JobQueueMarkUnqueued(JobQueue *queue, u32 count)
{
	Assert(queue->queued != 0, "queue queued is 0 before trying to mark dequeued");
	pAtomicFetchSubU32(&queue->queued, count);
}

static inline void 
JobQueueMarkCompleted(JobQueue *queue, u32 count)
{
	Assert(queue->remaining != 0, "queue remaining is 0 before trying to mark completed");
	pAtomicFetchSubU32(&queue->remaining, count);
}

static inline void 
JobQueueReset(JobQueue *queue)
{
	pAtomicFetchSubU32(&queue->queued, queue->queued);
	pAtomicFetchSubU32(&queue->remaining, queue->remaining);
}

static inline b32 
JobQueueCompleted(JobQueue *queue)
{
	u32 remaining = pAtomicLoadU32(&queue->remaining);
	return remaining == 0;
}

// ::Util::Async::Functions::End::


// ::DataStructures::Globals::Start::

RBNode 
RB_NIL = { .color = RB_BLACK };
RBNode *
P_RB_NIL = &RB_NIL;

RBDataNode 
RB_DN_NIL = {0};
RBDataNode *
P_RB_DN_NIL = &RB_DN_NIL;

HashNode 
HT_NIL = {0};
HashNode *
P_HT_NIL = &HT_NIL;

// ::DataStructures::Globals::End::



// ::DataStructures::RedBlackTree::Functions::Start::

static void 
RBTreeInit(RBTree *tree)
{
	Assert(tree != NULL, "RBTree is null");
	RB_NIL.right = RB_NIL.left = RB_NIL.parent = P_RB_NIL;
	RB_DN_NIL.next = P_RB_DN_NIL;

	tree->root = P_RB_NIL;
	tree->nil = P_RB_NIL;
}

static inline void 
RBTreePushDataNode(RBDataNode *first, RBDataNode *last, rawptr value)
{
	RBDataNode *data_node = FLMemAllocZeroed(sizeof(RBDataNode));
	data_node->data = value;
	RBQueuePush(first, last, data_node);
}

static inline RBNode *
RBTreeInitNode(u64 key, rawptr value)
{
	RBNode *node = FLMemAllocZeroed(sizeof(RBNode));
	node->parent = node->left = node->right = P_RB_NIL;
	node->color = RB_BLACK;
	node->key = key;

	RBTreePushDataNode(node->bucket.first, node->bucket.last, value);

	return node;
}

static void 
RBTreeInsert(RBTree *tree, u64 key, rawptr value)
{
	RBNode *node = P_RB_NIL;

	node->left = node->right = tree->nil;
	node->color = RB_RED;

	if (tree->root == tree->nil)
	{
		node->color = RB_BLACK;
		node->parent = tree->nil;
		tree->root = node;
	}
	else 
	{
		RBNode *curr_node = tree->root;
		while (true)
		{
			Assert(curr_node != tree->nil, "Current Node is NIL");

			if (curr_node->key == key)
			{
				RBTreePushDataNode(curr_node->bucket.first, curr_node->bucket.last, value);
				break;
			}
			else if (curr_node->key < key)
			{
				if (curr_node->right == tree->nil)
				{
					node = RBTreeInitNode(key, value);
					node->parent = curr_node;
					curr_node->right = node;
					break;
				}
				else
				{
					curr_node = curr_node->right;
				}
			}
			else
			{
				if (curr_node->left == tree->nil)
				{
					node = RBTreeInitNode(key, value);
					node->parent = curr_node;
					curr_node->left = node;
					break;
				}
				else
				{
					curr_node = curr_node->left;
				}
			}
		}
	}

	if (node->parent->color != RB_BLACK && node != P_RB_NIL)
		RBTreeCorrect(tree, node);
}

static void 
RBTreeCorrect(RBTree *tree, RBNode *node)
{
	RBNode *gp = node->parent->parent;
	RBNode *p = node->parent;
	do
	{
		if (node == tree->root)
		{
			node->color = RB_BLACK;
			break;
		}

		if (gp == tree->nil)
		{
			p->color = RB_BLACK;
			break;
		}

		RBNodeDir dir = NodeDir(p);
		RBNode *unc = gp->child[1 - dir];
		if (unc == tree->nil || unc->color == RB_BLACK)
		{
			if (node == p->child[1 - dir])
			{
				RBTreeRotate(tree, p, dir);
				node = p;
				p = gp->child[dir];
			}

			RBTreeRotate(tree, gp, 1 - dir);
			p->color = RB_BLACK;
			gp->color = RB_RED;
			break;
		}

		p->color = RB_BLACK;
		unc->color = RB_BLACK;
		gp->color = RB_RED;
		node = gp;
		gp = node->parent->parent;
	} while ((p = node->parent));
}

static void 
RBTreeDelete(RBTree *tree, u64 key, rawptr value)
{
	RBNode *node = NULL;
	Assert(RBTreeSearch(tree, key, &node), "Unable to find node in RBTreeDelete");

	if (node->bucket.first->data != value)
	{
		Assert(node->bucket.first->next != P_RB_DN_NIL, "RBTreeDelete Failure: unable to find value to delete");
		RBDataNode *data_node = node->bucket.first->next;
		RBDataNode *prev_node = node->bucket.first;

		while (data_node != P_RB_DN_NIL)
		{
			if (data_node->data == value)
			{
				prev_node->next = data_node->next;
				if (data_node == node->bucket.last)
					node->bucket.last = prev_node;	

				FLMemFree(data_node);
			}
		}
	}
	else
	{
		FLMemFree(node->bucket.first);

		if (node == tree->root && node->left == tree->nil && node->right == tree->nil)
		{
			tree->root = tree->nil;
		}
		else if (node->left != tree->nil && node->right != tree->nil)
		{
			RBNode *ln = node->right;

			while (ln->left != tree->nil)
				ln = ln->left;

			node->key = ln->key;
			node->bucket = ln->bucket;

			if (node->right == ln)
				node->right = tree->nil;
			else
				ln->parent->left = tree->nil;

			ln->parent = tree->nil;
		}
		else if (node->color == RB_BLACK && node->left != tree->nil)
		{
			node->key = node->left->key;
			node->bucket = node->left->bucket;
			node->left = tree->nil;
		}
		else if (node->color == RB_BLACK && node->right != tree->nil)
		{
			node->key = node->right->key;
			node->bucket = node->right->bucket;
			node->right = tree->nil;
		}
		else if (node->color == RB_RED && node->right == tree->nil && node->left == tree->nil)
		{
			RBNodeDir dir = NodeDir(node);
			node->parent->child[dir] = tree->nil;
		}
		else 
		{
			RBNode *p = node->parent;
			RBNodeColor col = node->color;
			RBNode *s, *cn, *dn;

			RBNodeDir dir = NodeDir(node);
			p->child[dir] = tree->nil;
			
			goto start_deletion;

			do 
			{
				dir = NodeDir(node);
			start_deletion:
				s = p->child[1 - dir];
				dn = s->child[1 - dir];
				cn = s->child[dir];

				if (s->color == RB_RED)
				{
					RBTreeRotate(tree, p, dir);
					p->color = RB_RED;
					s->color = RB_BLACK;
					s = cn;

					dn = s->child[1 - dir];
					if (dn->color == RB_RED)
						goto rotate_sibling;
					cn = s->child[dir];
					if (cn->color == RB_RED)
						goto rotate_parent;

					s->color = RB_RED;
					p->color = RB_BLACK;
					return;
				}
				
				if (dn->color == RB_RED)
					goto rotate_parent;

				if (cn->color == RB_RED)
					goto rotate_sibling;

				if (p->color == RB_RED)
				{
					s->color = RB_RED;
					p->color = RB_BLACK;
					return;
				}

				if (p == tree->nil)
					return;

				s->color = RB_RED;
				node = p;
			} while ((p = node->parent));

		rotate_sibling:
			RBTreeRotate(tree, s, 1 - dir);
			s->color = RB_RED;
			cn->color = RB_BLACK;
			dn = s;
			s = cn;

		rotate_parent:
			RBTreeRotate(tree, p, dir);
			s->color = p->color;
			p->color = RB_BLACK;
			dn->color = RB_BLACK;
		}
	}
}

static b32 
RBTreeSearchNearest(RBTree *tree, u64 key, RBNode **out_node)
{
	if (tree->root == tree->nil) return false;

	RBNode *node = tree->root;
	RBNode *nearest = tree->root;
	u64 nearest_diff = UINT64_MAX;

	while (true)
	{
		if (node == tree->nil)
			break;

		u64 diff = node->key - key;
		diff = Absu64(diff);

		if (diff == 0)
		{
			nearest = node;
			break;
		}

		if (diff < nearest_diff)
		{
			nearest_diff = diff;
			nearest = node;
		}

		if (node->key < key)
			node = node->right;
		else 
			node = node->left;
	}

	*out_node = nearest != tree->nil ? nearest : tree->nil;

	return *out_node != tree->nil;
}

static b32 
RBTreeSearch(RBTree *tree, u64 key, RBNode **out_node)
{
	if (tree->root == tree->nil) return false;

	b32 found = false;
	RBNode *node = tree->root;

	while (true)
	{
		if (node->key == key)
		{
			found = true;
			break;
		}

		if (node == tree->nil)
			break;
		
		if (node->key < key)
			node = node->right;
		else
			node = node->left;
	}

	if (found)
		*out_node = node;

	return found;
}

static inline void 
RBTreeTransplant(RBTree *tree, RBNode *node, RBNode *placed_node)
{
	if (node->parent == tree->nil)
		tree->root = placed_node;
	else if (node == node->parent->left)
		node->parent->left = placed_node;
	else
		node->parent->right = placed_node;

	placed_node->parent = node->parent;
}

static void 
RBTreeRotate(RBTree *tree, RBNode *node, RBNodeDir dir)
{
	RBNode *p = node->parent;
	RBNode *root = node->child[1 - dir];
	RBNode *child = root->child[dir];

	node->child[1 - dir] = child;

	if (child != tree->nil)
		child->parent = node;

	root->child[dir] = node;
	root->parent = p;
	node->parent = root;

	if (p != tree->nil)
		p->child[node == p->right] = root;
	else
		tree->root = root;
}

static void 
RBTreeLeftRotate(RBTree *tree, RBNode *node)
{
	RBNode *right = node->right;
	if (right->left != tree->nil)
		node->right = right->left;

	if (node->parent == tree->nil)
		tree->root = right;
	else if (node->parent->left == node)
		node->parent->left = right;
	else
		node->parent->right = right;

	right->parent = node->parent;
	right->left = node;
	node->parent = right;
}

static void 
RBTreeRightRotate(RBTree *tree, RBNode *node)
{
	RBNode *left = node->left;
	if (left->right != tree->nil)
		node->left = left->right;

	if (node->parent == tree->nil)
		tree->root = left;
	else if (node->parent->left == node)
		node->parent->left = left;
	else
		node->parent->right = left;

	left->parent = node->parent;
	left->right = node;
	node->parent = left;
}

// ::DataStructures::RedBlackTree::Functions::End::



// ::DataStructures::HashTable::Functions::Start::

static void 
HashTableInit(HashTable *table, u32 init_size)
{
	table->cap = init_size;
	table->count = 0;
	table->free_lists.first = P_HT_NIL;
	table->free_lists.last = P_HT_NIL;

	table->lists = FLMemAlloc(sizeof(HashList) * init_size);
	for (u32 i = 0; i < init_size; i++)
	{
		table->lists[i].first = P_HT_NIL;
		table->lists[i].last = P_HT_NIL;
	}
}

static void 
HashTableConcatInPlace(HashList *list, HashList *to_concat)
{
	SLLConcatInPlaceNoCount(list, to_concat);
}

static void 
HashTableClear(HashTable *table)
{
	table->count = 0;
	for (u32 i = 0; i < table->count; i++)
	{
		HashTableConcatInPlace(&table->free_lists, &table->lists[i]);
	}
}

static HashNode *
HashListPop(HashList *list)
{
	HashNode *result = list->first;
	HTQueuePop(list->first, list->last);
	return result;
}

static HashNode *
HashTablePush(HashTable *table, u64 hash, KeyValuePair value)
{
	HashNode *node = NULL;
	if (table->free_lists.first != P_HT_NIL)
		node = HashListPop(&table->free_lists);
	else
		node = FLMemAlloc(sizeof(HashNode));

	node->next = P_HT_NIL;
	node->v = value;

	u64 index = hash % table->cap;
	HTQueuePush(table->lists[index].first, table->lists[index].last, node);
	table->count += 1;	

	return node;
}

static HashNode *
HashTablePushU64U32(HashTable *table, u64 key, u32 value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_u32 = value });
}

static HashNode *
HashTablePushU64U64(HashTable *table, u64 key, u64 value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_u64 = value });
}

static HashNode *
HashTablePushU64String8(HashTable *table, u64 key, String8 value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_string = value });
}

static HashNode *
HashTablePushU64Rawptr(HashTable *table, u64 key, rawptr value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_rawptr = value });
}

static HashNode *
HashTablePushU64U64Split(HashTable *table, u64 key, u32 upper, u32 lower)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_u64 = key, .value_u64_split = { .upper = upper, .lower = lower }});
}

static HashNode *
HashTablePushRawptrU64(HashTable *table, rawptr key, u64 value)
{
	u64 hash = HashFromString(String8Struct(&key));
	return HashTablePush(table, hash, (KeyValuePair){ .key_rawptr = key, .value_u64 = value });
}

static KeyValuePair *
HashTableSearchU64(HashTable *table, u64 key)
{
	KeyValuePair *result = NULL;

	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_u64 == key)
		{
			result = &node->v;
			break;
		}
	}

	return result;
}

static KeyValuePair *
HashTableSearchRawptr(HashTable *table, rawptr key)
{
	KeyValuePair *result = NULL;

	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_rawptr == key)
		{
			result = &node->v;
			break;
		}
	}

	return result;
}

static void 
HashTableDeleteU64(HashTable *table, u64 key)
{
	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	HashNode *prev = P_HT_NIL;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_u64 == key)
		{
			if (prev != P_HT_NIL)
				prev->next = node->next;

			node->v.key_u64 = 0;
			node->v.value_u64 = 0;
			HTQueuePush(table->free_lists.first, table->free_lists.last, node);
			break;
		}
	}
}

static rawptr 
HashTableDeleteU64Rawptr(HashTable *table, u64 key)
{
	rawptr value = NULL;

	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	HashNode *prev = P_HT_NIL;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_u64 == key)
		{
			if (prev != P_HT_NIL)
				prev->next = node->next;
			
			value = node->v.value_rawptr;

			node->v.key_u64 = 0;
			node->v.value_rawptr = NULL;
			HTQueuePush(table->free_lists.first, table->free_lists.last, node);
			break;
		}
	}

	return value;
}

static U64Split 
HashTableDeleteU64U64Split(HashTable *table, u64 key)
{
	U64Split value = { .upper = UINT32_MAX };

	u64 hash = HashFromString(String8Struct(&key));
	u64 index = hash % table->cap;
	HashList *list = table->lists + index;
	HashNode *prev = P_HT_NIL;
	for (HashNode *node = list->first; node != P_HT_NIL; node = node->next)
	{
		if (node->v.key_u64 == key)
		{
			if (prev != P_HT_NIL)
				prev->next = node->next;
			
			value.upper = node->v.value_u64_split.upper;
			value.lower = node->v.value_u64_split.lower;

			node->v.key_u64 = 0;
			node->v.value_u64_split.upper = 0;
			node->v.value_u64_split.lower = 0;
			HTQueuePush(table->free_lists.first, table->free_lists.last, node);
			break;
		}
	}

	return value;
}

// ::DataStructures::HashTable::Functions::End::



// ::Allocator::Globals::

FLAlloc 
FL_ALLOC = {0};
Allocator 
ALLOC = {0};
read_only FLNode 
FL_NIL_NODE = {0};
read_only FreeList 
FL_NIL_LIST = {0};

constexpr usize 
FL_GLOBAL_SIZE = MB(32);
static b32 
FL_GLOBAL_INIT = false;

// ::Allocator::Util::Header::

static inline usize 
CalcPaddingWithHeader(uintptr ptr, uintptr alignment, usize header_size)
{
	Assert(IsPow2(alignment), "Alignment provided to CalcPaddingWithHeader is not a power of two");

	uintptr padding = CalcPadding(ptr, alignment);
	uintptr needed_space = (uintptr)header_size;

	if (padding < needed_space)
	{
		needed_space -= padding;

		if ((needed_space & (alignment-1)) != 0)
			padding += alignment * (1 + (needed_space/alignment));
		else
			padding += alignment * (needed_space/alignment);
	}

	return (usize)padding;
}

static inline usize 
CalcPadding(uintptr ptr, uintptr alignment)
{
	Assert(IsPow2(alignment), "CalcPadding failure: IsPow2 failed");

	uintptr padding = 0;
	uintptr modulo = ptr & (alignment-1);
	if (modulo != 0)
		padding = alignment - modulo;

	return (usize)padding;
}

// ::Allocator::Arena::Start::

static Arena *
ArenaInit(rawptr buffer, usize size)
{
	Arena *arena = (Arena *)buffer;
	buffer = PtrAdd(buffer, ARENA_HEADER_SIZE);

	arena->buffer = buffer;
	arena->length = size;
	arena->pos = 0;

	return arena;
}

static Arena *
ArenaCreate(usize size)
{
	u8 *mem = pMemAllocZeroed(size);
	return ArenaInit(mem, size);
}

static Arena *
ArenaCreateDebug(usize size, u32 init_line_no)
{
	u8 *mem = pMemAllocZeroed(size);
	return ArenaInitDebug(mem, size, init_line_no);
}

// TODO: investigate overflows when out of memory because something bad is going on
static rawptr 
ArenaAllocAlign(Arena *arena, usize size, usize align)
{
	rawptr ptr = NULL;

	uintptr curr_ptr = (uintptr)arena->buffer + (uintptr)arena->pos;
	uintptr offset = AlignPow2(curr_ptr, align);
	offset -= (uintptr)arena->buffer;

	if (offset+size <= arena->length)
	{
		ptr = &arena->buffer[offset];
		arena->pos = offset+size;
	}
	else
	{
		Printfln("Out of memory: %d", arena->init_line_no);
		Assert(0, "Memory Failure");
	}

	return ptr;
}

static rawptr 
ArenaAlloc(Arena *arena, usize size)
{
	return ArenaAllocAlign(arena, size, DEFAULT_ALIGNMENT);
}

static void 
ArenaFree(Arena *arena)
{
	arena->pos = 0;
}

static void 
ArenaFreeZeroed(Arena *arena)
{
	MemZero(arena->buffer, arena->pos);
	ArenaFree(arena);
}

static void 
DeallocArena(Arena *arena)
{
	pMemFree(arena, arena->length);
}

static Arena * 
ArenaInitDebug(rawptr buffer, usize size, u32 init_line_no)
{
	Arena *arena = ArenaInit(buffer, size);
	arena->init_line_no = init_line_no;
	return arena;
}

// ::Allocator::Arena::End::



// ::Allocator::GlobalAlloc::Start::

static void 
InitAllocator(usize init_size)
{
	ALLOC.grow_size = init_size;
	ALLOC.buffer = pMemAllocZeroed(init_size);
	ALLOC.size = init_size;
	ALLOC.free_size = init_size;

	ALLOC.hash_table = FLMemAlloc(sizeof(HashTable));
	HashTableInit(ALLOC.hash_table, 32);

	ALLOC.tree = FLMemAlloc(sizeof(RBTree));
	RBTreeInit(ALLOC.tree);

	RBTreeInsert(ALLOC.tree, init_size, ALLOC.buffer);
}

static void 
DeinitAlloc()
{
	pMemFree(ALLOC.buffer, ALLOC.size);
}

static rawptr 
Alloc(usize size)
{
	return AllocAlign(size, DEFAULT_ALIGNMENT);
}

static rawptr 
AllocAlign(usize size, usize alignment)
{
	if (size == 0) return NULL;

	RBNode *node = P_RB_NIL;
	rawptr mem = NULL;
	if (!RBTreeSearchNearest(ALLOC.tree, size + alignment, &node))
	{
		AllocGrow(size);
		RBTreeSearchNearest(ALLOC.tree, size + alignment, &node);
	}

	u64 alloc_size = node->key;
	rawptr free_alloc = node->bucket.last->data;
	RBTreeDelete(ALLOC.tree, alloc_size, free_alloc);

	usize padding = CalcPadding(uintptr(free_alloc), alignment);
	uintptr new_addr = uintptr(free_alloc) + size + padding;
	RBTreeInsert(ALLOC.tree, alloc_size - size - padding, rawptr(new_addr));

	HashTablePushRawptrU64(ALLOC.hash_table, free_alloc, size + padding);

	return free_alloc;
}

// TODO: finish allocator
// need an idea
static void 
Free(rawptr ptr)
{
	if (ptr == NULL) return;

	
}

static void 
AllocGrow(usize size)
{
	usize grow_size = size < ALLOC.grow_size ? ALLOC.grow_size : ALLOC.grow_size + size;
	pMemRealloc(ALLOC.buffer, ALLOC.size, ALLOC.size + grow_size);

	RBTreeInsert(ALLOC.tree, grow_size, ALLOC.buffer + ALLOC.size); // TODO: check this if things fuck up it could be wrong

	ALLOC.size += grow_size;
	ALLOC.free_size += grow_size;
}

// ::Allocator::GlobalAlloc::End::



// ::Allocator::FreeList::Start::

static void 
GlobalFreeListInit(usize size)
{
	FreeListInit(&FL_ALLOC, size);
	FL_GLOBAL_INIT = true;
}

static rawptr 
FLMemAlloc(usize size)
{
	if (!FL_GLOBAL_INIT)
	{
		GlobalFreeListInit(FL_GLOBAL_SIZE);
	}

	return FreeListAlloc(&FL_ALLOC, size);
}

static rawptr 
FLMemAllocZeroed(usize size)
{
	rawptr ptr = FLMemAlloc(size);
	MemZero(ptr, size);
	return ptr;
}

static rawptr 
FLMemRealloc(rawptr old_ptr, usize size)
{
	if (!FL_GLOBAL_INIT)
	{
		GlobalFreeListInit(FL_GLOBAL_SIZE);
	}

	rawptr ptr = NULL;
	if (old_ptr == NULL)
		ptr = FLMemAlloc(size);
	else
		ptr = FreeListRealloc(&FL_ALLOC, old_ptr, size);

	return ptr;
}

static void 
FLMemFree(rawptr ptr)
{
	if (!FL_GLOBAL_INIT)
	{
		GlobalFreeListInit(FL_GLOBAL_SIZE);
	}

	FreeListFree(&FL_ALLOC, ptr);
}

/*
 * Should be async safe given no other thread frees a pointer in the middle of a realloc
 */
static rawptr 
FreeListRealloc(FLAlloc *alloc, rawptr old_ptr, usize size)
{
	rawptr ptr = FreeListAlloc(alloc, size);
	usize old_size = FreeListPtrSize(alloc, old_ptr);

	MemCpy(ptr, old_ptr, old_size);

	FreeListFree(alloc, old_ptr);

	return ptr;
}

static usize 
FreeListPtrSize(FLAlloc *alloc, rawptr ptr)
{
	if (ptr == NULL) return 0;

	usize size = 0;

	TicketMutLock(&alloc->mut);

	FreeList *list = _FreeListFindList(alloc, ptr);
	if (list != NULL)
	{
		FLAllocHeader *header = cast(FLAllocHeader *, u8ptr(ptr) - sizeof(FLAllocHeader));
		size = header->size + header->padding;
	}

	TicketMutUnlock(&alloc->mut);

	return size;
}

static void 
FreeListInit(FLAlloc *alloc, usize size)
{
	alloc->list_head = pMemAllocZeroed(sizeof(FreeList));
	alloc->nil = &FL_NIL_NODE;
	alloc->grow_size = size;

	_FreeListInit(&alloc->list_head, size);

	FreeListFreeAll(alloc);
}

static void 
_FreeListInit(FreeList **alloc, usize size)
{
	*alloc = (FreeList *)pMemAllocZeroed(size);
	(*alloc)->data = (rawptr)(((uintptr)*alloc) + ((uintptr)sizeof(FreeList)));
	(*alloc)->size = size;
	(*alloc)->used = sizeof(FreeList);
	(*alloc)->next = &FL_NIL_LIST;
}

static void 
FreeListFreeAll(FLAlloc *alloc)
{
	TicketMutLock(&alloc->mut);

	FreeList *current = alloc->list_head->next;
	FreeList *next = &FL_NIL_LIST;
	while (current != &FL_NIL_LIST)
	{
		next = current->next;
		pMemFree(current, current->size);
		current = next;
	}

	current = alloc->list_head;

	FLNode *node = (FLNode *)current->data;
	node->size = current->size;
	node->next = &FL_NIL_NODE;

	current->head = node;
	current->used = sizeof(FreeList);

	TicketMutUnlock(&alloc->mut);
}

static FLNode *
FreeListSearch(FreeList *alloc, usize size, usize alignment, usize *out_padding, FLNode **prev_node)
{
	FLNode *node = alloc->head;
	FLNode *prev = &FL_NIL_NODE;

	usize padding = 0;

	while (node != &FL_NIL_NODE)
	{
		padding = CalcPaddingWithHeader((uintptr)node, (uintptr)alignment, sizeof(FLNode));
		usize required_size = size + padding;
		if (node->size >= required_size)
			break;

		prev = node;
		node = node->next;
	}

	if (out_padding) 
		*out_padding = padding;

	if (prev_node)
		*prev_node = prev;

	return node;
}

/*
 *	NOT SAFE TO CALL OUTSIDE OF FreeListAlloc
 */
static FreeList *
FreeListGrow(FLAlloc *alloc, usize alloc_size)
{
	usize grow_size = alloc->grow_size;
	if (alloc_size > grow_size)
		grow_size += alloc_size;

	FreeList *list = NULL;

	_FreeListInit(&list, grow_size);

	FLNode *node = (FLNode *)list->data;
	node->size = list->size;
	node->next = alloc->nil;

	list->head = node;

	FreeList *current = alloc->list_head;
	while (current != &FL_NIL_LIST)
	{
		if (current->next == &FL_NIL_LIST)
		{
			current->next = list;
			break;
		}

		current = current->next;
	}

	return list;
}

static rawptr 
_FreeListAllocAlign(FreeList *alloc, usize size, usize alignment)
{
	if (size == 0) return NULL;

	usize padding = 0;
	FLNode *prev_node = &FL_NIL_NODE;

	FLAllocHeader *header;

	if (size < sizeof(FLNode))
		size = sizeof(FLNode);

	if (alignment < 8)
		alignment = 8;

	FLNode *node = FreeListSearch(alloc, size, alignment, &padding, &prev_node);

	if (node == &FL_NIL_NODE)
		Assert(0, "FreeListAllocAlign failed to allocate, oom");
	
	usize alignment_padding = padding - sizeof(FLAllocHeader);
	usize required_space = size + padding;
	usize remaining = node->size - required_space;

	if (remaining > 0)
	{
		FLNode *new_node = (FLNode *)(((c8 *)node) + required_space);
		new_node->size = remaining;
		FreeListInsert(&alloc->head, node, new_node);
	}

	FreeListRemove(&alloc->head, prev_node, node);

	header = (FLAllocHeader *)(((c8 *)node) + alignment_padding);
	header->size = required_space;
	header->padding = alignment_padding;

	alloc->used += required_space;

	return (rawptr)(((c8 *)header) + sizeof(FLAllocHeader));
}

static rawptr 
FreeListAlloc(FLAlloc *alloc, usize size)
{
	return FreeListAllocAlign(alloc, size, DEFAULT_ALIGNMENT);
}

static rawptr 
FreeListAllocAlign(FLAlloc *alloc, usize size, usize alignment)
{
	TicketMutLock(&alloc->mut);

	FreeList *fl = &FL_NIL_LIST;
	FreeList *current = alloc->list_head;
	while (current != &FL_NIL_LIST)
	{
		usize remaining = current->size - current->used;
		if (size < remaining)
		{
			fl = current;
			break;
		}

		current = current->next;
	}

	if (fl == &FL_NIL_LIST)
	{
		fl = FreeListGrow(alloc, size);
	}

	rawptr ptr = _FreeListAllocAlign(fl, size, alignment);

	TicketMutUnlock(&alloc->mut);

	return ptr;
}

static FreeList *
_FreeListFindList(FLAlloc *alloc, rawptr ptr)
{
	FreeList *list = NULL;
	FreeList *current = alloc->list_head;
	while (current != &FL_NIL_LIST)
	{
		uintptr ptr_addr = uintptr(ptr);
		uintptr start = uintptr(current->data);
		uintptr end = uintptr(current->data) + uintptr(current->size);
		if (ptr_addr >= start && ptr_addr < end)
		{
			list = current;
			break;
		}
	}

	return list;
}

static void 
_FreeListFree(FreeList *alloc, rawptr ptr)
{
	FLAllocHeader *header = cast(FLAllocHeader *, u8ptr(ptr) - sizeof(FLAllocHeader));
	FLNode *free_node = cast(FLNode *,header);
	free_node->size = header->size + header->padding;
	free_node->next = &FL_NIL_NODE;

	FLNode *prev_node = &FL_NIL_NODE;
	FLNode *node = alloc->head;
	while (node != &FL_NIL_NODE)
	{
		if (ptr < node)
		{
			FreeListInsert(&alloc->head, prev_node, free_node);
			break;
		}

		prev_node = node;
		node = node->next;
	}

	alloc->used -= free_node->size;

	FreeListCoalescence(alloc, prev_node, free_node);
}

static void 
FreeListFree(FLAlloc *alloc, rawptr ptr)
{
	if (ptr == NULL) return;

	TicketMutLock(&alloc->mut);

	FreeList *list = _FreeListFindList(alloc, ptr);
	if (list != NULL)
	{
		_FreeListFree(list, ptr);
	}

	TicketMutUnlock(&alloc->mut);
}

static void 
FreeListCoalescence(FreeList *alloc, FLNode *prev_node, FLNode *free_node)
{
	if (free_node->next != &FL_NIL_NODE && (rawptr)(((c8 *)free_node) + free_node->size) == free_node->next)
	{
		free_node->size += free_node->next->size;
		FreeListRemove(&alloc->head, free_node, free_node->next);
	}

	if (prev_node->next != &FL_NIL_NODE && (rawptr)(((c8 *)prev_node) + prev_node->size) == free_node)
	{
		prev_node->size += free_node->next->size;
		FreeListRemove(&alloc->head, prev_node, free_node);
	}
}

static void 
FreeListRemove(FLNode **head, FLNode *prev_node, FLNode *del_node)
{
	if (prev_node == &FL_NIL_NODE)
		*head = del_node->next;
	else
		prev_node->next = del_node->next;
}

static void 
FreeListInsert(FLNode **head, FLNode *prev_node, FLNode *new_node)
{
	if (prev_node == &FL_NIL_NODE)
	{
		if (*head != &FL_NIL_NODE)
		{
			new_node->next = *head;
			*head = new_node;
		}
		else
			*head = new_node;
	}
	else
	{
		new_node->next = prev_node->next;
		prev_node->next = new_node;
	}
}

// ::Allocator::FreeList::End::



#endif // STG_IMPLEMENTATION
