#include "open_engine/scene/components.hpp"
#include "open_engine/scene/entity.hpp"
#include "editor_component.hpp"

#include <panels/scene_hierarchy.hpp>

#include <cstring>
#include <cstdint>
#include <imgui.h>

namespace OpenEngine {

    using ComponentDrawer = std::function<void(entt::registry&, entt::entity)>;

    SceneHierarchy::SceneHierarchy(const Ref<Scene>& scene)
        : scene(scene)
    {
    }

    void SceneHierarchy::Init(const Ref<Scene>& context)
    {
        RegisterDrawer<TransformComponent>("Transform", &TransformOnImGuiRender);
        RegisterDrawer<SpriteRendererComponent>("Sprite Renderer", &SpriteOnImGuiRender);
        RegisterDrawer<CameraComponent>("Camera", &CameraOnImGuiRender);
        RegisterDrawer<MeshComponent>("Mesh", &MeshOnImGuiRender);
        RegisterDrawer<MaterialComponent>("Material", &MaterialOnImGuiRender);
        RegisterDrawer<PhysicsBodyComponent>("Physics Body", &BodyOnImGuiRender);
        RegisterDrawer<SphereShapeComponent>("Sphere Shape", &SphereShapeOnImGuiRender);
        RegisterDrawer<BoxShapeComponent>("Box Shape", &BoxShapeOnImGuiRender);
        RegisterDrawer<PlaneShapeComponent>("Plane Shape", &PlaneShapeOnImGuiRender);
        RegisterDrawer<ModelComponent>("Model", &ModelOnImGuiRender);

        scene = context;
        selected_context = {};
    }

    void ComponentPopup(Entity& selected_context)
    {
        ImGui::SeparatorText("Add");
        if (!selected_context.HasComponent<TransformComponent>())
            if (ImGui::MenuItem("Transform")) {
                selected_context.AddComponent<TransformComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<SpriteRendererComponent>())
            if (ImGui::MenuItem("Sprite")) {
                selected_context.AddComponent<SpriteRendererComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<CameraComponent>())
            if (ImGui::MenuItem("Camera")) {
                selected_context.AddComponent<CameraComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<MeshComponent>())
            if (ImGui::MenuItem("Mesh")) {
                selected_context.AddComponent<MeshComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<MaterialComponent>())
            if (ImGui::MenuItem("Material")) {
                selected_context.AddComponent<MaterialComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<PhysicsBodyComponent>())
            if (ImGui::MenuItem("Physics body")) {
                selected_context.AddComponent<PhysicsBodyComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<SphereShapeComponent>())
            if (ImGui::MenuItem("Sphere shape")) {
                selected_context.AddComponent<SphereShapeComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<BoxShapeComponent>())
            if (ImGui::MenuItem("Box shape")) {
                selected_context.AddComponent<BoxShapeComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<PlaneShapeComponent>())
            if (ImGui::MenuItem("Plane shape")) {
                selected_context.AddComponent<PlaneShapeComponent>();
                ImGui::CloseCurrentPopup();
            }
        if (!selected_context.HasComponent<ModelComponent>())
            if (ImGui::MenuItem("Model")) {
                selected_context.AddComponent<ModelComponent>();
                ImGui::CloseCurrentPopup();
            }
    }

    void EntityPopup(Ref<Scene>& scene)
    {
        ImGui::SeparatorText("Add");
        if (ImGui::MenuItem("Empty entity")) {
            scene->CreateEntity("Empty entity");
            ImGui::CloseCurrentPopup();
        }
    }

    void SceneHierarchy::OnImGuiRender()
    {
        // Scene hierarchy
        if (ImGui::Begin("Scene")) {

            // Draw all enitity tree names
            for (auto entity_entt : scene->GetRegistry().view<TagComponent>()) {
                Entity entity{ entity_entt, scene.get() };

                DrawEntityNode(entity);
            }

            // On click not on item, deselect
            if ((ImGui::IsMouseDown(0) && ImGui::IsWindowHovered())
                    || (ImGui::IsKeyDown(ImGuiKey_Escape) && ImGui::IsWindowHovered())) {
                selected_context = {};
                renamed_entity = {};
            }

            // On Right click open entity creation window
            if (ImGui::BeginPopupContextWindow(0, ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverItems)) {
                EntityPopup(scene);
                ImGui::EndPopup();
            }

            // Centred fullwidth bottom button to add entity
            float button_height = ImGui::GetFrameHeight(); 
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - button_height);

            if (ImGui::Button("Add Entity", ImVec2(ImGui::GetContentRegionAvail().x, 0.0f)))
                ImGui::OpenPopup("add_entity");

            if (ImGui::BeginPopup("add_entity")) {
                EntityPopup(scene);
                ImGui::EndPopup();
            }

            ImGui::End();
        }

        // Property window
        ImGui::Begin("Properties");

        if (selected_context) {
            DrawComponents(selected_context);

            if (ImGui::BeginPopupContextWindow(0, ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverItems)) {
                ComponentPopup(selected_context);
                ImGui::EndPopup();
            }

            // Centred fullwidth bottom button to add component
            float button_height = ImGui::GetFrameHeight(); 
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - button_height);

            if (ImGui::Button("Add Component", ImVec2(ImGui::GetContentRegionAvail().x, 0.0f)))
                ImGui::OpenPopup("add_component");

            if (ImGui::BeginPopup("add_component")) {
                ComponentPopup(selected_context);
                ImGui::EndPopup();
            }
        }

        ImGui::End();
    }

    void SceneHierarchy::DrawEntityNode(Entity& entity)
    {
        auto& tag = entity.GetComponents<TagComponent>().tag;

        if (renamed_entity == entity && selected_context == entity) {
            char buffer[255];
            std::memset(buffer, 0, sizeof(buffer));
            std::strncpy(buffer, tag.c_str(), sizeof(buffer));

            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
            ImGui::SetKeyboardFocusHere();

            ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);

            if (ImGui::InputText("##tagEditor", buffer, sizeof(buffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)
                    || (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0))) {
                tag = buffer;
                renamed_entity = {};
            }

            if (ImGui::IsKeyPressed(ImGuiKey_Escape))
                renamed_entity = {};

            ImGui::PopStyleVar();
        } else {
            ImGuiTreeNodeFlags flags = ((selected_context == entity) ? ImGuiTreeNodeFlags_Selected : 0) | ImGuiTreeNodeFlags_OpenOnArrow;
            flags |= ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanAvailWidth;

            bool opened = ImGui::TreeNodeEx((void*)(uint64_t)(uint32_t)entity, flags, "%s", tag.c_str());

            if (ImGui::BeginPopupContextItem()) {
                if (ImGui::MenuItem("Delete entity")) {
                    scene->MarkEntityForDeletion(entity);
                    selected_context = {};
                }
                ImGui::EndPopup();
            }

            if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
                // FocusViewportOnEntity(entity); // future implementation
                renamed_entity = {};
            } else if (ImGui::IsItemClicked()) {

                double current_time = ImGui::GetTime();
                double double_click_time = ImGui::GetIO().MouseDoubleClickTime; // Usually ~0.3s

                if (selected_context == entity) {
                    renamed_entity = entity;
                    if (current_time - last_selected_time > double_click_time)
                    {
                        renamed_entity = entity;
                    }
                } else {
                    // First time selecting
                    selected_context = entity;
                    last_selected_time = current_time;
                }
            }
            if (opened)
                ImGui::TreePop();
        }
    }

    void SceneHierarchy::DrawComponents(Entity& entity)
    {
        auto& reg = scene->GetRegistry();
        entt::entity handle = entity;

        if (!entity)
            return;

        entity.GetComponents<TagComponent>();
        TagOnImGuiRender(reg, entity);

        // TODO: Defer deletion like the entities?
        std::vector<entt::id_type> pending_deletion; // 0 is null/invalid in entt usually

        // Iterate through every component type entt knows about
        for (auto [id, storage] : reg.storage()) {
            if (storage.contains(handle)) {
                if (drawers.contains(id)) {
                    bool component_marked_deletion = false;
                    ImVec2 region_available = ImGui::GetContentRegionAvail();
                    ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_DefaultOpen
                        | ImGuiTreeNodeFlags_Framed
                        | ImGuiTreeNodeFlags_AllowOverlap;
                    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{ 4, 4 });
                    float line_height = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f;

                    bool opened = ImGui::TreeNodeEx((void*)(intptr_t)id, tree_node_flags, "%s", drawers[id].name.c_str());
                    ImGui::SameLine(region_available.x - line_height * 0.5f);

                    ImGui::PushStyleColor(ImGuiCol_Button, { 0.290f, 0.301f, 0.388f, 1.0f });
                    if (ImGui::Button("...", ImVec2{ line_height, line_height }))
                        ImGui::OpenPopup("component_settings");
                    ImGui::PopStyleColor();
                    ImGui::PopStyleVar();

                    if (ImGui::BeginPopup("component_settings")) {
                        if (ImGui::MenuItem("Remove component"))
                            pending_deletion.emplace_back(id);

                        ImGui::EndPopup();
                    }

                    if (opened) {
                        ImGui::Spacing();
                        drawers[id].draw_func(reg, handle);
                        ImGui::Spacing();
                        ImGui::TreePop();
                    }
                }
            }
        }

        for (auto& id : pending_deletion) {
            drawers[id].remove_func(reg, entity);
            pending_deletion.clear();
        }
    }
}
