#include "Win32Window.h"

#include "Win32WindowInterface.h"
#include "DirectXInterface.h"
#include "Win32DxWindowInterface.h"

#include "FileLogger.h"
#include "UnicodeUtils.h"
#include "Widget.h"
#include "DrawingContext.h"
#include "DirectXPainter.h"

#include "KeyboardEvent.h"
#include "DesktopManager.h"

#include <WinUser.h>
#include <windowsx.h>

LRESULT CALLBACK FreeWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

Win32Window::Win32Window(mt::Window* window, DirectXInterface* dxInterface)
    : IPlatformWindow(window)
{
    if (dxInterface)
    {
        mDxInterface = std::make_unique<Win32DxWindowInterface>(window, dxInterface);
        dynamic_cast<DirectXPainter*>(window->getDrawingContent()->getPainter())->setDxInterface(dxInterface);
    }
}

std::unique_ptr<Win32Window> Win32Window::Create(mt::Window* window, DirectXInterface* dxInterface)
{
    return std::make_unique<Win32Window>(window, dxInterface);
}

HWND Win32Window::getHandle() const
{
    return mHandle;
}

void Win32Window::createNative(Win32ApplicationContext* context, DesktopManager* desktopManager)
{
    mDesktopManager = desktopManager;

    const char CLASS_NAME[] = "Sample Window Class";

    auto hInstance = reinterpret_cast<HINSTANCE>(context->hInstance);
    WNDCLASS wc = { };
    wc.lpfnWndProc = FreeWindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    MLOG_INFO("Request window create");
    mHandle = CreateWindowEx(
        0,                              // Optional window styles.
        CLASS_NAME,                     // Window class
        "Notes TK",    // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

        nullptr,       // Parent window    
        nullptr,       // Menu
        hInstance,  // Instance handle
        this        // Additional application data
    );

    if (mHandle == nullptr)
    {
        LPVOID lpMsgBuf;
        auto dw = ::GetLastError();

        ::FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            nullptr, dw,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);

        MLOG_INFO("Request window failed: " << dw);

        LocalFree(lpMsgBuf);
    }
    MLOG_INFO("Request window create got handle: " << "0x" << mHandle);
}

void Win32Window::onPaintMessage()
{
    mDesktopManager->onUiEvent(PaintEvent::Create(mDesktopManager->getThemeManager(), mDesktopManager->getFontsManager()));

    mWindow->doPaint(nullptr);
}

void Win32Window::onMouseDownMessage(int x, int y)
{
    auto event = MouseEvent::Create();
    event->setClientLocation({ static_cast<unsigned>(x), static_cast<unsigned>(y) });
    event->setAction(MouseEvent::Action::Pressed);
    mDesktopManager->onUiEvent(std::move(event));
    MLOG_INFO("Mouse down at: " << x << " , " << y);
}

void Win32Window::onMouseUpMessage(int x, int y)
{
    auto event = MouseEvent::Create();
    event->setClientLocation({ static_cast<unsigned>(x), static_cast<unsigned>(y) });
    event->setAction(MouseEvent::Action::Released);
    mDesktopManager->onUiEvent(std::move(event));
    MLOG_INFO("Mouse up at: " << x << " , " << y);
}

LRESULT CALLBACK Win32Window::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
        case WM_CHAR:
        {
            auto key_event = std::make_unique<KeyboardEvent>();
            key_event->setAction(KeyboardEvent::Action::Pressed);

            auto keyChar = (wchar_t)wParam;
            key_event->setKeyString(UnicodeUtils::utf16ToUtf8String(std::wstring(1, keyChar)));
            mDesktopManager->onUiEvent(std::move(key_event));
            return 0;
        }
        case WM_PAINT:
        {
            onPaintMessage();
            return 0;
        }
        case WM_LBUTTONDOWN :
        {
            const auto x = GET_X_LPARAM(lParam);
            const auto y = GET_Y_LPARAM(lParam);
            onMouseDownMessage(x, y);
            return 0;
        }
        case WM_LBUTTONUP:
        {
            const auto x = GET_X_LPARAM(lParam);
            const auto y = GET_Y_LPARAM(lParam);
            onMouseUpMessage(x, y);
            return 0;
        }
        case WM_SIZE:
        {
            auto width = LOWORD(lParam);
            auto height = HIWORD(lParam);
            MLOG_INFO("WM_SIZE: " << width << " | " <<  height);

            mWindow->setSize(width, height, false);
            if (wParam == SIZE_MINIMIZED)
            {
                mWindow->setDisplayState(WindowState::Display::MINIMIZED);
            }
            else if (wParam == SIZE_MAXIMIZED)
            {
                mWindow->setDisplayState(WindowState::Display::MAXIMIZED);
                mWindow->onResize();
            }
            else if (wParam == SIZE_RESTORED)
            {
                if (mWindow->getDisplayState() == WindowState::Display::MINIMIZED)
                {
                    mWindow->setDisplayState(WindowState::Display::NORMAL);
                    mWindow->onResize();
                }
                else if (mWindow->getDisplayState() == WindowState::Display::MAXIMIZED)
                {
                    mWindow->setDisplayState(WindowState::Display::NORMAL);
                    mWindow->onResize();
                }
                else if (!mWindow->getIsSizingOnGoing())
                {
                    mWindow->onResize();
                }
            }
            return 0;
        }
        case WM_ENTERSIZEMOVE:
        {
            MLOG_INFO("WM_ENTERSIZEMOVE");
            mWindow->setIsSizingOnGoing(true);
            return 0;
        }
        case WM_EXITSIZEMOVE:
        {
            MLOG_INFO("WM_EXITSIZEMOVE");
            mWindow->setIsSizingOnGoing(false);
            mWindow->onResize();
            return 0;
        }
    }
    return DefWindowProc(mHandle, message, wParam, lParam);
}

static LRESULT CALLBACK FreeWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    Win32Window* window;

    if (uMsg == WM_NCCREATE)
    {
        CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
        window = (Win32Window*)cs->lpCreateParams;

        SetLastError(0);
        if (::SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)window) == 0)
        {
            if (GetLastError() != 0)
            {
                return FALSE;
            }   
        }
    }
    else
    {
        window = (Win32Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    }

    if (window)
    {
        window->setHandle(hwnd);
        return window->WindowProc(uMsg, wParam, lParam);
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void Win32Window::beforePaint(mt::Screen* screen)
{
    if (mDxInterface)
    {
        mDxInterface->initialize();
    }
}

void Win32Window::afterPaint(mt::Screen* screen)
{
    if (mDxInterface)
    {
        mDxInterface->onRender();
    }
    else
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(mHandle, &ps);

        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));

        auto text = L"Hello World";
        auto val = DrawText(hdc, (LPCSTR)(&text[0]), -1, &ps.rcPaint, 0);

        EndPaint(mHandle, &ps);
    }
}

void Win32Window::onResize(unsigned width, unsigned height)
{
    if (mDxInterface)
    {
        mDxInterface->onResize();
    }
}