#include "Window.h"
#include "Ground.h"
#include "Stage.h"
#include "imgui/imgui.h"

Mouse Window::mouse;

Window::Window(HWND& hWnd, HINSTANCE& hInstance, int nCmdShow, int x, int y, Logger& logger)
{
    // this struct holds information for the window class
    WNDCLASSEX wc;

    // clear out the window class for use
    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    // fill in the struct with the needed information
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = HandleMsgSetup;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = "WindowClass";

    // register the window class
    RegisterClassEx(&wc);

    // create rectangle for client area
    RECT wr = { 0, 0, x, y };     // set the size only, but not the position
    AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);  // adjust the size

    // create the window and use the result as the handle
    hWnd = CreateWindowEx(
        NULL,
        "WindowClass",          // name of the window class
        "Oasis Demo",           // title of the window
        WS_OVERLAPPEDWINDOW,    // window style
        300,                    // x-position of the window
        200,                    // y-position of the window
        x,                      // width of the window
        y,                      // height of the window
        NULL,                   // we have no parent window, NULL
        NULL,                   // we aren't using menus, NULL
        hInstance,              // application handle
        NULL);                  // used with multiple windows, NULL

    // display the window on the screen
    ShowWindow(hWnd, nCmdShow);

    // Init graphics
    pGfx = std::make_unique<Graphics>(hWnd, x, y);

    // Set projection
    pGfx->SetProjection(DirectX::XMMatrixPerspectiveLH(1.0f, float(y) / float(x), 0.5f, 40.0f));

    // Init scenes
    this->InitScenes(logger);
}

LRESULT CALLBACK Window::HandleMsgSetup(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
{
    // use create parameter passed in from CreateWindow() to store window class pointer at WinAPI side
    if (msg == WM_NCCREATE)
    {
        // extract ptr to window class from creation data
        // 使用CREATESTRUCT函数的第一个参数lpCreateParams，通过reinterpret_cast，获取到CREATESTRUCT函数的指针
        // lpCreateParams的指针和CREATESTRUCT的指针指向同一个物理地址，只是寻址对象不同
        const CREATESTRUCTW* const pCreate = reinterpret_cast<CREATESTRUCTW*>(lParam);

        // 取得指向Window的指针
        Window* const pWnd = static_cast<Window*>(pCreate->lpCreateParams);

        // 将Window类，存储到WinAPI
        // set WinAPI-managed user data to store ptr to window class
        SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pWnd));
        // set message proc to normal (non-setup) handler now that setup is finished
        SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&Window::HandleMsgThunk));
        // forward message to window class handler
        return pWnd->HandleMsg(hWnd, msg, wParam, lParam);
    }
}

LRESULT CALLBACK Window::HandleMsgThunk(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
{
    // retrieve ptr to window class
    Window* const pWnd = reinterpret_cast<Window*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
    // forward message to window class handler
    return pWnd->HandleMsg(hWnd, msg, wParam, lParam);
}

LRESULT Window::HandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
{
    // for imgui
    extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam);

    POINTS pt;
    switch (msg)
    {
    // we don't want the DefProc to handle this message because
    // we want our destructor to destroy the window, so return 0 instead of break
    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
    case WM_MOUSEMOVE:
        pt = MAKEPOINTS(lParam);
        mouse.OnMouseMove(pt.x, pt.y);
        break;
    case WM_LBUTTONDOWN:
        pt = MAKEPOINTS(lParam);
        mouse.OnLeftClick(pt.x, pt.y);
        break;
    case WM_LBUTTONUP:
        pt = MAKEPOINTS(lParam);
        mouse.OnleftRelease(pt.x, pt.y);
        break;
    case WM_RBUTTONDOWN:
        pt = MAKEPOINTS(lParam);
        mouse.OnRightClick(pt.x, pt.y);
        break;
    case WM_RBUTTONUP:
        pt = MAKEPOINTS(lParam);
        mouse.OnRightRelease(pt.x, pt.y);
        break;
    case WM_MOUSEWHEEL:
        pt = MAKEPOINTS(lParam);
        int mouseDelta = GET_WHEEL_DELTA_WPARAM(wParam);
        mouse.OnWheelDelta(pt.x, pt.y, mouseDelta);
        break;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

Graphics& Window::GetGfx()
{
    return *pGfx;
}

void Window::InitScenes(Logger &logger)
{
    //Ground ground(*pGfx, logger);
    pStage = make_shared<Stage>(*pGfx, logger);
    pScenes.push_back(pStage);
}

void Window::DrawScenes(
    Graphics& gfx,
    Logger& logger) noexcept
{
    for (auto& pScene : pScenes)
    {
        pScene->DrawSections(gfx, logger);
    }
};

void Window::SetTitle(HWND hWnd, const std::string& title)
{
    SetWindowText(hWnd, title.c_str());
}