#include "os_linux.h"

// ::Constants::
static b32 g_Global_Quit = false;

// TODO: figure out a way to get appropriate defaults
static constexpr i32 WINDOW_WIDTH = 1280;
static constexpr i32 WINDOW_HEIGHT = 720;

// ::Misc::Impl::Start::

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

// ::Misc::Impl::End::


// ::Console::Impl::Start::

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

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

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

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

	return sprf_res;
}

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

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

	return result;
}

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

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

	return result;
}

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

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

	return result;
}

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

static i32 
EPrintfVargs(const 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);
}

static i32 
PrintfVargs(const 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;
}

static i32 
PrintflnVargs(const 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;
}

// ::Console::Impl::End::


// ::DynLib::Impl::

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

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

// ::MemAlloc::Impl::

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

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

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

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

// ::FileSystem::Impl::Start::

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

static pFile 
pFileOpen(char *file_name, pFileAccess 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;
}

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

// ::FileSystem::Impl::End::


// ::Profiling::Impl::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();
}

// ::Profiling::Impl::End::


// ::Atomics::Impl::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); 
}

// ::Atomics::Impl::End::
