OS_Handle FS_FileOpen(Str8 path, FS_AccessFlags access) {
	OS_Handle result = { 0 };

	M_TempScope(0, 0) {
		Str8 wpath = Win32_WideStr(temp.arena, path);

		DWORD dwAccess = 0;
		DWORD dwShare  = FILE_SHARE_READ;
		DWORD dwCreate = OPEN_EXISTING;

		if (access & FS_ACCESS_WRITE) {
			dwCreate = CREATE_ALWAYS;
			dwAccess = GENERIC_WRITE;
		}

		if (access & FS_ACCESS_READ) {
			dwAccess |= GENERIC_READ;
		}

		HANDLE hFile = CreateFileW((LPCWSTR) wpath.data, dwAccess, dwShare, 0, dwCreate, 0, 0);

		result.v[0] = (hFile == INVALID_HANDLE_VALUE) ? 0 : ((U64) hFile);
	}
	return result;
}

void FS_FileClose(OS_Handle file) {
	HANDLE hFile = cast(HANDLE) file.v[0];
	if (hFile) {
		CloseHandle(hFile);
	}
}

void FS_FileRead(OS_Handle file, void *ptr, U64 size, U64 offset) {
	HANDLE hFile = cast(HANDLE) file.v[0];
	if (hFile) {
		U8 *buffer    = ptr;
		U64 current   = offset;
		U64 remainder = size;

		while (remainder != 0) {
			OVERLAPPED overlapped = { 0 };
			overlapped.Offset     = cast(DWORD) (current & 0xFFFFFFFF);
			overlapped.OffsetHigh = cast(DWORD) ((current >> 32) & 0xFFFFFFFF);

			DWORD toread = (remainder > U32_MAX) ? U32_MAX : (DWORD) remainder;
			DWORD nread  = 0;

			if (!ReadFile(hFile, buffer, toread, &nread, &overlapped)) {
				break;
			}

			buffer    += nread;
			current   += nread;
			remainder -= nread;
		}
	}
}

void FS_FileWrite(OS_Handle file, void *ptr, U64 size, U64 offset) {
	HANDLE hFile = cast(HANDLE) file.v[0];
	if (hFile) {
		U8 *buffer    = ptr;
		U64 current   = offset;
		U64 remainder = size;

		while (remainder != 0) {
			OVERLAPPED overlapped = { 0 };
			overlapped.Offset     = cast(DWORD) (current & 0xFFFFFFFF);
			overlapped.OffsetHigh = cast(DWORD) ((current >> 32) & 0xFFFFFFFF);

			DWORD towrite  = (remainder > U32_MAX) ? U32_MAX : (DWORD) remainder;
			DWORD nwritten = 0;

			if (!WriteFile(hFile, buffer, towrite, &nwritten, &overlapped)) {
				break;
			}

			buffer    += nwritten;
			current   += nwritten;
			remainder -= nwritten;
		}
	}
}

U64 FS_FileSize(OS_Handle file) {
	U64 result = 0;
	HANDLE hFile = cast(HANDLE) file.v[0];
	if (hFile) {
		DWORD dwLow, dwHigh;
		dwLow = GetFileSize(hFile, &dwHigh);

		result = ((U64) dwHigh << 32) | ((U64) dwLow);
	}

	return result;
}

FS_List FS_PathList(M_Arena *arena, Str8 path) {
	FS_List result = { 0 };

	M_TempScope(1, &arena) {
		Str8 wpath = Win32_WideStr(temp.arena, Sf(temp.arena, "%.*s\\*", Sv(path)));

		WIN32_FIND_DATAW find = { 0 };
		HANDLE hFind = FindFirstFileW((LPCWSTR) wpath.data, &find);

		while (hFind != INVALID_HANDLE_VALUE) {
			if (find.cFileName[0] != L'.') {
				FS_Entry *entry = M_ArenaPush(arena, FS_Entry);

				entry->type = (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FS_ENTRY_TYPE_DIR : FS_ENTRY_TYPE_FILE;

				entry->basename = Win32_MultiByteStr(arena, find.cFileName);
				entry->path     = Sf(arena, "%.*s/%.*s", Sv(path), Sv(entry->basename));

				FILETIME *wt = &find.ftLastWriteTime;

				entry->time = ((U64) wt->dwHighDateTime << 32) | ((U64) wt->dwLowDateTime);
				entry->size = ((U64) find.nFileSizeHigh << 32) | ((U64) find.nFileSizeLow);

				SLL_Enqueue(result.first, result.last, entry);

				result.count += 1;
			}

			if (!FindNextFileW(hFind, &find)) {
				break;
			}
		}
	}

	return result;
}

Str8 FS_SystemPath(M_Arena *arena, FS_SystemPathType path) {
	Str8 result = { 0 };

	M_TempScope(1, &arena) {
		switch (path) {
			case FS_SYSTEM_PATH_EXE: {
				U64 offset = M_ArenaOffset(temp.arena);

				DWORD err, nchars;

				DWORD  nSize      = 1024;
				LPWSTR lpFilename = M_ArenaPush(temp.arena, WCHAR, .count = nSize, .flags = M_ARENA_NO_ZERO);

				for (;;) {
					nchars = GetModuleFileNameW(0, lpFilename, nSize);

					err = GetLastError();
					if (err != ERROR_INSUFFICIENT_BUFFER) {
						break;
					}

					M_ArenaPop(temp.arena, offset);

					nSize      *= 2;
					lpFilename  = M_ArenaPush(temp.arena, WCHAR, .count = nSize, .flags = M_ARENA_NO_ZERO);
				}

				if (err == ERROR_SUCCESS) {
					while (lpFilename[nchars] != '\\') { nchars -= 1; }
					lpFilename[nchars] = 0;

					result = Win32_MultiByteStr(arena, lpFilename);
				}
			}
			break;

			case FS_SYSTEM_PATH_WORKING: {
				DWORD nSize = GetCurrentDirectoryW(0, 0);

				if (nSize != 0) {
					LPWSTR lpBuffer = M_ArenaPush(temp.arena, WCHAR, .count = nSize, .flags = M_ARENA_NO_ZERO);
					GetCurrentDirectoryW(nSize, lpBuffer);

					result = Win32_MultiByteStr(arena, lpBuffer);
				}
			}
			break;

			case FS_SYSTEM_PATH_USER: {
				LPWSTR wpath;
				HRESULT hResult = SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, 0, &wpath);
				if (hResult == S_OK) {
					result = Win32_MultiByteStr(arena, wpath);
					CoTaskMemFree(wpath);
				}
			}
			break;

			case FS_SYSTEM_PATH_TEMP: {
				DWORD nSize     = MAX_PATH + 1;
				LPWSTR lpBuffer = M_ArenaPush(temp.arena, WCHAR, .count = nSize);

				DWORD nchars = GetTempPathW(nSize, lpBuffer);
				if (nchars != 0) {
					lpBuffer[nchars - 1] = 0; // Strip trailing backslash
					result = Win32_MultiByteStr(arena, lpBuffer);
				}
			}
			break;
		}
	}

	return result;
}
