// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IASoft (PVT) LTD (oss@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#include <IAEngine/Engine.hpp>
#include <IAEngine/UI.hpp>

#include <Renderer/Renderer.hpp>

#include <SDL3/SDL.h>

#include <RmlUi/Core.h>
#include <RmlUi/Debugger.h>

namespace ia::iae
{
    Rml::Context *g_context{};
    BOOL g_debuggerEnabled{false};
    Rml::ElementDocument *g_document{};

    class RmlUIRenderInterface : public Rml::RenderInterface
    {
      public:
        Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices,
                                                    Rml::Span<const int> indices);
        void RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation,
                            Rml::TextureHandle texture);
        void ReleaseGeometry(Rml::CompiledGeometryHandle geometry);
        Rml::TextureHandle LoadTexture(Rml::Vector2i &texture_dimensions, const Rml::String &source);
        Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions);
        void ReleaseTexture(Rml::TextureHandle texture);
        void EnableScissorRegion(bool enable);
        void SetScissorRegion(Rml::Rectanglei region);
    } g_rmlUIRenderInterface{};

    class RmlEventListener : public Rml::EventListener
    {
      public:
        RmlEventListener()
        {
        }

        VOID AddClickListener(IN Rml::Element *element, IN std::function<VOID()> callback)
        {
            element->AddEventListener("click", this);
            m_clickCallbacks[element->GetId().c_str()] = callback;
        }

        // VOID AddHoverEnterListener(IN PCCHAR elementId, IN std::function<VOID()> callback)
        //{
        //     m_document->GetElementById(elementId)->AddEventListener("mouseover", this);
        //     m_hoverEnterCallbacks[elementId] = callback;
        // }
        //
        // VOID AddHoverExitListener(IN PCCHAR elementId, IN std::function<VOID()> callback)
        //{
        //    m_document->GetElementById(elementId)->AddEventListener("mouseout", this);
        //    m_hoverExitCallbacks[elementId] = callback;
        //}
        //
        // VOID AddPointerDownListener(IN PCCHAR elementId, IN std::function<VOID()> callback)
        //{
        //    m_document->GetElementById(elementId)->AddEventListener("mousedown", this);
        //    m_pointerDownCallbacks[elementId] = callback;
        //}
        //
        // VOID AddPointerUpListener(IN PCCHAR elementId, IN std::function<VOID()> callback)
        //{
        //    m_document->GetElementById(elementId)->AddEventListener("mouseup", this);
        //    m_pointerUpCallbacks[elementId] = callback;
        //}

        VOID ProcessEvent(IN Rml::Event &event)
        {
            switch (event.GetId())
            {
            case Rml::EventId::Click:
                m_clickCallbacks[event.GetTargetElement()->GetId().c_str()]();
                break;

            case Rml::EventId::Mouseover:
                m_hoverEnterCallbacks[event.GetTargetElement()->GetId().c_str()]();
                break;

            case Rml::EventId::Mouseout:
                m_hoverExitCallbacks[event.GetTargetElement()->GetId().c_str()]();
                break;

            case Rml::EventId::Mousedown:
                m_pointerDownCallbacks[event.GetTargetElement()->GetId().c_str()]();
                break;

            case Rml::EventId::Mouseup:
                m_pointerUpCallbacks[event.GetTargetElement()->GetId().c_str()]();
                break;

            default:
                break;
            }
        }

      private:
        Map<String, std::function<VOID()>> m_clickCallbacks;
        Map<String, std::function<VOID()>> m_hoverEnterCallbacks;
        Map<String, std::function<VOID()>> m_hoverExitCallbacks;
        Map<String, std::function<VOID()>> m_pointerDownCallbacks;
        Map<String, std::function<VOID()>> m_pointerUpCallbacks;
    } g_eventListener{};

    /* Taken from https://github.com/mikke89/RmlUi/blob/master/Backends/RmlUi_Platform_SDL.cpp */
    INT32 GetKeyModifierState()
    {
        SDL_Keymod sdl_mods = SDL_GetModState();
        INT32 keymods = 0;
        if (sdl_mods & SDL_KMOD_CTRL)
            keymods |= Rml::Input::KM_CTRL;
        if (sdl_mods & SDL_KMOD_SHIFT)
            keymods |= Rml::Input::KM_SHIFT;
        if (sdl_mods & SDL_KMOD_ALT)
            keymods |= Rml::Input::KM_ALT;
        if (sdl_mods & SDL_KMOD_NUM)
            keymods |= Rml::Input::KM_NUMLOCK;
        if (sdl_mods & SDL_KMOD_CAPS)
            keymods |= Rml::Input::KM_CAPSLOCK;
        return keymods;
    }

    /* Taken from https://github.com/mikke89/RmlUi/blob/master/Backends/RmlUi_Platform_SDL.cpp */
    INT32 SDLMouseButtonToRml(IN INT32 button)
    {
        switch (button)
        {
        case SDL_BUTTON_LEFT:
            return 0;
        case SDL_BUTTON_RIGHT:
            return 1;
        case SDL_BUTTON_MIDDLE:
            return 2;
        default:
            break;
        }
        return 3;
    }

    /* Taken from https://github.com/mikke89/RmlUi/blob/master/Backends/RmlUi_Platform_SDL.cpp */
    Rml::Input::KeyIdentifier SDLKeyToRml(int sdlkey)
    {
        switch (sdlkey)
        {
        case SDLK_UNKNOWN:
            return Rml::Input::KI_UNKNOWN;
        case SDLK_ESCAPE:
            return Rml::Input::KI_ESCAPE;
        case SDLK_SPACE:
            return Rml::Input::KI_SPACE;
        case SDLK_0:
            return Rml::Input::KI_0;
        case SDLK_1:
            return Rml::Input::KI_1;
        case SDLK_2:
            return Rml::Input::KI_2;
        case SDLK_3:
            return Rml::Input::KI_3;
        case SDLK_4:
            return Rml::Input::KI_4;
        case SDLK_5:
            return Rml::Input::KI_5;
        case SDLK_6:
            return Rml::Input::KI_6;
        case SDLK_7:
            return Rml::Input::KI_7;
        case SDLK_8:
            return Rml::Input::KI_8;
        case SDLK_9:
            return Rml::Input::KI_9;
        case SDLK_A:
            return Rml::Input::KI_A;
        case SDLK_B:
            return Rml::Input::KI_B;
        case SDLK_C:
            return Rml::Input::KI_C;
        case SDLK_D:
            return Rml::Input::KI_D;
        case SDLK_E:
            return Rml::Input::KI_E;
        case SDLK_F:
            return Rml::Input::KI_F;
        case SDLK_G:
            return Rml::Input::KI_G;
        case SDLK_H:
            return Rml::Input::KI_H;
        case SDLK_I:
            return Rml::Input::KI_I;
        case SDLK_J:
            return Rml::Input::KI_J;
        case SDLK_K:
            return Rml::Input::KI_K;
        case SDLK_L:
            return Rml::Input::KI_L;
        case SDLK_M:
            return Rml::Input::KI_M;
        case SDLK_N:
            return Rml::Input::KI_N;
        case SDLK_O:
            return Rml::Input::KI_O;
        case SDLK_P:
            return Rml::Input::KI_P;
        case SDLK_Q:
            return Rml::Input::KI_Q;
        case SDLK_R:
            return Rml::Input::KI_R;
        case SDLK_S:
            return Rml::Input::KI_S;
        case SDLK_T:
            return Rml::Input::KI_T;
        case SDLK_U:
            return Rml::Input::KI_U;
        case SDLK_V:
            return Rml::Input::KI_V;
        case SDLK_W:
            return Rml::Input::KI_W;
        case SDLK_X:
            return Rml::Input::KI_X;
        case SDLK_Y:
            return Rml::Input::KI_Y;
        case SDLK_Z:
            return Rml::Input::KI_Z;
        case SDLK_SEMICOLON:
            return Rml::Input::KI_OEM_1;
        case SDLK_PLUS:
            return Rml::Input::KI_OEM_PLUS;
        case SDLK_COMMA:
            return Rml::Input::KI_OEM_COMMA;
        case SDLK_MINUS:
            return Rml::Input::KI_OEM_MINUS;
        case SDLK_PERIOD:
            return Rml::Input::KI_OEM_PERIOD;
        case SDLK_SLASH:
            return Rml::Input::KI_OEM_2;
        case SDLK_GRAVE:
            return Rml::Input::KI_OEM_3;
        case SDLK_LEFTBRACKET:
            return Rml::Input::KI_OEM_4;
        case SDLK_BACKSLASH:
            return Rml::Input::KI_OEM_5;
        case SDLK_RIGHTBRACKET:
            return Rml::Input::KI_OEM_6;
        case SDLK_DBLAPOSTROPHE:
            return Rml::Input::KI_OEM_7;
        case SDLK_KP_0:
            return Rml::Input::KI_NUMPAD0;
        case SDLK_KP_1:
            return Rml::Input::KI_NUMPAD1;
        case SDLK_KP_2:
            return Rml::Input::KI_NUMPAD2;
        case SDLK_KP_3:
            return Rml::Input::KI_NUMPAD3;
        case SDLK_KP_4:
            return Rml::Input::KI_NUMPAD4;
        case SDLK_KP_5:
            return Rml::Input::KI_NUMPAD5;
        case SDLK_KP_6:
            return Rml::Input::KI_NUMPAD6;
        case SDLK_KP_7:
            return Rml::Input::KI_NUMPAD7;
        case SDLK_KP_8:
            return Rml::Input::KI_NUMPAD8;
        case SDLK_KP_9:
            return Rml::Input::KI_NUMPAD9;
        case SDLK_KP_ENTER:
            return Rml::Input::KI_NUMPADENTER;
        case SDLK_KP_MULTIPLY:
            return Rml::Input::KI_MULTIPLY;
        case SDLK_KP_PLUS:
            return Rml::Input::KI_ADD;
        case SDLK_KP_MINUS:
            return Rml::Input::KI_SUBTRACT;
        case SDLK_KP_PERIOD:
            return Rml::Input::KI_DECIMAL;
        case SDLK_KP_DIVIDE:
            return Rml::Input::KI_DIVIDE;
        case SDLK_KP_EQUALS:
            return Rml::Input::KI_OEM_NEC_EQUAL;
        case SDLK_BACKSPACE:
            return Rml::Input::KI_BACK;
        case SDLK_TAB:
            return Rml::Input::KI_TAB;
        case SDLK_CLEAR:
            return Rml::Input::KI_CLEAR;
        case SDLK_RETURN:
            return Rml::Input::KI_RETURN;
        case SDLK_PAUSE:
            return Rml::Input::KI_PAUSE;
        case SDLK_CAPSLOCK:
            return Rml::Input::KI_CAPITAL;
        case SDLK_PAGEUP:
            return Rml::Input::KI_PRIOR;
        case SDLK_PAGEDOWN:
            return Rml::Input::KI_NEXT;
        case SDLK_END:
            return Rml::Input::KI_END;
        case SDLK_HOME:
            return Rml::Input::KI_HOME;
        case SDLK_LEFT:
            return Rml::Input::KI_LEFT;
        case SDLK_UP:
            return Rml::Input::KI_UP;
        case SDLK_RIGHT:
            return Rml::Input::KI_RIGHT;
        case SDLK_DOWN:
            return Rml::Input::KI_DOWN;
        case SDLK_INSERT:
            return Rml::Input::KI_INSERT;
        case SDLK_DELETE:
            return Rml::Input::KI_DELETE;
        case SDLK_HELP:
            return Rml::Input::KI_HELP;
        case SDLK_F1:
            return Rml::Input::KI_F1;
        case SDLK_F2:
            return Rml::Input::KI_F2;
        case SDLK_F3:
            return Rml::Input::KI_F3;
        case SDLK_F4:
            return Rml::Input::KI_F4;
        case SDLK_F5:
            return Rml::Input::KI_F5;
        case SDLK_F6:
            return Rml::Input::KI_F6;
        case SDLK_F7:
            return Rml::Input::KI_F7;
        case SDLK_F8:
            return Rml::Input::KI_F8;
        case SDLK_F9:
            return Rml::Input::KI_F9;
        case SDLK_F10:
            return Rml::Input::KI_F10;
        case SDLK_F11:
            return Rml::Input::KI_F11;
        case SDLK_F12:
            return Rml::Input::KI_F12;
        case SDLK_F13:
            return Rml::Input::KI_F13;
        case SDLK_F14:
            return Rml::Input::KI_F14;
        case SDLK_F15:
            return Rml::Input::KI_F15;
        case SDLK_NUMLOCKCLEAR:
            return Rml::Input::KI_NUMLOCK;
        case SDLK_SCROLLLOCK:
            return Rml::Input::KI_SCROLL;
        case SDLK_LSHIFT:
            return Rml::Input::KI_LSHIFT;
        case SDLK_RSHIFT:
            return Rml::Input::KI_RSHIFT;
        case SDLK_LCTRL:
            return Rml::Input::KI_LCONTROL;
        case SDLK_RCTRL:
            return Rml::Input::KI_RCONTROL;
        case SDLK_LALT:
            return Rml::Input::KI_LMENU;
        case SDLK_RALT:
            return Rml::Input::KI_RMENU;
        case SDLK_LGUI:
            return Rml::Input::KI_LMETA;
        case SDLK_RGUI:
            return Rml::Input::KI_RMETA;
        default:
            break;
        }
        return Rml::Input::KI_UNKNOWN;
    }
} // namespace ia::iae

namespace ia::iae
{
    VOID UI::Initialize()
    {
        Rml::SetRenderInterface(&g_rmlUIRenderInterface);
        Rml::Initialise();

        const auto displayExtent = Engine::GetDisplayExtent();
        g_context = Rml::CreateContext("main", Rml::Vector2i(displayExtent.x, displayExtent.y));
        Rml::Debugger::Initialise(g_context);
        Rml::Debugger::SetVisible(false);
        g_document = g_context->CreateDocument();
    }

    VOID UI::Terminate()
    {
        Rml::Shutdown();
    }

    VOID UI::Update()
    {
        if (Engine::WasInputKeyPressed(InputKey::F8))
            Rml::Debugger::SetVisible(g_debuggerEnabled = !g_debuggerEnabled);

        g_context->Update();
    }

    VOID UI::Draw()
    {
        g_context->Render();
    }

    VOID UI::OnSDLEvent(IN PVOID _event)
    {
        const auto keymods = GetKeyModifierState();
        const auto event = (SDL_Event *) _event;
        switch (event->type)
        {
        case SDL_EventType::SDL_EVENT_MOUSE_MOTION:
            g_context->ProcessMouseMove(event->motion.x, event->motion.y, keymods);
            break;
        case SDL_EventType::SDL_EVENT_MOUSE_BUTTON_UP:
            g_context->ProcessMouseButtonUp(SDLMouseButtonToRml(event->button.button), keymods);
            break;
        case SDL_EventType::SDL_EVENT_MOUSE_BUTTON_DOWN:
            g_context->ProcessMouseButtonDown(SDLMouseButtonToRml(event->button.button), keymods);
            break;
        case SDL_EventType::SDL_EVENT_KEY_DOWN:
            g_context->ProcessKeyDown(SDLKeyToRml(event->key.key), keymods);
            break;
        case SDL_EventType::SDL_EVENT_KEY_UP:
            g_context->ProcessKeyUp(SDLKeyToRml(event->key.key), keymods);
            break;
        case SDL_EventType::SDL_EVENT_TEXT_INPUT:
            g_context->ProcessTextInput(Rml::String(&event->text.text[0]));
            break;
        }
    }

    VOID UI::OnScreenResize(IN INT32 newWidth, IN INT32 newHeight)
    {
    }

    VOID UI::AddFontFromFile(IN CONST String &path)
    {
        Rml::LoadFontFace(path.c_str());
    }

    VOID UI::SetHTML(IN CONST String &source)
    {
        g_document->SetInnerRML(
            BuildString("<body style=\"display: block; width: 100%; height: 100%;\">", source, "</body>").c_str());
    }

    VOID UI::AddClickEvent(IN PCCHAR elementId, IN std::function<VOID()> callback)
    {
        g_eventListener.AddClickListener(g_document->GetElementById(elementId), callback);
    }
} // namespace ia::iae

namespace ia::iae
{
    Rml::CompiledGeometryHandle RmlUIRenderInterface::CompileGeometry(Rml::Span<const Rml::Vertex> vertices,
                                                                      Rml::Span<const int> indices)
    {
        Vector<GeometryVertex> _vertices;
        Vector<INT32> _indices;
        for (const auto &v : vertices)
            _vertices.pushBack({Vec2{v.position.x, v.position.y}, Vec2{v.tex_coord.x, v.tex_coord.y},
                                Vec4{v.colour.red / 255.0f, v.colour.green / 255.0f, v.colour.blue / 255.0f,
                                     v.colour.alpha / 255.0f}});
        for (const auto &v : indices)
            _indices.pushBack(v);
        return (Rml::CompiledGeometryHandle) Engine::CreateGeometry(_vertices, _indices);
    }

    void RmlUIRenderInterface::RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation,
                                              Rml::TextureHandle texture)
    {

        Engine::SetRenderState_Texture((Handle) texture);
        Engine::SetRenderState_FlippedH(false);
        Engine::SetRenderState_FlippedV(false);
        Engine::SetRenderState_ColorOverlay({255, 255, 255, 255});
        Engine::SetRenderState_TextureOffset({0, 0});
        Engine::SetRenderState_CameraRelative(false);
        Engine::SetRenderState_Transform({translation.x, translation.y}, {1.0f, 1.0f}, 0, 0xFF, 0);

        Engine::DrawGeometry((Handle) geometry);
    }

    void RmlUIRenderInterface::ReleaseGeometry(Rml::CompiledGeometryHandle geometry)
    {
        Engine::DestroyGeometry((Handle) geometry);
    }

    Rml::TextureHandle RmlUIRenderInterface::LoadTexture(Rml::Vector2i &texture_dimensions, const Rml::String &source)
    {
        return Engine::CreateImageFromFile(Engine::GetUniqueResourceName(), source.c_str(), texture_dimensions.x,
                                           texture_dimensions.y);
    }

    Rml::TextureHandle RmlUIRenderInterface::GenerateTexture(Rml::Span<const Rml::byte> source,
                                                             Rml::Vector2i source_dimensions)
    {
        return Engine::CreateImage(Engine::GetUniqueResourceName(), (PCUINT8) source.data(), source_dimensions.x,
                                   source_dimensions.y);
    }

    void RmlUIRenderInterface::ReleaseTexture(Rml::TextureHandle texture)
    {
        Engine::DestroyImage((Handle) texture);
    }

    void RmlUIRenderInterface::EnableScissorRegion(bool enable)
    {
        if (!enable)
            Engine::SetRenderState_Scissor({});
    }

    void RmlUIRenderInterface::SetScissorRegion(Rml::Rectanglei region)
    {
        Engine::SetRenderState_Scissor({region.Left(), region.Top(), region.Right(), region.Bottom()});
    }
} // namespace ia::iae