// ::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::

rawptr pMemAlloc(usize size) 
{
	rawptr addr = mmap(
		NULL, 
		size, 
		PROT_READ | PROT_WRITE, 
		MAP_ANON | MAP_PRIVATE,
		-1,
		0
	);

	if (pSyscallErrCheck(addr)) addr = NULL;

	return addr;
}

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

rawptr pMemRealloc(rawptr ptr, usize old_size, usize new_size)
{
	rawptr addr = mremap(
		ptr,
		old_size,
		new_size,
		MAP_ANON | MAP_PRIVATE
	);

	if (pSyscallErrCheck(addr)) addr = NULL;

	return addr;
}

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

usize pPageSize()
{
	return (usize)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::Functions::Directory::Start::

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

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

	c8 **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 (c8 **)file_names;
}

static b8 pDirIsVisible(c8 *dir_name)
{
	b8 found = false;

	struct dirent *dir;
	DIR *d = opendir(".");

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

	return found;
}

// ::Platform::Functions::Directory::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::
