#include "filesystem.h"
#include "thread_queue.h"

#ifndef NOMINMAX
    #define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
    #define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <stdio.h>
#include <intrin.h>

#include "win32_thread.cpp"

// Basic begin
void *VReserve(size_t size) {
    void *result = (uint8_t *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
    return result;
}

bool VCommit(void *p, size_t size) {
    void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
    return result ? true : false;
}

bool VRelease(void *p) {
    BOOL result = VirtualFree(p, 0, MEM_RELEASE);
    return result ? true : false;
}

bool VDecommit(void *p, size_t size) {
    BOOL result = VirtualFree(p, size, MEM_DECOMMIT);
    return result ? true : false;
}
// Basic end

Process RunEx(String args) {
    Scratch      scratch;
    wchar_t     *application_name = NULL;
    wchar_t     *cmd              = ToWidechar(scratch, args);
    BOOL         inherit_handles  = FALSE;
    DWORD        creation_flags   = 0;
    void        *enviroment       = NULL;
    wchar_t     *working_dir      = NULL;
    STARTUPINFOW startup_info     = {};
    startup_info.cb               = sizeof(STARTUPINFOW);
    Process result                = {};
    Assert(sizeof(result.platform) >= sizeof(PROCESS_INFORMATION));
    PROCESS_INFORMATION *process_info = (PROCESS_INFORMATION *)result.platform;
    BOOL                 success      = CreateProcessW(application_name, cmd, NULL, NULL, inherit_handles, creation_flags, enviroment, working_dir, &startup_info, process_info);
    result.is_valid                   = true;
    if (!success) {
        result.is_valid = false;

        LPVOID lpMsgBuf;
        DWORD  dw = GetLastError();
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);

        char  *buff    = (char *)lpMsgBuf;
        size_t buffLen = strlen((const char *)buff);
        if (buffLen > 0 && buff[buffLen - 1] == '\n') {
            buff[buffLen - 1] = 0;
        }

        // @warning: leak! but we don't care
        result.error_message = Format(GetSystemAllocator(), "Failed to create process | message: %s | cmd: %.*s", (char *)lpMsgBuf, FmtString(args));
        LocalFree(lpMsgBuf);
    }
    return result;
}

int Wait(Process *process) {
    Assert(process->is_valid);
    PROCESS_INFORMATION *pi = (PROCESS_INFORMATION *)process->platform;
    WaitForSingleObject(pi->hProcess, INFINITE);

    DWORD exit_code;
    BOOL  err = GetExitCodeProcess(pi->hProcess, &exit_code);
    Assert(err != 0);

    CloseHandle(pi->hProcess);
    CloseHandle(pi->hThread);
    process[0] = {};
    return (int)exit_code;
}

bool InitOS() {
    SetConsoleOutputCP(CP_UTF8);

    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hOut != INVALID_HANDLE_VALUE) {
        DWORD dwMode = 0;
        if (GetConsoleMode(hOut, &dwMode)) {
            dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
            if (SetConsoleMode(hOut, dwMode)) {
                return true;
            } else {
                printf("Failed to enable colored terminal output C\n");
            }
        } else {
            printf("Failed to enable colored terminal output B\n");
        }
    } else {
        printf("Failed to enable colored terminal output A\n");
    }
    return false;
}

String ReadFile(Allocator arena, String path) {
    bool   success = false;
    String result  = {};

    wchar_t wpath[1024];
    CreateWidecharFromChar(wpath, Lengthof(wpath), path.data, path.len);
    HANDLE handle = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (handle != INVALID_HANDLE_VALUE) {
        LARGE_INTEGER file_size;
        if (GetFileSizeEx(handle, &file_size)) {
            if (file_size.QuadPart != 0) {
                result.len  = (int64_t)file_size.QuadPart;
                result.data = (char *)AllocSize(arena, result.len + 1);
                DWORD read;
                if (ReadFile(handle, result.data, (DWORD)result.len, &read, NULL)) { // @todo: can only read 32 byte size files?
                    if (read == result.len) {
                        success                 = true;
                        result.data[result.len] = 0;
                    }
                }
            }
        }
        CloseHandle(handle);
    }

    if (!success) {
        Dealloc(arena, &result.data);
        result = {};
    }

    return result;
}

typedef struct Win32_FileIter {
    HANDLE           handle;
    WIN32_FIND_DATAW data;
} Win32_FileIter;

String GetAbsolutePath(Allocator arena, String relative) {
    wchar_t wpath[1024];
    CreateWidecharFromChar(wpath, Lengthof(wpath), relative.data, relative.len);
    wchar_t wpath_abs[1024];
    DWORD   written = GetFullPathNameW((wchar_t *)wpath, Lengthof(wpath_abs), wpath_abs, 0);
    if (written == 0)
        return {};
    String path = ToString(arena, {(wchar_t *)wpath_abs, written});
    NormalizePathInPlace(path);
    return path;
}

bool IsValid(const FileIter &it) {
    return it.is_valid;
}

void Advance(FileIter *it) {
    if (it->temp.arena) EndTemp(it->temp);
    it->temp = BeginTemp(it->arena);
    while (FindNextFileW(it->w32->handle, &it->w32->data) != 0) {
        WIN32_FIND_DATAW *data = &it->w32->data;

        // Skip '.' and '..'
        if (data->cFileName[0] == '.' && data->cFileName[1] == '.' && data->cFileName[2] == 0) continue;
        if (data->cFileName[0] == '.' && data->cFileName[1] == 0) continue;

        it->is_directory      = data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
        it->filename          = ToString(*it->arena, (wchar_t *)data->cFileName, WideLength(data->cFileName));
        const char *is_dir    = it->is_directory ? "/" : "";
        const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/";
        it->relative_path     = Format(*it->arena, "%.*s%s%.*s%s", FmtString(it->path), separator, FmtString(it->filename), is_dir);
        it->absolute_path     = GetAbsolutePath(*it->arena, it->relative_path);
        it->is_valid          = true;

        if (it->is_directory) {
            Assert(it->relative_path.data[it->relative_path.len - 1] == '/');
            Assert(it->absolute_path.data[it->absolute_path.len - 1] == '/');
        }
        return;
    }

    it->is_valid = false;
    DWORD error  = GetLastError();
    Assert(error == ERROR_NO_MORE_FILES);
    FindClose(it->w32->handle);
    Dealloc(it->allocator, &it->arena);
}

FileIter IterateFiles(Allocator alo, String path) {
    FileIter it  = {0};
    it.allocator = alo;
    it.arena     = AllocArena(alo, MiB(2));
    it.path      = path;

    String   modified_path   = Format(*it.arena, "%.*s\\*", FmtString(path));
    String16 modified_path16 = ToString16(*it.arena, modified_path);

    it.w32         = AllocType(*it.arena, Win32_FileIter);
    it.w32->handle = FindFirstFileW(modified_path16.data, &it.w32->data);
    if (it.w32->handle == INVALID_HANDLE_VALUE) {
        it.is_valid = false;
        return it;
    }

    Advance(&it);
    return it;
}

#if _WIN32
    #include <Windows.h>
double get_time_in_micros(void) {
    static double invfreq;
    if (!invfreq) {
        LARGE_INTEGER frequency;
        QueryPerformanceFrequency(&frequency);
        invfreq = 1000000.0 / frequency.QuadPart;
    }
    LARGE_INTEGER counter;
    QueryPerformanceCounter(&counter);
    return counter.QuadPart * invfreq;
}
#else
    #include <unistd.h>
double get_time_in_micros(void) {
    struct timespec spec;
    clock_gettime(CLOCK_MONOTONIC, &spec);
    return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000);
}
#endif

bool WriteFile(String path, String data) {
    bool    result = false;
    wchar_t wpath[1024];
    CreateWidecharFromChar(wpath, Lengthof(wpath), path.data, path.len);

    DWORD  access               = GENERIC_WRITE;
    DWORD  creation_disposition = CREATE_ALWAYS;
    HANDLE handle               = CreateFileW(wpath, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL);
    if (handle != INVALID_HANDLE_VALUE) {
        DWORD bytes_written = 0;
        Assert(data.len == (DWORD)data.len); // @Todo: can only read 32 byte size files?
        BOOL error = WriteFile(handle, data.data, (DWORD)data.len, &bytes_written, NULL);
        if (error == TRUE) {
            if (bytes_written == data.len) {
                result = true;
            }
        }
        CloseHandle(handle);
    }

    return result;
}

String GetExePath(Allocator allocator) {
    wchar_t wbuffer[1024];
    DWORD   wsize = GetModuleFileNameW(0, wbuffer, Lengthof(wbuffer));
    Assert(wsize != 0);

    String path = ToString(allocator, wbuffer, wsize);
    NormalizePathInPlace(path);
    return path;
}

String GetExeDir(Allocator allocator) {
    Scratch scratch;
    String  path = GetExePath(scratch);
    path         = ChopLastSlash(path);
    path         = Copy(allocator, path);
    return path;
}
