#include "imgui.h"
#include "open_engine/renderer/renderer3d.hpp"
#include "open_engine/scene/components.hpp"
#include <Jolt/Physics/Body/MotionType.h>
#include <cstdint>
#include <editor_component.hpp>
#include <entt/entity/fwd.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <open_engine.hpp>

namespace OpenEngine {

    void DrawVec3Control(const char* label, glm::vec3& values,
            float reset_value, float column_width,
            const std::array<const char*, 3> labels)
    {
        ImGuiIO& io = ImGui::GetIO();
        auto bold_font = io.Fonts->Fonts[0];

        ImGui::PushID(label);
        ImVec2 item_spacing = { 15.0f, 0.0f };
        ImGui::Columns(2);
        ImGui::SetColumnWidth(0, column_width);
        ImGui::Text("%s", label);
        if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && ImGui::IsItemHovered())
            values = glm::vec3(reset_value);
        ImGui::NextColumn();

        ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{ 0, 0 });

        float line_height = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f;
        ImVec2 button_size = { line_height + 3.0f, line_height };

        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.953f, 0.545f, 0.659f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 1.0f, 0.8f, 0.9f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.953f, 0.545f, 0.659f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 0.0f, 0.0f, 0.0f, 1.0f });
        ImGui::PushFont(bold_font);
        if (ImGui::Button(labels[0], button_size))
            values.x = reset_value;
        ImGui::PopFont();
        ImGui::PopStyleColor(4);

        ImGui::SameLine();
        ImGui::DragFloat("##X", &values.x, 0.1f, 0.0f, 0.0f, "%.2f");
        ImGui::PopItemWidth();
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, item_spacing);
        ImGui::SameLine();

        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.650f, 0.890f, 0.631f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.9f, 1.0f, 0.9f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.650f, 0.890f, 0.631f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 0.0f, 0.0f, 0.0f, 1.0f });
        ImGui::PushFont(bold_font);
        if (ImGui::Button(labels[1], button_size))
            values.y = reset_value;
        ImGui::PopFont();
        ImGui::PopStyleColor(4);
        ImGui::PopStyleVar();

        ImGui::SameLine();
        ImGui::DragFloat("##Y", &values.y, 0.1f, 0.0f, 0.0f, "%.2f");
        ImGui::PopItemWidth();
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, item_spacing);
        ImGui::SameLine();

        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.533f, 0.698f, 0.976f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.7f, 0.9f, 1.0f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.533f, 0.698f, 0.976f, 1.0f });
        ImGui::PushStyleColor(ImGuiCol_Text, ImVec4{ 0.0f, 0.0f, 0.0f, 1.0f });
        ImGui::PushFont(bold_font);
        if (ImGui::Button(labels[2], button_size))
            values.z = reset_value;
        ImGui::PopFont();
        ImGui::PopStyleColor(4);
        ImGui::PopStyleVar(1);

        ImGui::SameLine();
        ImGui::DragFloat("##Z", &values.z, 0.1f, 0.0f, 0.0f, "%.2f");
        ImGui::PopItemWidth();

        ImGui::PopStyleVar();

        ImGui::Columns(1);
        ImGui::PopID();
    }

    void TagOnImGuiRender(entt::registry& registry, entt::entity entity)
    {
        char buffer[256];

        auto& tag = registry.get<TagComponent>(entity).tag;
        std::memset(buffer, 0, sizeof(buffer));
        std::strncpy(buffer, tag.c_str(), sizeof(buffer));

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

    void TransformOnImGuiRender(entt::registry& registry, entt::entity entity) 
    {
        auto& transform = registry.get<TransformComponent>(entity);
        DrawVec3Control("Position", transform.translation);
        glm::vec3 rotation_comp = glm::degrees(transform.rotation);
        DrawVec3Control("Rotation", rotation_comp);
        transform.rotation = glm::radians(rotation_comp);
        DrawVec3Control("Scale", transform.scale);
        ImGui::Spacing();
    };
    
    void SpriteOnImGuiRender(entt::registry& registry, entt::entity entity) 
    {
        auto& sprite = registry.get<SpriteRendererComponent>(entity);
        ImGui::ColorEdit4("color", glm::value_ptr(sprite.color));
        ImGui::Button("Texture", { 100, 0 });

        if (ImGui::BeginDragDropTarget()) {
            if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("CONTENT_BROWSER_PAYLOAD"))
                sprite.texture = Texture2D::Create(((const char*)payload->Data));

            ImGui::EndDragDropTarget();
        }

        ImGui::DragFloat("Tiling factor", &sprite.tiling_factor);
    };

    void CameraOnImGuiRender(entt::registry& registry, entt::entity entity) 
    {
        auto& camera_comp = registry.get<CameraComponent>(entity);
        auto& camera = camera_comp.camera;
        ImGui::Checkbox("Is primary", &camera_comp.primary);
        const char* projection_type_strings[] = {"Perspective", "Orthographic"};
        const char* current_projection_type = projection_type_strings[(int)camera.GetProjectionType()];

        if (ImGui::BeginCombo("Projection", current_projection_type)) {
            for (int i = 0; i < 2; i++) {
                bool is_selected = current_projection_type == projection_type_strings[i];
                if (ImGui::Selectable(projection_type_strings[i], is_selected)) {
                    current_projection_type = projection_type_strings[i];
                    camera.SetProjectionType((SceneCamera::ProjectionType)i);
                }

                if (is_selected)
                    ImGui::SetItemDefaultFocus();
            }
            ImGui::EndCombo();
        }

        if (camera.GetProjectionType() == SceneCamera::ProjectionType::Perspective) {
            float fov = camera.GetVerticalFov();
            if (ImGui::DragFloat("Fov", &fov, 0.1f))
                camera.SetVerticalFov(fov);

            float near_clip = camera.GetPerspectiveNearClip();
            if (ImGui::DragFloat("Near clip", &near_clip, 0.1f))
                camera.SetPerspectiveNearClip(near_clip);

            float far_clip = camera.GetPerspectiveFarClip();
            if (ImGui::DragFloat("Far clip", &far_clip, 0.1f))
                camera.SetPerspectiveFarClip(far_clip);
        }
        else if (camera.GetProjectionType() == SceneCamera::ProjectionType::Orthographic) {
            ImGui::Checkbox("Has fixed aspect ratio", &camera_comp.fixed_aspect_ratio);
            float ortho_size = camera.GetOrthographicSize();
            if (ImGui::DragFloat("Orthographic size", &ortho_size, 0.1f))
                camera.SetOrthographicSize(ortho_size);

            float near_clip = camera.GetOrthographicNearClip();
            if (ImGui::DragFloat("Near clip", &near_clip, 0.1f))
                camera.SetOrthographicNearClip(near_clip);

            float far_clip = camera.GetOrthographicFarClip();
            if (ImGui::DragFloat("Far clip", &far_clip, 0.1f))
                camera.SetOrthographicFarClip(far_clip);
        }

    };

    void MeshOnImGuiRender(entt::registry& registry, entt::entity entity)
    {
        auto& mesh_component = registry.get<MeshComponent>(entity); 

        const char* items[] = { "None", "Quad", "Cube" };

        int item_selected_idx = (int)mesh_component.primitive_type;
        if (ImGui::BeginCombo("Mesh", items[item_selected_idx])) {
            for (int n = 0; n < 3; n++) {
                const bool is_selected = (item_selected_idx == n);
                if (ImGui::Selectable(items[n], is_selected))
                    item_selected_idx = n;

                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
                if (is_selected)
                    ImGui::SetItemDefaultFocus();
            }
            ImGui::EndCombo();
        }

        if ((int)mesh_component.primitive_type == (item_selected_idx))
            return;

        mesh_component.primitive_type = (PrimitiveType)(item_selected_idx);
        switch (mesh_component.primitive_type) {
            case OpenEngine::PrimitiveType::Quad:
                {
                    mesh_component.mesh = CreateQuad((uint32_t)entity);
                    break;
                }
            case OpenEngine::PrimitiveType::Cube:
                {
                    mesh_component.mesh = CreateCube((uint32_t)entity);
                    break;
                }
            default:
                mesh_component.mesh = nullptr;
                break;
        }
    }

    void MaterialOnImGuiRender(entt::registry& registry, entt::entity entity)
    {
        auto& material = registry.get<MaterialComponent>(entity).material;

        ImGui::SliderFloat4("Albedo", glm::value_ptr(material.albedo), 0, 1);
        ImGui::SliderFloat("Roughness", &material.roughness, 0, 1);
        ImGui::SliderFloat("Metallic", &material.metallic, 0, 1);
        ImGui::SliderFloat("Ambient strength", &material.ambient_strength, 0, 1);
        ImGui::SliderFloat("Specular strength", &material.specular_strength, 0, 10);
    }

    void BodyOnImGuiRender(entt::registry &registry, entt::entity entity)
    {
        auto& body_comp = registry.get<PhysicsBodyComponent>(entity);

        const char* items[] = { "Static", "Kinematic", "Dynamic" };
        int item_selected_idx = body_comp.type;

        if (ImGui::BeginCombo("Body type", items[item_selected_idx])) {
            for (int n = 0; n < 3; n++)
            {
                const bool is_selected = (item_selected_idx == n);
                if (ImGui::Selectable(items[n], is_selected))
                    item_selected_idx = n;

                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
                if (is_selected)
                    ImGui::SetItemDefaultFocus();
            }
            ImGui::EndCombo();
        }

        body_comp.type = item_selected_idx;

        ImGui::SliderFloat("Linear damping", &body_comp.linear_damping, 0, 1);
        ImGui::SliderFloat("Angular damping", &body_comp.angular_damping, 0, 1);
        ImGui::SliderFloat("Gravity factor", &body_comp.gravity_factor, 0, 1);
        ImGui::SliderFloat("Bounciness", &body_comp.restitution, 0, 1);
        ImGui::SliderFloat("Friction", &body_comp.friction, 0, 1);
    }

    void SphereShapeOnImGuiRender(entt::registry &registry, entt::entity entity)
    {
        auto& sphere_comp = registry.get<SphereShapeComponent>(entity);
        
        ImGui::DragFloat("Radius",
                &sphere_comp.radius,
                0.1f,
                0.11f, FLT_MAX,
                "%.2f",
                ImGuiSliderFlags_AlwaysClamp);
    }

    void BoxShapeOnImGuiRender(entt::registry &registry, entt::entity entity)
    {
        auto& box_comp = registry.get<BoxShapeComponent>(entity);
        
        ImGui::DragFloat3("Size",
                glm::value_ptr(box_comp.size),
                0.1f,
                0.11f, FLT_MAX,
                "%.2f",
                ImGuiSliderFlags_AlwaysClamp);
    }

    void PlaneShapeOnImGuiRender(entt::registry& registry, entt::entity entity)
    {
        auto& planed_comp = registry.get<PlaneShapeComponent>(entity);

        ImGui::DragFloat("Size",
                &planed_comp.extent,
                0.1f,
                0.01f, FLT_MAX,
                "%.2f",
                ImGuiSliderFlags_AlwaysClamp);
    }

    void ModelOnImGuiRender(entt::registry& registry, entt::entity entity)
    {
        auto& model_comp = registry.get<ModelComponent>(entity);

        bool opened = ImGui::TreeNodeEx(
                (void*)(intptr_t)entity,
                ImGuiTreeNodeFlags_None,
                "Meshes");

        if (opened) {
            unsigned int i = 0;
            for (auto& mesh : model_comp.model->GetMeshes())
                ImGui::Text("Mesh: %i", i++);


            ImGui::PushStyleColor(ImGuiCol_ChildBg, {1, 1, 1, 1});
            if (ImGui::BeginChild("Material list")) {
                for (auto& mesh : model_comp.model->GetMeshes())
                    ImGui::Text("Material: %s", mesh.material.name.c_str());
                ImGui::EndChild();
            }
            ImGui::PopStyleColor();

            ImGui::TreePop();
        }
    }
}
