#include "pch.h"

#include "platform_windows/windows_window_shell.h"

#include "app.h"
#include "platform_windows/windows_lifecycle_state.h"
#include "platform_windows/windows_lifecycle_shell.h"
#include "platform_windows/windows_main_window_session.h"
#include "platform_windows/windows_platform_services.h"
#include "platform_windows/windows_runtime_shell.h"
#include "platform_windows/windows_stylus_input.h"
#include "keymap.h"
#include "wacom.h"

void destroy_window();

namespace pp::platform::windows {

[[nodiscard]] std::map<kKey, int>& retained_virtual_key_map();

namespace {

[[nodiscard]] WacomTablet& active_wacom_tablet()
{
    auto* tablet = bound_wacom_tablet();
    assert(tablet);
    return *tablet;
}

[[nodiscard]] kEventSource resolve_pointer_source(const WacomTablet& tablet, LPARAM extra)
{
    if (tablet.m_ink_pen || tablet.m_ink_touch)
        return tablet.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch;

    auto pointer_source = tablet.m_stylus ? kEventSource::Stylus : kEventSource::Mouse;
    if ((extra & 0xFFFFFF00) == 0xFF515700)
        pointer_source = kEventSource::Touch;
    return pointer_source;
}

void synchronize_app_key_state_from_keyboard(App& app)
{
    BYTE keys[256];
    if (!GetKeyboardState(keys))
        return;

    const bool alt = (keys[VK_MENU] & 0x80) != 0;
    for (const auto& [key, vk] : retained_virtual_key_map())
    {
        if (alt && key == kKey::KeyTab)
            continue;

        const bool down = (keys[vk] & 0x80) != 0;
        if (app.keys[(int)key] && !down)
            app.key_up(key);
        else if (!app.keys[(int)key] && down)
            app.key_down(key);
    }
}

}

LRESULT CALLBACK main_window_proc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    auto* app = bound_app();

    auto extra = GetMessageExtraInfo();

    switch (msg)
    {
    case kUserCloseMessage:
        handle_window_close_message(pp::platform::windows::platform_vr_state());
        return 0;
    case WM_PAINT:
        app->redraw = true;
        break;
    case WM_CREATE:
        BT_SetTerminate();
        break;
    case WM_CLOSE:
    {
        app->ui_task_async([app] {
            if (app->request_close())
            {
                destroy_window();
            }
        });
        return 1;
        break;
    }
    case WM_SIZE:
    {
        auto w = (float)LOWORD(lp);
        auto h = (float)HIWORD(lp);
        if (h != 0 && lifecycle_is_running())
        {
            app->ui_task_async([app, w, h]
            {
                app->resize(w, h);
                app->redraw = true;
            }, true);
        }
        break;
    }
    case WM_ACTIVATE:
    {
        platform_services().set_cursor_visible(true);
        app->ui_task_async([app, wp, lp] {
            int active = GET_WM_ACTIVATE_STATE(wp, lp);
            active_wacom_tablet().set_focus(active);
            synchronize_app_key_state_from_keyboard(*app);
        });
        break;
    }
    case WT_PACKET:
    {
        note_wintab_packet();
        app->ui_task_async([=] {
            active_wacom_tablet().handle_message(hWnd, msg, wp, lp);
        });
        break;
    }
    case WM_SYSKEYDOWN:
    case WM_KEYDOWN:
        if ((lp >> 30 & 1) == 0 &&
            !(wp == VK_TAB && app->keys[(int)kKey::KeyAlt]))
        {
            app->ui_task_async([app, wp] {
                app->key_down(convert_key((int)wp));
            });
        }
        break;
    case WM_SYSKEYUP:
    case WM_KEYUP:
        if (!(wp == VK_TAB && app->keys[(int)kKey::KeyAlt]))
        {
            app->ui_task_async([app, wp] {
                app->key_up(convert_key((int)wp));
            });
        }
        break;
    case WM_CHAR:
        app->ui_task_async([app, wp] {
            app->key_char((int)wp);
        });
        break;
    case WM_MOUSEMOVE:
    {
        POINT pt{ GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };
        auto& tablet = active_wacom_tablet();
        app->ui_task_async([app, pt, extra, p = tablet.get_pressure()] {
            auto& ui_tablet = active_wacom_tablet();
            app->mouse_move((float)pt.x, (float)pt.y, p,
                resolve_pointer_source(ui_tablet, extra), ui_tablet.m_eraser);
        });
        break;
    }
    case WM_LBUTTONDOWN:
    {
        SetCapture(hWnd);
        POINT pt{ GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };
        auto& tablet = active_wacom_tablet();
        app->ui_task_async([app, pt, extra, p = tablet.get_pressure()] {
            auto& ui_tablet = active_wacom_tablet();
            app->mouse_down(0, (float)pt.x, (float)pt.y, p,
                resolve_pointer_source(ui_tablet, extra), ui_tablet.m_eraser);
        });
    }
    break;
    case WM_LBUTTONUP:
    {
        ReleaseCapture();
        POINT pt{ GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };
        app->ui_task_async([app, pt, extra] {
            auto& tablet = active_wacom_tablet();
            tablet.reset_pressure();
            app->mouse_up(0, (float)pt.x, (float)pt.y,
                resolve_pointer_source(tablet, extra), tablet.m_eraser);
        });
    }
    break;
    case WM_RBUTTONDOWN:
    {
        SetCapture(hWnd);
        POINT pt{ GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };
        app->ui_task_async([app, pt, extra] {
            const auto& tablet = active_wacom_tablet();
            app->mouse_down(1, (float)pt.x, (float)pt.y, 1.f, resolve_pointer_source(tablet, extra), 0);
        });
    }
    break;
    case WM_RBUTTONUP:
    {
        ReleaseCapture();
        POINT pt{ GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };
        app->ui_task_async([app, pt, extra] {
            const auto& tablet = active_wacom_tablet();
            app->mouse_up(1, (float)pt.x, (float)pt.y, resolve_pointer_source(tablet, extra), 0);
        });
    }
    break;
    case WM_MOUSEWHEEL:
    {
        POINT pt;
        pt.x = GET_X_LPARAM(lp);
        pt.y = GET_Y_LPARAM(lp);
        ScreenToClient(hWnd, &pt);
        app->ui_task_async([app, pt, wp] {
            app->mouse_scroll((float)pt.x, (float)pt.y,
                (float)GET_WHEEL_DELTA_WPARAM(wp) / (float)WHEEL_DELTA);
        });
        break;
    }
    case WM_POINTERUPDATE:
    {
        handle_pointer_update_message(wp);
        break;
    }
    }

    if (wp == SC_KEYMENU && (lp >> 16) <= 0)
        return 0;

    return DefWindowProc(hWnd, msg, wp, lp);
}

}
