// 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/Scene.hpp>

#include <IAEngine/pugixml/pugixml.hpp>

namespace ia::iae
{
     // -----------------------------------------------
    // getInnerXML and xml_string_writer were taken from https://stackoverflow.com/a/60337372
    struct xml_string_writer : pugi::xml_writer
    {
        std::string result;

        virtual void write(const void *data, size_t size)
        {
            result.append(static_cast<const char *>(data), size);
        }
    };

    const auto getInnerXML = [](pugi::xml_node target) {
        xml_string_writer writer;
        for (pugi::xml_node child = target.first_child(); child; child = child.next_sibling())
            child.print(writer, "");
        return writer.result;
    };

    // -----------------------------------------------

    RefPtr<Scene> Scene::Create()
    {
        const auto scene = MakeRefPtr<Scene>();
        scene->Extent() = Engine::GetDisplayExtent();
        const auto cameraNode = MakeRefPtr<CameraNode>("MainCamera");
        scene->AddNode(cameraNode);
        scene->m_camera = cameraNode->GetCameraComponent();
        return scene;
    }

    RefPtr<Scene> Scene::Create(IN CONST String &sceneXML,
                                IN std::function<RefPtr<Node2D>(IN CONST String &, IN Handle)> getCustomNode,
                                IN std::function<Handle(IN ResourceType type, IN CONST String &, IN INT64)> getResource)
    {
        const auto scene = Create();

        pugi::xml_document doc;
        doc.load_string(sceneXML.c_str());
        const auto sceneRoot = doc.child("Scene");

        Map<String, Handle> resources;

        // Process Resources
        const auto resRoot = sceneRoot.child("Resources");
        if (!resRoot)
            THROW_INVALID_DATA("Scene file is missing 'Resources' tag");
        {
            for (const auto &t : resRoot.children())
            {
                if (!strcmp(t.name(), "Image"))
                {
                    resources[t.attribute("name").as_string()] = getResource(
                        ResourceType::IMAGE, t.attribute("path").as_string(), t.attribute("index").as_llong());
                }
                else if (!strcmp(t.name(), "Audio"))
                {
                    resources[t.attribute("name").as_string()] =
                        getResource(ResourceType::SOUND, t.attribute("path").as_string(), 0);
                }
            }
        }

        // Process Properties
        const auto propRoot = sceneRoot.child("Properties");
        if (!propRoot)
            THROW_INVALID_DATA("Scene file is missing 'Properties' tag");
        {
            auto t = propRoot.child("Extent");
            scene->Extent() = Vec2{t.attribute("width").as_float(), t.attribute("height").as_float()};

            t = propRoot.child("Viewport");
            scene->Viewport() = Vec2{t.attribute("width").as_float(), t.attribute("height").as_float()};
        }

        // Process Nodes
        const auto nodeRoot = sceneRoot.child("Nodes");
        if (!nodeRoot)
            THROW_INVALID_DATA("Scene file is missing 'Nodes' tag");
        {
            for (const auto &t : nodeRoot)
            {
                Node2D* n{}; 
                if (!strcmp(t.name(), "TextureNode"))
                {
                    const auto node = MakeRefPtr<TextureNode>(Engine::GetUniqueResourceName());
                    node->GetTextureComponent()->SetTexture(resources[t.attribute("texture").as_string()]);
                    scene->AddNode(node);
                    n = node.get();
                }
                else
                {
                    Handle id{INVALID_HANDLE};
                    const auto attrID = t.attribute("id");
                    if(attrID)
                        id = (Handle)attrID.as_ullong();
                    const auto node = getCustomNode(t.name(), id);
                    scene->AddNode(node);
                    n = node.get();
                }
                
                if(!n) continue;
                if(t.attribute("X") && t.attribute("Y"))
                {
                    n->SetLocalPosition({
                        t.attribute("X").as_float(),
                        t.attribute("Y").as_float()
                    });   
                }
            }
        }

        // Process UI
        const auto uiRoot = sceneRoot.child("UI");
        if (!uiRoot)
            THROW_INVALID_DATA("Scene file is missing 'UI' tag");
        {
            scene->UIMarkupStyles() = getInnerXML(uiRoot.child("CSS")).c_str();
            auto html = String(getInnerXML(uiRoot.child("HTML")).c_str());
            html =
                Utils::RegexReplaceGroups(html, "<img(.*?)src=\"(.*?)\"", [&](IN INT32 index, IN CONST String &match) {
                    if(index == 1)
                        return BuildString("$H$", resources[match]);
                    return match;
                });
            scene->UIMarkup() = html;
        }

        return scene;
    }

    VOID Scene::Draw()
    {
        for (auto &t : m_nodes)
            t->Value->Draw();
    }

    VOID Scene::DebugDraw()
    {
        for (auto &t : m_nodes)
            t->Value->DebugDraw();
    }

    VOID Scene::FixedUpdate()
    {
        for (auto &t : m_nodes)
            t->Value->FixedUpdate();
    }

    VOID Scene::Update()
    {
        for (auto &t : m_nodes)
            t->Value->Update();
    }

    VOID Scene::AddNode(IN RefPtr<INode> node)
    {
        m_nodes[node->GetName()] = node;
    }

    INode *Scene::GetNode(IN CONST String &name)
    {
        return m_nodes[name].get();
    }

    VOID Scene::RemoveNode(IN CONST String &name)
    {
        m_nodes[name] = nullptr;
    }

    VOID Scene::OnActivate()
    {
        UI::SetMarkup(UIMarkup(), UIMarkupStyles());
        if(m_camera)
            Engine::SetActiveCamera(m_camera);
    }
} // namespace ia::iae
