#ifndef COMPONENTS_HPP
#define COMPONENTS_HPP

#include "open_engine/renderer/renderer3d.hpp"
#include "open_engine/scene/scene_camera.hpp"
#include "open_engine/renderer/texture.hpp"
#include "open_engine/renderer/renderer3d.hpp"
#include "open_engine/physics.hpp"
#include "open_engine/core/uuid.hpp"
#include "open_engine/ref_scope.hpp"
#include "open_engine/renderer/model3d.hpp"

#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyInterface.h>
#include <Jolt/Physics/Body/MotionType.h>
#include <Jolt/Physics/Collision/ObjectLayer.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>
#include <Jolt/Physics/EActivation.h>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/fwd.hpp>
#include <glm/glm.hpp>

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>

#include <imgui_internal.h>
#include <imgui.h>
#include <string>

namespace OpenEngine {

    struct IDComponent
    {
        UUID id;

        IDComponent() = default;
        IDComponent(const IDComponent&) = default;
        IDComponent(const UUID& uuid) : id(uuid) {};
    };

    struct TagComponent
    {
        std::string tag;

        TagComponent() = default;
        TagComponent(const TagComponent&) = default;
        TagComponent(const std::string& tag)
            : tag(tag) {}
    };

    struct TransformComponent 
    {
        glm::vec3 translation = { 0.0f, 0.0f, 0.0f };
        glm::vec3 rotation = { 0.0f, 0.0f, 0.0f };
        glm::vec3 scale = { 1.0f, 1.0f, 1.0f };

        TransformComponent() = default;
        TransformComponent(const TransformComponent&) = default;
        TransformComponent(const glm::vec3& position)
            : translation(position) {}
    };

    glm::mat4 GetTransformFromComp(TransformComponent& transform_comp);

    struct SpriteRendererComponent 
    {
        glm::vec4 color{ 1.0f, 1.0f, 1.0f, 1.0f };
        Ref<Texture2D> texture;
        float tiling_factor = 1.0f;

        SpriteRendererComponent() = default;
        SpriteRendererComponent(const SpriteRendererComponent&) = default;
        SpriteRendererComponent(const glm::vec4& color)
            : color(color) {}
    };

    struct CameraComponent 
    {
        SceneCamera camera;
        bool primary = true;
        bool fixed_aspect_ratio = false;

        CameraComponent() = default;
        CameraComponent(const CameraComponent&) = default;
    };

    class NativeScriptableEntity;

    struct NativeScriptComponent 
    {
        NativeScriptableEntity* instance = nullptr;

        NativeScriptableEntity* (*InstanciateScript)();
        void (*DestroyInstanceScript)(NativeScriptComponent*);

        template <typename T>
            void Bind()
            {
                InstanciateScript = []() { return static_cast<NativeScriptableEntity*>(new T()); };
                DestroyInstanceScript = [](NativeScriptComponent* nsc)
                {
                    (delete nsc->instance);
                    nsc->instance = nullptr;
                };
            };
    };

    struct MeshComponent
    {
        Ref<Mesh> mesh;
        PrimitiveType primitive_type = PrimitiveType::None;
        
        MeshComponent() = default;
        MeshComponent(const MeshComponent&) = default;
        MeshComponent(const Ref<Mesh>& mesh)
            : mesh(mesh) {};
    };

    struct MaterialComponent
    {
        Material material;

        MaterialComponent() = default;
        MaterialComponent(const MaterialComponent&) = default;
        MaterialComponent(const Material& material)
            : material(material) {};
    };

    struct PhysicsBodyComponent
    {
        BodyID body;

        float linear_damping = 0.05f;
        float angular_damping = 0.05f;
        float gravity_factor = 1.0f;
        float restitution = 0.8f;
        float friction = 0.5f;

        int type = (int)EMotionType::Static;
        int initial_activation_state = (int)EActivation::Activate;
        int layer = (int)Layers::MOVING;

        PhysicsBodyComponent() = default;
        PhysicsBodyComponent(const PhysicsBodyComponent&) = default;
    };

    // TODO: Let's add more shapes
    struct BoxShapeComponent
    {
        glm::vec3 size = { 1.0f, 1.0f, 1.0f };

        BoxShapeComponent() = default;
        BoxShapeComponent(const BoxShapeComponent&) = default;
    };

    struct SphereShapeComponent
    {
        float radius = 1.0f;

        SphereShapeComponent() = default;
        SphereShapeComponent(const SphereShapeComponent&) = default;
    };

    struct PlaneShapeComponent
    {
        float extent = 1.0f;

        PlaneShapeComponent() = default;
        PlaneShapeComponent(const PlaneShapeComponent&) = default;
    };

    struct ModelComponent
    {
        Ref<Model3D> model;

        ModelComponent() = default;
        ModelComponent(const ModelComponent&) = default;
        ModelComponent(Ref<Model3D>& model)
            : model(model)
        {}
    };
}

#endif // COMPONENTS_HPP
