#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#include "timeapi.h"

#include "vn_platform.h"

#include "win32_main.h"
#include "win32_opengl.cpp"

global win32_state Global_Win32State;
global WINDOWPLACEMENT Global_WindowPosition = {sizeof(Global_WindowPosition)};;

static void Win32_PlatformError(char *Message, bool IsFatal)
{
    MessageBoxA(0, Message, "vn - Platform Error", MB_OK|(IsFatal?MB_ICONSTOP:MB_ICONEXCLAMATION));
    
    if(IsFatal)
    {
        ExitProcess((UINT)-1);
    }
}

static PLATFORM_SHOW_MESSAGE(Win32_ShowMessage)
{
    DWORD Flags = MB_OK;
    switch(Type)
    {
        case Platform_Message_Info:    { Flags |= MB_ICONINFORMATION; } break;
        case Platform_Message_Warning: { Flags |= MB_ICONWARNING;     } break;
        case Platform_Message_Error:   { Flags |= MB_ICONEXCLAMATION; } break;
        case Platform_Message_Fatal:   { Flags |= MB_ICONSTOP;        } break;
        
        InvalidDefaultCase;
    }
    
    // sixten(NOTE): Check for null-termination.
    Assert(Message.Data[Message.Count] == 0);
    
    MessageBoxA(0, (char *)Message.Data, "vn - A message from the developer", Flags);
    
    if(Type == Platform_Message_Fatal)
    {
        ExitProcess((UINT)-1);
    }
}

static u64 Win32_GetPageSize(void)
{
    SYSTEM_INFO Info;
    GetSystemInfo(&Info);
    return(Info.dwPageSize);
}

static PLATFORM_RESERVE(Win32_Reserve)
{
    u64 GigabyteAlignedSize = Size+Gigabytes(1)-1;
    GigabyteAlignedSize -= GigabyteAlignedSize%Gigabytes(1);
    void *Result = VirtualAlloc(0, GigabyteAlignedSize, MEM_RESERVE, PAGE_NOACCESS);
    return(Result);
}

static PLATFORM_RELEASE(Win32_Release)
{
    VirtualFree(Pointer, 0, MEM_RELEASE);
}

static PLATFORM_COMMIT(Win32_Commit)
{
    u64 PageAlignedSize = Size+Win32_GetPageSize()-1;
    PageAlignedSize -= PageAlignedSize%Win32_GetPageSize();
    VirtualAlloc(Pointer, PageAlignedSize, MEM_COMMIT, PAGE_READWRITE);
}

static PLATFORM_DECOMMIT(Win32_Decommit)
{
    VirtualFree(Pointer, Size, MEM_DECOMMIT);
}

static PLATFORM_OPEN_FILE(Win32_OpenFile)
{
    DWORD DesiredAccess = 0;
    if(FileAccess & PlatformAccess_Read)
    {
        DesiredAccess |= GENERIC_READ;
    }
    if(FileAccess & PlatformAccess_Write)
    {
        DesiredAccess |= GENERIC_WRITE;
    }
    
    DWORD CreationAttributes = 0;
    if(FileAccess & PlatformAccess_Read)
    {
        CreationAttributes = OPEN_EXISTING;
    }
    if(FileAccess & PlatformAccess_Write)
    {
        CreationAttributes = CREATE_ALWAYS;
    }
    
    temporary_memory Scratch = GetScratch(0, 0);
    
    string FullPath = PushFormat(Scratch.Arena, "%S\\%S", Global_Win32State.ContentsPath, Path);
    HANDLE File = CreateFileA((char *)FullPath.Data, DesiredAccess, 0, 0, CreationAttributes, 0, 0);
    
    ReleaseScratch(Scratch);
    
    platform_file_handle Result = {};
    Result.Platform = (u64)File;
    Result.IsValid = (File != INVALID_HANDLE_VALUE);
    
    return(Result);
}

static PLATFORM_CLOSE_FILE(Win32_CloseFile)
{
    HANDLE File = (HANDLE)Handle.Platform;
    if(File != INVALID_HANDLE_VALUE)
    {
        CloseHandle(File);
    }
}

static PLATFORM_READ_FILE(Win32_ReadFile)
{
    HANDLE File = (HANDLE)Handle.Platform;
    if(File != INVALID_HANDLE_VALUE)
    {
        DWORD BytesRead;
        ReadFile(File, Dest, Size, &BytesRead, 0);
        
        Assert(BytesRead == Size);
    }
}

static PLATFORM_WRITE_FILE(Win32_WriteFile)
{
    HANDLE File = (HANDLE)Handle.Platform;
    if(File != INVALID_HANDLE_VALUE)
    {
        DWORD BytesWritten;
        WriteFile(File, Source, Size, &BytesWritten, 0);
        
        Assert(BytesWritten == Size);
    }
}

static PLATFORM_GET_FILE_SIZE(Win32_GetFileSize)
{
    u64 Result = 0;
    
    HANDLE File = (HANDLE)Handle.Platform;
    if(File != INVALID_HANDLE_VALUE)
    {
        LARGE_INTEGER FileSize;
        GetFileSizeEx(File, &FileSize);
        
        Result = FileSize.QuadPart;
    }
    
    return(Result);
}

static PLATFORM_SET_CURSOR(Win32_SetCursor)
{
    Global_Win32State.Cursor = Cursor;
}

static PLATFORM_BEGIN_FILE_ITER(Win32_BeginFileIter)
{
    win32_file_find_data *FileFindData = PushStruct(Arena, win32_file_find_data);
    temporary_memory Scratch = GetScratch(&Arena, 1);
    Path = PushFormat(Scratch.Arena, "%S/%S*", Global_Win32State.ContentsPath, Path);
    string16 Path16 = String16FromString8(Scratch.Arena, Path);
    FileFindData->Handle = FindFirstFileW((WCHAR *)Path16.Data, &FileFindData->FindData);
    ReleaseScratch(Scratch);
    platform_file_iter *Iter = (platform_file_iter *)FileFindData;
    return(Iter);
}

static PLATFORM_ADVANCE_FILE_ITER(Win32_AdvanceFileIter)
{
    b32 Result = false;
    win32_file_find_data *FileFindData = (win32_file_find_data *)Iter;
    WIN32_FIND_DATAW FindData = {};
    s64 InvalidCount = 0;
    for(;;)
    {
        if(FileFindData->FindFirstReturned)
        {
            Result = FindNextFileW(FileFindData->Handle, &FindData);
        }
        else
        {
            Result = (FileFindData->Handle != 0 && FileFindData->Handle != INVALID_HANDLE_VALUE);
            FindData = FileFindData->FindData;
            FileFindData->FindFirstReturned = true;
        }
        
        b32 NameIsInvalid = (FindData.cFileName[0] == '.' && (FindData.cFileName[1] == 0 || FindData.cFileName[1] == '.'));
        if(NameIsInvalid && InvalidCount < 10)
        {
            InvalidCount += 1;
            continue;
        }
        
        break;
    }
    
    if(Result != 0)
    {
        string16 Name16 = {};
        Name16.Data = (u16 *)FindData.cFileName;
        for(u16 Index = 0; Index < MAX_PATH; Index += 1)
        {
            if(FindData.cFileName[Index] == 0)
            {
                break;
            }
            Name16.Count += 1;
        }
        
        *OutInfo = {};
        OutInfo->Name = String8FromString16(Arena, Name16);
        OutInfo->IsDirectory = FindData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY;
    }
    return(Result);
}

static PLATFORM_END_FILE_ITER(Win32_EndFileIter)
{
    win32_file_find_data *FileFindData = (win32_file_find_data *)Iter;
    FindClose(FileFindData->Handle);
}

inline u64 Win32_GetWallClock(void)
{
    LARGE_INTEGER Query;
    QueryPerformanceCounter(&Query);
    
    u64 Result = Query.QuadPart;
    return(Result);
}

inline r64 Win32_GetSecondsElapsed(u64 Start, u64 End)
{
    u64 Elapsed = End - Start;
    
    r64 Result = (r64)Elapsed/(r64)Global_Win32State.PerformanceFrequency;
    return(Result);
}

inline FILETIME Win32_GetLastWriteTime(char *Path)
{
    FILETIME Result = {};
    
    HANDLE File = CreateFileA(Path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    if(File)
    {
        FILETIME Creation, LastAccess, LastWrite;
        GetFileTime(File, &Creation, &LastAccess, &LastWrite);
        
        Result = LastWrite;
        
        CloseHandle(File);
    }
    
    return(Result);
}

static win32_loaded_code Win32_LoadCode(void)
{
    win32_loaded_code Code = {};
    
    win32_state *State = &Global_Win32State;
    
    if(CopyFile(State->DLLPath, State->TempDLLPath, FALSE))
    {
        Code.LastWriteTime = Win32_GetLastWriteTime(State->DLLPath);
        
        Code.DLL = LoadLibraryA(State->TempDLLPath);
        if(Code.DLL)
        {
            Code.UpdateAndRender = (vn_update_and_render *)GetProcAddress(Code.DLL, "VN_UpdateAndRender");
            if(Code.UpdateAndRender)
            {
                Code.IsValid = true;
            }
        }
    }
    
    return(Code);
}

static void Win32_UnloadCode(win32_loaded_code *Code)
{
    if(Code->DLL)
    {
        FreeLibrary(Code->DLL);
    }
    
    *Code = {};
}

static void Win32_UpdateCode(win32_loaded_code *Code)
{
    win32_state *State = &Global_Win32State;
    
    FILETIME LastWriteTime = Win32_GetLastWriteTime(State->DLLPath);
    if(CompareFileTime(&Code->LastWriteTime, &LastWriteTime) != 0)
    {
        Win32_UnloadCode(Code);
        *Code = Win32_LoadCode();
        
        // sixten(NOTE): Sometimes the program decides to crash upon reloads, so we just wait for those to be over...
        Sleep(500);
    }
}

static PLATFORM_TOGGLE_FULLSCREEN(Win32_ToggleFullscreen)
{
    HWND Window = Global_Win32State.Window;
    
    DWORD Style = GetWindowLong(Window, GWL_STYLE);
    if(Style & WS_OVERLAPPEDWINDOW)
    {
        MONITORINFO MonitorInfo = {sizeof(MonitorInfo)};
        if(GetWindowPlacement(Window, &Global_WindowPosition) &&
           GetMonitorInfo(MonitorFromWindow(Window, MONITOR_DEFAULTTOPRIMARY), &MonitorInfo))
        {
            // sixten(NOTE): This doesn't work when the window is maximized. One wordaround would be to set the 
            // window to "normal" size using ShowWindow(Window, SW_SHOWNORMAL) but it looks *very* scuffed.
            SetWindowLong(Window, GWL_STYLE, Style & ~WS_OVERLAPPEDWINDOW);
            SetWindowPos(Window, HWND_TOP,
                         MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.top,
                         MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left,
                         MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top,
                         SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
        }
    }
    else
    {
        SetWindowLong(Window, GWL_STYLE, Style | WS_OVERLAPPEDWINDOW);
        SetWindowPlacement(Window, &Global_WindowPosition);
        SetWindowPos(Window, 0, 0, 0, 0, 0,
                     SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_FRAMECHANGED);
    }
}

static PLATFORM_SET_CLIPBOARD(Win32_SetClipboard)
{
    temporary_memory Scratch = GetScratch();
    if(OpenClipboard(0))
    {
        EmptyClipboard();
        
        string16 String16 = String16FromString8(Scratch.Arena, String);
        
        HANDLE CopyHandle = GlobalAlloc(GMEM_MOVEABLE, String16.Count*sizeof(u16)+1);
        if(CopyHandle)
        {
            u16 *CopyBuffer = (u16 *)GlobalLock(CopyHandle);
            Copy(CopyBuffer, String16.Data, String16.Count*sizeof(u16));
            CopyBuffer[String.Count] = 0;
            GlobalUnlock(CopyHandle);
            SetClipboardData(CF_UNICODETEXT, CopyHandle);
        }
        CloseClipboard();
    }
    ReleaseScratch(Scratch);
}

static PLATFORM_GET_CLIPBOARD(Win32_GetClipboard)
{
    string Result = {};
    if(IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(0))
    {
        HANDLE DataHandle = GetClipboardData(CF_UNICODETEXT);
        if(DataHandle)
        {
            u16 *Data = (u16 *)GlobalLock(DataHandle);
            if(Data)
            {
                s64 Count = StringLength16(Data);
                Result = String8FromString16(Arena, MakeString16(Data, Count));
                GlobalUnlock(DataHandle);
            }
        }
        CloseClipboard();
    }
    return(Result);
}

inline v2 Win32_GetMouseP(HWND Window)
{
    POINT Point;
    GetCursorPos(&Point);
    ScreenToClient(Window, &Point);
    
    v2 Result = V2(Point.x, Point.y);
    return(Result);
}

inline v2 Win32_GetWindowDim(HWND Window)
{
    RECT ClientRect;
    GetClientRect(Window, &ClientRect);
    
    v2 Result = V2(ClientRect.right - ClientRect.left, ClientRect.bottom - ClientRect.top);
    return(Result);
}

inline platform_modifiers Win32_GetModifiers(void)
{
    platform_modifiers Modifiers = 0;
    
    if(GetKeyState(VK_CONTROL) & 0x8000)
    {
        Modifiers |= PlatformModifier_Ctrl;
    }
    if(GetKeyState(VK_SHIFT) & 0x8000)
    {
        Modifiers |= PlatformModifier_Shift;
    }
    if(GetKeyState(VK_MENU) & 0x8000)
    {
        Modifiers |= PlatformModifier_Alt;
    }
    
    return(Modifiers);
}

static LRESULT Win32_WindowCallback(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam)
{
    LRESULT Result = 0;
    
    win32_state *State = &Global_Win32State;
    
    temporary_memory Scratch = GetScratch(0, 0);
    
    platform_event *Event = 0;
    
    b32 ButtonIsUp = false;
    axis2 ScrollAxis = Axis2_Y;
    
    switch(Message)
    {
        case WM_CLOSE:
        {
            Event = PushStruct(State->EventArena, platform_event);
            Event->Type = PlatformEvent_WindowClose;
        } break;
        
        case WM_WINDOWPOSCHANGED:
        {
            // TODO(casey): For now, we are setting the window styles in here
            // because sometimes Windows can reposition our window out of fullscreen
            // without going through our ToggleFullscreen(), and we want to put our
            // title bar and border back when it does!
            
            WINDOWPOS *NewPos = (WINDOWPOS *)LParam;
            
            b32 BecomingFullscreen = false;
            MONITORINFO MonitorInfo = {sizeof(MonitorInfo)};
            if(GetMonitorInfo(MonitorFromWindow(Window, MONITOR_DEFAULTTOPRIMARY),
                              &MonitorInfo))
            {
                s32 MonWidth = (MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left);
                s32 MonHeight = (MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top);
                BecomingFullscreen = ((MonitorInfo.rcMonitor.left == NewPos->x) &&
                                      (MonitorInfo.rcMonitor.top == NewPos->y) &&
                                      (MonWidth == NewPos->cx) &&
                                      (MonHeight == NewPos->cy));
            }
            
            DWORD OldStyle = GetWindowLong(Window, GWL_STYLE);
            DWORD FullscreenStyle = OldStyle & ~WS_OVERLAPPEDWINDOW;
            DWORD WindowedStyle = OldStyle | WS_OVERLAPPEDWINDOW;
            DWORD NewStyle = (BecomingFullscreen) ? FullscreenStyle : WindowedStyle;
            
            if(NewStyle != OldStyle)
            {
                SetWindowLong(Window, GWL_STYLE, NewStyle);
            }
            
            Result = DefWindowProcA(Window, Message, WParam, LParam);
        } break;
        
        case WM_MOUSEHWHEEL:
        {
            ScrollAxis = Axis2_X;
        } fallthrough;
        case WM_MOUSEWHEEL:
        {
            Event = PushStruct(State->EventArena, platform_event);
            Event->Type = PlatformEvent_MouseScroll;
            Event->Scroll.E[ScrollAxis] = GET_WHEEL_DELTA_WPARAM(WParam) / 120.0;
        } break;
        
        case WM_LBUTTONUP:
        case WM_MBUTTONUP:
        case WM_RBUTTONUP:
        {
            ButtonIsUp = true;
        } fallthrough;
        
        case WM_LBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_RBUTTONDOWN:
        {
            platform_event_type Type = ButtonIsUp ? PlatformEvent_Release : PlatformEvent_Press;
            
            platform_key Key = Key_Invalid;
            switch(Message)
            {
                case WM_LBUTTONUP: case WM_LBUTTONDOWN: { Key = Key_MouseLeft; } break;
                case WM_MBUTTONUP: case WM_MBUTTONDOWN: { Key = Key_MouseMiddle; } break;
                case WM_RBUTTONUP: case WM_RBUTTONDOWN: { Key = Key_MouseRight; } break;
            }
            
            Event = PushStruct(State->EventArena, platform_event);
            Event->Type = Type;
            Event->Key = Key;
            Event->P = Win32_GetMouseP(Window);
        } break;
        
        case WM_SYSKEYUP:
        case WM_KEYUP:
        {
            ButtonIsUp = true;
        } fallthrough;
        case WM_SYSKEYDOWN:
        case WM_KEYDOWN:
        {
            platform_event_type Type = ButtonIsUp ? PlatformEvent_Release : PlatformEvent_Press;
            
            u32 VKCode = (u32)WParam;
            platform_key Key = Key_Invalid;
            
            if(VKCode >= 'A' && VKCode <= 'Z')
            {
                Key = (platform_key)(Key_A + (VKCode - 'A'));
            }
            else if(VKCode >= VK_F1 && VKCode <= VK_F12)
            {
                Key = (platform_key)(Key_F1 + (VKCode - VK_F1));
            }
            else if(VKCode == VK_LEFT) { Key = Key_Left; }
            else if(VKCode == VK_RIGHT) { Key = Key_Right; }
            else if(VKCode == VK_UP) { Key = Key_Up; }
            else if(VKCode == VK_DOWN) { Key = Key_Down; }
            else if(VKCode == VK_SPACE) { Key = Key_Space; }
            else if(VKCode == VK_RETURN) { Key = Key_Return; }
            else if(VKCode == VK_PRIOR) { Key = Key_PageUp; }
            else if(VKCode == VK_NEXT) { Key = Key_PageDown; }
            else if(VKCode == VK_HOME) { Key = Key_Home; }
            else if(VKCode == VK_END) { Key = Key_End; }
            else if(VKCode == VK_BACK) { Key = Key_Backspace; }
            else if(VKCode == VK_DELETE) { Key = Key_Delete; }
            
            if(Key != Key_Invalid)
            {
                Event = PushStruct(State->EventArena, platform_event);
                Event->Type = Type;
                Event->Key = Key;
            }
            
            if(!ButtonIsUp && (VKCode == VK_RETURN) && (LParam & (1 << 29)))
            {
                Win32_ToggleFullscreen();
            }
            else
            {
                Result = DefWindowProc(Window, Message, WParam, LParam);
            }
            
        } break;
        
        case WM_CHAR:
        {
            u32 Codepoint = (u32)WParam;
            if(Codepoint == '\r')
            {
                Codepoint = '\n';
            }
            
            if((Codepoint >= 32 && Codepoint != 127) || Codepoint == '\t' || Codepoint == '\n')
            {
                Event = PushStruct(State->EventArena, platform_event);
                Event->Type = PlatformEvent_Text;
                Event->Codepoint = Codepoint;
            }
        } break;
        
        case WM_SETCURSOR:
        {
            range2_r32 WindowRect = {};
            WindowRect.Max = Win32_GetWindowDim(Window);
            if(InRange(WindowRect, Win32_GetMouseP(Window)))
            {
                persist HCURSOR CursorTable[PlatformCursor_Count];
                persist b32 CursorTableLoaded = false;
                if(!CursorTableLoaded)
                {
                    CursorTable[PlatformCursor_Arrow] = LoadCursor(0, IDC_ARROW);
                    CursorTable[PlatformCursor_Cross] = LoadCursor(0, IDC_CROSS);
                    CursorTable[PlatformCursor_Hand] = LoadCursor(0, IDC_HAND);
                    CursorTable[PlatformCursor_Help] = LoadCursor(0, IDC_HELP);
                    CursorTable[PlatformCursor_IBeam] = LoadCursor(0, IDC_IBEAM);
                    CursorTable[PlatformCursor_SlashedCircle] = LoadCursor(0, IDC_NO);
                    CursorTable[PlatformCursor_ArrowAll] = LoadCursor(0, IDC_SIZEALL);
                    CursorTable[PlatformCursor_ArrowNESW] = LoadCursor(0, IDC_SIZENESW);
                    CursorTable[PlatformCursor_ArrowVertical] = LoadCursor(0, IDC_SIZENS);
                    CursorTable[PlatformCursor_ArrowNWSE] = LoadCursor(0, IDC_SIZENWSE);
                    CursorTable[PlatformCursor_ArrowHorizontal] = LoadCursor(0, IDC_SIZEWE);
                    CursorTable[PlatformCursor_Wait] = LoadCursor(0, IDC_WAIT);
                    
                    CursorTableLoaded = true;
                }
                
                SetCursor(CursorTable[Global_Win32State.Cursor]);
            }
            else
            {
                DefWindowProc(Window, Message, WParam, LParam);
            }
        } break;
        
        default:
        {
            Result = DefWindowProc(Window, Message, WParam, LParam);
        } break;
    }
    
    if(Event)
    {
        Event->Modifiers = Win32_GetModifiers();
        DLLInsertLast(State->EventList.First, State->EventList.Last, Event);
    }
    
    ReleaseScratch(Scratch);
    
    return(Result);
}

static void Win32_ProcessInput(vn_input *Input, HWND Window, r32 dtForFrame)
{
    win32_state *State = &Global_Win32State;
    
    ArenaClear(State->EventArena);
    
    MSG Message;
    while(PeekMessage(&Message, Window, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&Message);
        DispatchMessageA(&Message);
    }
    
    Input->EventList = &State->EventList;
    
    v2 NewMouseP = Win32_GetMouseP(Window);
    v2 OldMouseP = Input->MouseP;
    
    Input->dMouseP = NewMouseP - OldMouseP;
    Input->MouseP  = NewMouseP;
    
    Input->dtForFrame = dtForFrame;
}

static void Win32_EnforceFrameRate(u64 FrameBegin, r32 TargetFrameRate)
{
    win32_state *State = &Global_Win32State;
    
    r64 TimeElapsed = Win32_GetSecondsElapsed(FrameBegin, Win32_GetWallClock());
    r64 Target = 1.0 / TargetFrameRate;
    
    r64 ToSleep = Target - TimeElapsed;
    if(ToSleep > 0)
    {
        u64 MSToSleep = (u64)Floor(ToSleep*1000);
        if(State->SleepIsGranular)
        {
            Sleep(MSToSleep);
        }
        
        while(ToSleep > 0)
        {
            TimeElapsed = Win32_GetSecondsElapsed(FrameBegin, Win32_GetWallClock());
            ToSleep = Target - TimeElapsed;
        }
    }
}

inline void Win32_GetRelevantPaths(win32_state *State)
{
    GetModuleFileName(0, State->EXEPath, ArrayCount(State->EXEPath));
    if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
    {
        Win32_PlatformError("Path to executable is too long. Try running the game from another directory", true);
    }
    
    string EXEPathString = MakeString(State->EXEPath);
    
    s64 OnePastLastSlash = LastIndexOf(EXEPathString, '\\') + 1;
    
    s64 BuildIndex = LastIndexOf(EXEPathString, StrLit("\\build\\")) + 1;
    if(BuildIndex == 0)
    {
        BuildIndex = LastIndexOf(EXEPathString, StrLit("//build//")) + 1;
    }
    
    if(BuildIndex == 0)
    {
        State->ContentsPath = MakeString(EXEPathString.Data, OnePastLastSlash);
    }
    else
    {
        State->ContentsPath = MakeString(EXEPathString.Data, BuildIndex);
    }
    
    string DLLName = StrLit("vn.dll");
    Copy(State->DLLPath, State->EXEPath, OnePastLastSlash);
    Copy(State->DLLPath+OnePastLastSlash, DLLName.Data, DLLName.Count);
    
    string TempDLLName = StrLit("temp_vn.dll");
    Copy(State->TempDLLPath, State->EXEPath, OnePastLastSlash);
    Copy(State->TempDLLPath+OnePastLastSlash, TempDLLName.Data, TempDLLName.Count);
}

int WinMain(HINSTANCE Instance, HINSTANCE PreviousInstance, LPSTR CommandLine, int ShowCommand)
{
    RegisterPlatformFunctions(Win32);
    
    thread_context ThreadContext = AllocateThreadContext();
    SetThreadContext(&ThreadContext);
    
    // sixten: Setup Win32 platform state.
    {
        win32_state *State = &Global_Win32State;
        Win32_GetRelevantPaths(State);
        
        LARGE_INTEGER FrequencyQuery;
        QueryPerformanceFrequency(&FrequencyQuery);
        State->PerformanceFrequency = FrequencyQuery.QuadPart;
        
        State->EventArena = ArenaAllocate(Gigabytes(1));
        
        State->SleepIsGranular = (timeBeginPeriod(1) == TIMERR_NOERROR);
    }
    
    WNDCLASS WindowClass = {};
    WindowClass.lpszClassName = "vn-window-class";
    WindowClass.lpfnWndProc = Win32_WindowCallback;
    WindowClass.hCursor = LoadCursorA(0, IDC_ARROW);
    WindowClass.style = CS_OWNDC;
    
    if(RegisterClassA(&WindowClass))
    {
        HWND Window = CreateWindowEx(0,
                                     WindowClass.lpszClassName,
                                     "vn - August 2023 Build",
                                     WS_OVERLAPPEDWINDOW,
                                     CW_USEDEFAULT, CW_USEDEFAULT,
                                     CW_USEDEFAULT, CW_USEDEFAULT,
                                     0, 0, Instance, 0);
        if(Window)
        {
            Global_Win32State.Window = Window;
            
            vn_input Input = {};
            vn_render_commands RenderCommands = {};
            
            // sixten: Setup OpenGL
            HDC DeviceContext = GetDC(Window);
            Win32_CreateOpenGLContext(DeviceContext);
            opengl_context OpenGLContext = OpenGL_SetupContext(&RenderCommands, 16*1024);
            
            vn_memory Memory = {};
            Memory.PlatformAPI = Platform;
            
            win32_loaded_code LoadedCode = Win32_LoadCode();
            
            ShowWindow(Window, SW_SHOWNORMAL);
            
            u64 CurrentTime = Win32_GetWallClock();
            
            while(!Input.ExitRequested)
            {
                u64 NewTime = Win32_GetWallClock();
                r64 dtForFrame = Win32_GetSecondsElapsed(CurrentTime, NewTime);
                CurrentTime = NewTime;
                
                Win32_ProcessInput(&Input, Window, dtForFrame);
                
                Win32_SetCursor(PlatformCursor_Arrow);
                
                // sixten: Update and render frame.
                {
                    Win32_UpdateCode(&LoadedCode);
                    
                    OpenGL_BeginFrame(&RenderCommands, Win32_GetWindowDim(Window));
                    
                    if(LoadedCode.IsValid)
                    {
                        LoadedCode.UpdateAndRender(&ThreadContext, &Memory, &Input, &RenderCommands);
                    }
                    
                    b32 UseVSync = (Input.RefreshRate == 0);
                    wglSwapIntervalEXT(UseVSync);
                    
                    OpenGL_EndFrame(&OpenGLContext, &RenderCommands);
                    wglSwapLayerBuffers(DeviceContext, WGL_SWAP_MAIN_PLANE);
                }
                
                b32 ShouldLimitFrameRate = (Input.RefreshRate > 0);
                if(ShouldLimitFrameRate)
                {
                    Win32_EnforceFrameRate(CurrentTime, Input.RefreshRate);
                }
            }
        }
        else
        {
            Win32_PlatformError("Unable to create window.", true);
        }
    }
    else
    {
        Win32_PlatformError("Unable to register window class.", true);
    }
    
    return(0);
}