// vim:fileencoding=utf-8:foldmethod=marker

#include "file_win.h"
#include "../../../common/platform/platform.h"

#ifdef WP_PLATFORM_WINDOWS

#include "../file.h"
#include "../../cpath/cpath.h"
#include "../../../common/aliases/aliases.h"
#include "../../../base/array/array.h"
#include "../../../base/strings/str8/str8.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <fileapi.h>
#include <intsafe.h>

wp_intern DWORD file_accesses[COUNT_FILE_ACCESS_MODE] = {
	[WP_ACCESS_READ]                       = FILE_READ_DATA,
	[WP_ACCESS_WRITE]                      = FILE_WRITE_DATA,
	[WP_ACCESS_APPEND]                     = FILE_APPEND_DATA,
	[WP_ACCESS_READ_EX]                    = FILE_READ_DATA | FILE_WRITE_DATA,
	[WP_ACCESS_WRITE_EX]                   = FILE_READ_DATA | FILE_WRITE_DATA,
	[WP_ACCESS_APPEND_EX]                  = FILE_READ_DATA | FILE_APPEND_DATA,
	[WP_ACCESS_WRITE_FAIL_ON_EXIST]        = FILE_WRITE_DATA,
	[WP_ACCESS_WRITE_FAIL_ON_EXIST_EX]     = FILE_READ_DATA | FILE_WRITE_DATA,
};

wp_intern DWORD creation_dispositions[COUNT_FILE_ACCESS_MODE] = {
	[WP_ACCESS_READ]                       = OPEN_EXISTING,
	[WP_ACCESS_WRITE]                      = CREATE_ALWAYS,
	[WP_ACCESS_APPEND]                     = OPEN_ALWAYS,
	[WP_ACCESS_READ_EX]                    = OPEN_EXISTING,
	[WP_ACCESS_WRITE_EX]                   = CREATE_ALWAYS,
	[WP_ACCESS_APPEND_EX]                  = OPEN_ALWAYS,
	[WP_ACCESS_WRITE_FAIL_ON_EXIST]        = CREATE_NEW,
	[WP_ACCESS_WRITE_FAIL_ON_EXIST_EX]     = CREATE_NEW,
};

wp_intern DWORD sharing_modes[COUNT_FILE_ACCESS_MODE] = {
	[WP_ACCESS_READ]                       = FILE_SHARE_READ | FILE_SHARE_WRITE,
	[WP_ACCESS_WRITE]                      = FILE_SHARE_READ,
	[WP_ACCESS_APPEND]                     = FILE_SHARE_READ,
	[WP_ACCESS_READ_EX]                    = FILE_SHARE_READ | FILE_SHARE_WRITE,
	[WP_ACCESS_WRITE_EX]                   = FILE_SHARE_READ,
	[WP_ACCESS_APPEND_EX]                  = FILE_SHARE_READ,
	[WP_ACCESS_WRITE_FAIL_ON_EXIST]        = FILE_SHARE_READ,
	[WP_ACCESS_WRITE_FAIL_ON_EXIST_EX]     = FILE_SHARE_READ,
};

wp_intern DWORD file_seek_origins[COUNT_FILE_SEEK_ORIGIN] = {
	[WP_SEEK_START]   = FILE_BEGIN,
	[WP_SEEK_CURRENT] = FILE_CURRENT,
	[WP_SEEK_END]     = FILE_END,
};

WpFile *wpFileStdin(void) {
	wp_persist WpFile _stdin  = { .fh = INVALID_HANDLE_VALUE };
	_stdin.fh = GetStdHandle(STD_INPUT_HANDLE);
	return &_stdin;
}

WpFile *wpFileStdout(void) {
	wp_persist WpFile _stdout = { .fh = INVALID_HANDLE_VALUE };
	_stdout.fh = GetStdHandle(STD_OUTPUT_HANDLE);
	return &_stdout;
}

WpFile *wpFileStderr(void) {
	wp_persist WpFile _stderr = { .fh = INVALID_HANDLE_VALUE };
	_stderr.fh = GetStdHandle(STD_ERROR_HANDLE);
	return &_stderr;
}

WpFile *_fileOpen(const WpAllocator *allocator, WpStr8RO *filepath, WpFileAccessMode mode) {
	wp_persist c8 tmp[WP_PATH_MAX] = {0};
	memset(tmp, 0, WP_PATH_MAX);
	memcpy(tmp, filepath->buf, filepath->size);

	HANDLE fh = CreateFileA((LPCSTR)tmp,
	                        file_accesses[mode],
	                        sharing_modes[mode],
	                        NULL,
	                        creation_dispositions[mode],
	                        FILE_ATTRIBUTE_NORMAL,
	                        NULL);
	if (fh == INVALID_HANDLE_VALUE) {
		return NULL;
	}

	WpFile *output = wpMemAllocatorAlloc(allocator, sizeof(WpFile));
	if (output) {
		output->fh = fh;
	}

	return output;
}

i64 _fileSeek(WpFile *file, i64 offset, WpFileSeekOrigin origin) {
	LARGE_INTEGER distance = {0};
	LARGE_INTEGER output   = {0};

	distance.QuadPart = offset;

	if (!SetFilePointerEx(file->fh, distance, &output, file_seek_origins[origin])) {
		return -1;
	}

	return output.QuadPart;
}

u64 _fileRead(void* dst_buf, u64 byte_count, WpFile* file, u64 file_length) {
	u64 copy_byte_count = file_length <= byte_count ? file_length : byte_count;
	wpDebugAssert(copy_byte_count <= DWORD_MAX, "Attempting to read large number of bytes at once");

	DWORD read_count = 0;
	if (!ReadFile(file->fh, dst_buf, (DWORD)copy_byte_count, &read_count, NULL)) {
		return 0;
	}

	return (u64)read_count;
}

i64 _fileWrite(const void *src_buf, WpFile *file, u64 byte_count) {
	wpDebugAssert(byte_count <= DWORD_MAX, "Attempting to write large number of bytes at once");

	DWORD write_count = 0;
	if (!WriteFile(file->fh, src_buf, (DWORD)byte_count, &write_count, NULL)) {
		return 0;
	}
	return (i64)write_count;
}

i32 _fileFlush(WpFile *file) {
	if (!FlushFileBuffers(file->fh)) {
		return -1;
	}

	return 0;
}

i32 _fileClose(WpFile *file) {
	if (!CloseHandle(file->fh)) {
		return -1;
	}

	return 0;
}

i32 _fileRename(WpStr8RO *old_filepath, WpStr8RO *new_filepath) {
	wp_persist c8 old_tmp[WP_PATH_MAX] = {0};
	wp_persist c8 new_tmp[WP_PATH_MAX] = {0};
	memset(old_tmp, 0, WP_PATH_MAX);
	memcpy(old_tmp, old_filepath->buf, old_filepath->size);
	memset(new_tmp, 0, WP_PATH_MAX);
	memcpy(new_tmp, new_filepath->buf, new_filepath->size);

	if (!MoveFile((LPCSTR)old_tmp, (LPCSTR)new_tmp)) {
		return -1;
	}

	return 0;
}

i32 _fileRemove(WpStr8RO *filepath) {
	wp_persist c8 tmp[WP_PATH_MAX] = {0};
	memset(tmp, 0, WP_PATH_MAX);
	memcpy(tmp, filepath->buf, filepath->size);

	if (!DeleteFile((LPCSTR)tmp)) {
		return -1;
	}

	return 0;
}

#endif // !WP_PLATFORM_WINDOWS
