#pragma once

#define STB_SPRINTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION

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

#include "glad/gl.h"

#include "xxhash/xxhash.h"
#include "xxhash/xxhash.c"

#define M3D_IMPLEMENTATION

#include "m3d/m3d.h"

#include "base_types.h"

#include <pthread.h>
#include <signal.h>
#include <limits.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 <cassert>
#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>

// TODO: Replace this eventually:
#include <cmath>

#include "alloc.h"
#include "util.h"

// ::Constants::

constexpr int SYS_ERR = -1;

constexpr int STDIN = 0;
constexpr int STDOUT = 1;
constexpr int STDERR = 2;

// ::Enums::

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

typedef i32 pFileAccess;

// ::Types::

typedef int pFile;

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

typedef struct pLibrary
{
	void *lib;
} pLibrary;

typedef struct pFunction
{
	void *fn;
} pFunction;

// ::Macros::

#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) 

// ::Misc::

static b32 pSyscallErrCheck(void *ptr);

// ::Console::

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

static i32 SPrintf(char *buf, int len, const char *fmt, ...);
static i32 Printf(const char *fmt, ...);
static i32 Printfln(const char *fmt, ...);
static i32 EPrintf(const char *fmt, ...);

static i32 PrintfVargs(const char *fmt, va_list arg);
static i32 PrintflnVargs(const char *fmt, va_list arg);
static i32 EPrintfVargs(const char *fmt, va_list arg);

// ::DynLib::

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

// ::MemAlloc::

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

// ::FileSystem::

static b32 pDirNavigate(char *dir);
static char **pDirGetFileNames(Arena *arena, u32 *count);
static pFile pFileOpen(char *file_name, pFileAccess 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, pFileAccess access);

// ::Profiling::

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

// ::Atomics::

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

// ::Includes::

#include "util.cpp"
#include "alloc.cpp"
