#include "Windows/WindowsWindow.h"
#include "Windows/WindowsInput.h"
#include "Logging/LogMacros.h"
#include "D3D11Device.h"

#include "imgui.h"

WNDCLASS FWindowsWindow::WindowClass = {};

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (ImGui_ImplWin32_WndProcHandler(hwnd, uMsg, wParam, lParam))
    {
        return true;
    }

    switch (uMsg)
    {
        case WM_SIZE:
        {
            if (FD3D11Device::GetDevice()->GetID3D11Device() != nullptr && wParam != SIZE_MINIMIZED)
            {
                FD3D11RenderTarget::GetRenderTarget()->CleanupRenderTarget();
                FD3D11Device::GetDevice()->GetIDXGISwapChain()->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0);
                FD3D11RenderTarget::CreateRenderTarget(FD3D11Device::GetDevice()->GetID3D11Device());
            }

            return 0;
        }
        case WM_SYSCOMMAND:
        {
            if ((wParam & 0xfff0) == SC_KEYMENU)
            {
                return 0;
            }

            break;
        }
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
        case WM_DPICHANGED:
        {
            if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
            {
                const RECT* SuggestedRect = (RECT*)lParam;
                SetWindowPos(hwnd, NULL, SuggestedRect->left, SuggestedRect->top, SuggestedRect->right - SuggestedRect->left, SuggestedRect->bottom - SuggestedRect->top, SWP_NOZORDER | SWP_NOACTIVATE);
            }

            break;
        }
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

std::unique_ptr<FWindowsWindow> FWindowsWindow::Create()
{
    return std::make_unique<FWindowsWindow>();
}

void FWindowsWindow::Initialize(HINSTANCE InHInstance, HINSTANCE InHPrevInstance, const bool bShowImmediately)
{
    WindowClass.lpfnWndProc = WindowProc;
    WindowClass.hInstance = InHInstance;
    WindowClass.lpszClassName = "FrostWindow";
    WindowClass.hCursor = LoadCursor(InHInstance, IDC_ARROW);

    RegisterClass(&WindowClass);

    RECT AvailableArea;
    SystemParametersInfo(SPI_GETWORKAREA, 0, &AvailableArea, 0);

    ScreenWidth = AvailableArea.right - AvailableArea.left;
    ScreenHeight = AvailableArea.bottom - AvailableArea.top;

    WindowWidth = 1280;
    WindowHeight = 720;

    Hwnd = CreateWindowEx(
            0,
            "FrostWindow",
            "Frost Game - Scene - Windows, Mac & Linux - Frost Editor 2023.1.0 <DX11>",
            WS_POPUP | WS_MAXIMIZEBOX,
            bIsWindowMaximized ? AvailableArea.left : (ScreenWidth - WindowWidth) / 2,
            bIsWindowMaximized ? AvailableArea.top : (ScreenHeight - WindowHeight) / 2,
            bIsWindowMaximized ? ScreenWidth : WindowWidth,
            bIsWindowMaximized ? ScreenHeight : WindowHeight,
            NULL,
            NULL,
            InHInstance,
            NULL);

    ShowWindow(Hwnd, bShowImmediately);
    UpdateWindow(Hwnd);

    ShowWindow(Hwnd, bIsWindowMaximized ? SW_MAXIMIZE : SW_SHOWMAXIMIZED);
    SetWindowPos(Hwnd, NULL, 0, 0, WindowWidth, WindowHeight, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);

    FD3D11Device::CreateDevice(Hwnd);
}

void FWindowsWindow::Start()
{
    Input = std::make_unique<FWindowsInput>(Hwnd);
}

void FWindowsWindow::Update()
{
    Input->Update();

    FD3D11Device::GetDevice()->GetIDXGISwapChain()->Present(1, 0);
}

void FWindowsWindow::Shutdown()
{
    FD3D11Device::GetDevice()->CleanupDevice();

    DestroyWindow(Hwnd);
    UnregisterClass(WindowClass.lpszClassName, WindowClass.hInstance);
}

void FWindowsWindow::SetWindowMinimized()
{
    ShowWindow(Hwnd, SW_MINIMIZE);
}

void FWindowsWindow::SetWindowMaximized(bool bInShouldMaximize)
{
    if (bInShouldMaximize)
    {
        SetWindowLong(Hwnd, GWL_STYLE, WS_POPUP | WS_MAXIMIZEBOX);
        bIsWindowMaximized = true;
    }
    else
    {
        ShowWindow(Hwnd, SW_RESTORE);
        bIsWindowMaximized = false;
    }

    ShowWindow(Hwnd, true);
}

void FWindowsWindow::SetWindowFullscreen(bool bInShouldFullscreen)
{
    if (bInShouldFullscreen)
    {
        SetWindowLong(Hwnd, GWL_STYLE, WS_POPUP | WS_MAXIMIZE);
        bIsWindowFullscreen = true;
    }
    else
    {
        SetWindowLong(Hwnd, GWL_STYLE, WS_POPUP | WS_MAXIMIZEBOX);
        bIsWindowFullscreen = false;
    }

    ShowWindow(Hwnd, true);
}

int FWindowsWindow::GetWindowX()
{
    return WindowRect.left;
}

int FWindowsWindow::GetWindowY()
{
    return WindowRect.top;
}

bool FWindowsWindow::IsWindowMaximized()
{
    return bIsWindowMaximized;
}

bool FWindowsWindow::IsWindowFullscreen()
{
    return bIsWindowFullscreen;
}

int FWindowsWindow::GetWindowWidth()
{
    return WindowWidth;
}

int FWindowsWindow::GetWindowHeight()
{
    return WindowHeight;
}

std::unique_ptr<FInput>& FWindowsWindow::GetInput()
{
    return Input;
}

HWND FWindowsWindow::GetHwnd()
{
    if (Hwnd)
    {
        return Hwnd;
    }
    else
    {
        FROST_LOG_ERROR("Failed to retrieve 'Hwnd'!");
        return nullptr;
    }
}