#include "kernel.h"
#include "egl_context.h"
#include "render_target.h"
#include "logger.h"
#include "assets_manager.h"
#include "aidl/com/omixlab/mosis/IMosisListener.h"
#include <android/hardware_buffer.h>
#include <android/asset_manager.h>
#include <RmlUi/Core.h>
#include "RmlUi_Renderer_GL3.h"
#include <span>
#include <ranges>
#include <chrono>
#include <format>

using namespace aidl::com::omixlab::mosis;
using namespace aidl::android::hardware;

class AssetFilesInterface : public Rml::FileInterface
{
public:
    static AssetFilesInterface& Instance()
    {
        static AssetFilesInterface instance;
        return instance;
    }
    Rml::FileHandle Open(const Rml::String &path) override
    {
        AAssetManager* am = AssetsManager::Native();
        AAsset* asset = AAssetManager_open(am, path.c_str(), AASSET_MODE_BUFFER);
        return reinterpret_cast<Rml::FileHandle>(asset);
    }
    void Close(Rml::FileHandle file) override
    {
        AAsset* asset = reinterpret_cast<AAsset*>(file);
        AAsset_close(asset);
    }
    size_t Read(void *buffer, size_t size, Rml::FileHandle file) override
    {
        AAsset* asset = reinterpret_cast<AAsset*>(file);
        return AAsset_read(asset, buffer, size);
    }
    bool Seek(Rml::FileHandle file, long offset, int origin) override
    {
        AAsset* asset = reinterpret_cast<AAsset*>(file);
        off_t new_cursor = AAsset_seek(asset, offset, origin);
        return new_cursor != -1;
    }
    size_t Tell(Rml::FileHandle file) override
    {
        AAsset* asset = reinterpret_cast<AAsset*>(file);
        return AAsset_seek(asset, 0, SEEK_CUR);
    }

    size_t Length(Rml::FileHandle file) override
    {
        AAsset* asset = reinterpret_cast<AAsset*>(file);
        return AAsset_getLength(asset);
    }

    bool LoadFile(const Rml::String &path, Rml::String &out_data) override
    {
        AAssetManager* am = AssetsManager::Native();
        AAsset* asset = AAssetManager_open(am, path.c_str(), AASSET_MODE_BUFFER);
        if (!asset)
            return false;
        out_data.resize(AAsset_getLength(asset));
        auto data_ptr = static_cast<const char*>(AAsset_getBuffer(asset));
        std::span data = std::span(data_ptr, out_data.size());
        std::ranges::copy(data, out_data.begin());
        return true;
    }
};

class SystemInterface : public Rml::SystemInterface
{
public:
    static SystemInterface& Instance()
    {
        static SystemInterface instance;
        return instance;
    }
    bool LogMessage(Rml::Log::Type type, const Rml::String &message) override
    {
        Logger::Log(std::format("RMLUI: {}", message));
        return true;
    }
};

void Kernel::main_loop()
{
    m_egl_context = std::make_unique<egl::Context>();
    if (!m_egl_context->create())
    {
        Logger::Log("failed to create EGL context");
        return;
    }
    m_render_target = std::make_unique<RenderTarget>();
    if (!m_render_target->create_exported(540, 960))
    {
        Logger::Log("failed to create render target");
        return;
    }
    m_render_target->bind();
    for (const auto& [pid, l] : m_listeners)
        l->onServiceInitialized(true);
    m_aidl_buffer = std::make_unique<HardwareBuffer>();
    m_aidl_buffer->reset(m_render_target->hardware_buffer());
    for (const auto& [pid, l] : m_listeners)
        l->onBufferAvailable(*m_aidl_buffer);

    RenderInterface_GL3 rmlui_render_interface;
    if (!rmlui_render_interface)
    {
        Logger::Log("failed to create render interface");
        return;
    }
    Rml::SetRenderInterface(&rmlui_render_interface);
    Rml::SetFileInterface(&AssetFilesInterface::Instance());
    Rml::SetSystemInterface(&SystemInterface::Instance());
    Rml::Initialise();
    Rml::Context* context = Rml::CreateContext("default", Rml::Vector2i(540, 960));
    if (!context)
    {
        Logger::Log("RMLUI failed to create a context");
        Rml::Shutdown();
        return;
    }
    Rml::LoadFontFace("Roboto/static/Roboto_Condensed-Regular.ttf");
    Rml::LoadFontFace("LatoLatin-Regular.ttf");
    Rml::LoadFontFace("NotoEmoji-Regular.ttf", true);

    std::string status_time = "0";
    Rml::DataModelHandle status_time_handle;
    if (Rml::DataModelConstructor status_data_ctor = context->CreateDataModel("status-data"))
    {
        status_data_ctor.Bind("time", &status_time);
        status_time_handle = status_data_ctor.GetModelHandle();
    }

    // Now we are ready to load our document.
    Rml::ElementDocument* document = context->LoadDocument("demo.rml");
    document->Show();

    while (true)
    {
        if (!m_tasks.empty())
        {
            std::lock_guard _lock(m_mutex);
            for (const auto &task: m_tasks)
                task(context);
            m_tasks.clear();
        }

        m_render_target->bind();
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        glViewport(0, 0, 540, 960);

        status_time = std::format("{:%H:%M:%S}",
            std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now()));
        status_time_handle.DirtyAllVariables();

        context->Update();
        rmlui_render_interface.SetViewport(540, 960);
        rmlui_render_interface.BeginFrame();
        context->Render();
        rmlui_render_interface.EndFrame(m_render_target->framebuffer());

        glFinish();
        {
            std::lock_guard _lock(m_mutex);
            for (const auto& [pid, l] : m_listeners)
                l->onFrameAvailable();
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    Rml::Shutdown();
}

Kernel::Kernel(const std::shared_ptr<IMosisListener> &listener)
{
    m_listeners.emplace(AIBinder_getCallingPid(), listener);
    m_main_loop_thread = std::thread(&Kernel::main_loop, this);
}

void Kernel::add_listener(const std::shared_ptr<IMosisListener> &listener)
{
    std::lock_guard _lock(m_mutex);
    m_listeners.emplace(AIBinder_getCallingPid(), listener);
    listener->onServiceInitialized(true);
    listener->onBufferAvailable(*m_aidl_buffer);
}

void Kernel::on_touch_down(float x, float y)
{
    Logger::Log(std::format("on_touch_down {} - {}", x, y));
}

void Kernel::on_touch_move(float x, float y)
{
    Logger::Log(std::format("on_touch_move {} - {}", x, y));
    std::lock_guard _lock(m_mutex);
    m_tasks.emplace_back([x, y](Rml::Context* context){
        context->ProcessMouseMove(x * 540, y * 960, 0);
    });
}

void Kernel::on_touch_up(float x, float y)
{
    Logger::Log(std::format("on_touch_up {} - {}", x, y));
}

Kernel::~Kernel() = default;
