#include "logging.hpp"
#include "physics.hpp"
#include "ref_scope.hpp"
#include "renderer/model3d.hpp"
#include <Jolt/Physics/Body/MotionType.h>
#include <Jolt/Physics/Collision/ObjectLayer.h>
#include <Jolt/Physics/Collision/Shape/ConvexShape.h>
#include <pch.hpp>

#include <renderer/renderer3d.hpp>
#include <scene/components.hpp>
#include <open_engine/scene/native_scriptable_entity.hpp>
#include <scene/entity.hpp>
#include <scene/scene.hpp>
#include <core/time.hpp>

#include <Jolt/Physics/Collision/Shape/Shape.h>
#include <Jolt/Physics/Collision/Shape/PlaneShape.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/EActivation.h>
#include <Jolt/Math/Real.h>
#include <Jolt/Math/Quat.h>
#include <cstdint>
#include <entt/entity/fwd.hpp>

namespace OpenEngine {

    static Ref<ShapeSettings> CreateShape(entt::registry& reg, entt::entity e)
    {
        if (reg.any_of<BoxShapeComponent>(e)) {
            auto& c = reg.get<BoxShapeComponent>(e);
            return CreateRef<BoxShapeSettings>(ToJolt(c.size * 0.5f));
        }
        if (reg.any_of<SphereShapeComponent>(e)) {
            auto& c = reg.get<SphereShapeComponent>(e);
            return CreateRef<SphereShapeSettings>(c.radius);
        }
        if (reg.any_of<PlaneShapeComponent>(e)) {
            auto& c = reg.get<PlaneShapeComponent>(e);
            const Plane plane({ 0.0f, 1.0f, 0.0f }, 0);
            return CreateRef<PlaneShapeSettings>(plane, nullptr, c.extent);
        }

        OE_CORE_ERROR("Entity {} has no shape component!", reg.get<TagComponent>(e).tag);
        return nullptr;
    };

    void Scene::OnRuntimeStart()
    {
        body_interface = &physics_engine.GetBodyInterface();
        // TODO: Cleanup components aquisition
        auto view = registry.view<TransformComponent, PhysicsBodyComponent>();
        for (auto e : view) {
            Entity entity = { e, this };
            
            auto shape_settings = CreateShape(registry, e);
            if (!shape_settings)
                shape_settings = CreateRef<BoxShapeSettings>(Vec3(0.5f, 0.5f, 0.5f));

            auto shape_result = shape_settings->Create();
            if (shape_result.HasError()) {
                OE_CORE_ERROR("Shape creation failed: {}", shape_result.GetError().c_str());
                continue;
            }
            ShapeRefC shape = shape_result.Get();

            auto& pbc = entity.GetComponents<PhysicsBodyComponent>();
            auto& tc = entity.GetComponents<TransformComponent>();
            
            glm::vec3& pos = tc.translation;
            glm::vec3& scale = tc.scale;
            glm::vec3& rot = tc.rotation;

            Quat quat = Quat::sEulerAngles(Vec3(rot.x, rot.y, rot.z));
            BodyCreationSettings settings(
                    shape,
                    //new BoxShape(Vec3(scale.x * 0.5, scale.y * 0.5f, scale.z * 0.5f)),
                    Vec3(pos.x, pos.y, pos.z),
                    quat,
                    (EMotionType)pbc.type,
                    pbc.layer);

            settings.mLinearDamping = pbc.linear_damping;
            settings.mAngularDamping = pbc.angular_damping;
            settings.mGravityFactor = pbc.gravity_factor;
            settings.mRestitution = pbc.restitution;
            settings.mFriction = pbc.friction;
            settings.mObjectLayer = (ObjectLayer)pbc.layer;

            pbc.body = body_interface->CreateAndAddBody(settings, (EActivation)pbc.initial_activation_state);
        }
    }

    void Scene::OnRuntimeStop()
    {
        auto view = registry.view<PhysicsBodyComponent>();
        for (auto e : view) {
            Entity entity = { e, this };

            auto& pbc = entity.GetComponents<PhysicsBodyComponent>();

            body_interface->RemoveBody(pbc.body);
            body_interface->DestroyBody(pbc.body);
        }
    }

    Entity Scene::CreateEntity(const std::string& name)
    {
        return CreateEntityWithUUID(UUID(), name);
    }

    Entity Scene::CreateEntityWithUUID(UUID uuid, const std::string& name)
    {
        Entity entity = { registry.create(), this };

        entity.AddComponent<IDComponent>(uuid);
        auto& tag = entity.AddComponent<TagComponent>();
        tag.tag = name.empty() ? "Entity" : name;

        return entity;
    }
    
    void Scene::DeleteEntity(entt::entity entity)
    {
        registry.destroy(entity);
    }

    void Scene::MarkEntityForDeletion(Entity entity)
    {
        pending_deletion.emplace_back(entity);
    }
    
    void Scene::UpdateEntities()
    {
        for (auto& entity : pending_deletion)
            DeleteEntity(entity);

        pending_deletion.clear();
    }

    void Scene::OnUpdatePhysics()
    {
        static int step = 0;
        static float accumulator = 0.0f;
        static const float fixedDT = 1.0f / 60.0f;

        float real_delta_time = Time::DeltaTime(); // e.g. from a timer
        accumulator += real_delta_time;

        if (accumulator >= fixedDT) {
            step++;
            physics_engine.Update(fixedDT, 1);
            accumulator -= fixedDT;
        }
    }

    void Scene::OnUpdateRuntime()
    {
        {
            OE_PROFILE_SCOPE("Updating Sripts");

            registry.view<NativeScriptComponent>().each([=, this](auto entity, auto& nsc)
                    {
                        if (!nsc.instance) {
                            nsc.instance = nsc.InstanciateScript();
                            nsc.instance->entity = Entity{ entity, this };
                            nsc.instance->OnCreate();
                        }

                            nsc.instance->OnUpdate();
                    });
        }

        auto view = registry.view<TransformComponent, PhysicsBodyComponent>();
        for (auto e : view) {
            Entity entity = { e, this };

            auto& pos = entity.GetComponents<TransformComponent>().translation;
            auto& body = entity.GetComponents<PhysicsBodyComponent>();

            body_interface->SetPosition(body.body, { pos.x, pos.y, pos.z }, EActivation::Activate);

            body_interface->SetRestitution(body.body, body.restitution);
            body_interface->SetFriction(body.body, body.friction);
        }

        OnUpdatePhysics();

        for (auto e : view) {
            Entity entity = { e, this };

            auto& transform = entity.GetComponents<TransformComponent>();
            auto& body = entity.GetComponents<PhysicsBodyComponent>();

            auto position = body_interface->GetPosition(body.body);
            auto rotation = body_interface->GetRotation(body.body).GetEulerAngles();
            transform.translation.x = position.GetX();
            transform.translation.y = position.GetY();
            transform.translation.z = position.GetZ();

            transform.rotation.x = rotation.GetX();
            transform.rotation.y = rotation.GetY();
            transform.rotation.z = rotation.GetZ();

            body_interface->SetGravityFactor(body.body, body.gravity_factor);
        }

        SceneCamera* main_camera = nullptr;
        glm::mat4 main_transform{ 1.0f };
        {
            auto camera_view = registry.view<CameraComponent, TransformComponent>();
            for (auto entity : camera_view) {
                auto [camera_component, transform] = camera_view.get<CameraComponent, TransformComponent>(entity);

                if (camera_component.primary) {
                    main_camera = &camera_component.camera;
                    main_transform = GetTransformFromComp(transform);
                    break;
                }
            }

            if (main_camera) {
                Renderer3D::BeginScene(*main_camera, main_transform);

                auto view = registry.view<TransformComponent, MeshComponent>();

                for (const auto& e : view) {
                    auto [transform, mesh] = view.get<TransformComponent, MeshComponent>(e);
                    Entity entity(e, this);

                    Material material;
                    if (entity.HasComponent<MaterialComponent>())
                        material = entity.GetComponents<MaterialComponent>().material;

                    Renderer3D::DrawMesh(mesh.mesh, material, GetTransformFromComp(transform));
                    /*
                    if (sprite.texture)
                        Renderer2D::DrawQuad(GetTransformFromComp(transform),
                                sprite.texture, sprite.color, (int)entity, sprite.tiling_factor);
                    else
                        Renderer2D::DrawQuad(GetTransformFromComp(transform),
                                sprite.color, (int)entity);
                    */
                }

                Renderer3D::EndScene();
            }
        }
    }

    void Scene::OnUpdateEditor(EditorCamera& camera)
	{
		Renderer3D::BeginScene(camera);

        auto view = registry.view<TransformComponent, MeshComponent>();

        for (const auto& entity : view) {
            auto [transform, mesh] = view.get<TransformComponent, MeshComponent>(entity);

            Material material;

            Entity _entity(entity, this);
            if (_entity.HasComponent<MaterialComponent>())
                material = _entity.GetComponents<MaterialComponent>().material;

            if (mesh.mesh)
                Renderer3D::DrawMesh(mesh.mesh, material, GetTransformFromComp(transform), (uint32_t)_entity);
        }

        auto model_view = registry.view<TransformComponent, ModelComponent>();

        for (const auto& entity : model_view) {
            auto [transform, model_comp] = model_view.get<TransformComponent, ModelComponent>(entity);

            Entity _entity(entity, this);

            Renderer3D::DrawModel(model_comp.model, GetTransformFromComp(transform), (uint32_t)_entity);
        }

		Renderer3D::EndScene();
	}

    void Scene::OnViewportResize(uint32_t width, uint32_t height)
    {
        viewport_width = width;
        viewport_height = height;

        auto view = registry.view<CameraComponent>();
        for (auto entity : view) {
            auto& camera_comp = view.get<CameraComponent>(entity);
            if (!camera_comp.fixed_aspect_ratio)
                camera_comp.camera.SetViewportSize(width, height);
        }
    }

    Entity Scene::GetPrimaryCamera()
    {
        auto view = registry.view<CameraComponent>();

        for (auto entity : view) {
            const auto& camera = view.get<CameraComponent>(entity);
            if (camera.primary)
                return Entity { entity, this };
        }

        return {};
    }

    template<typename T>
        void Scene::OnComponentAdded(Entity entity, T& component)
        {
            static_assert(false);
        }

    template<>
	void Scene::OnComponentAdded<TransformComponent>(Entity entity, TransformComponent& component)
	{
	}

    template<>
	void Scene::OnComponentAdded<CameraComponent>(Entity entity, CameraComponent& component)
	{
		if (viewport_width > 0 && viewport_height > 0)
			component.camera.SetViewportSize(viewport_width, viewport_height);
	}

    template<>
	void Scene::OnComponentAdded<SpriteRendererComponent>(Entity entity, SpriteRendererComponent& component)
	{
	}

	template<>
	void Scene::OnComponentAdded<TagComponent>(Entity entity, TagComponent& component)
	{
	}

	template<>
	void Scene::OnComponentAdded<NativeScriptComponent>(Entity entity, NativeScriptComponent& component)
	{
	}

	template<>
	void Scene::OnComponentAdded<MeshComponent>(Entity entity, MeshComponent& component)
	{
	}

	template<>
	void Scene::OnComponentAdded<MaterialComponent>(Entity entity, MaterialComponent& component)
	{
	}

	template<>
	void Scene::OnComponentAdded<PhysicsBodyComponent>(Entity entity, PhysicsBodyComponent& component)
	{
	}

	template<>
	void Scene::OnComponentAdded<SphereShapeComponent>(Entity entity, SphereShapeComponent& component)
	{
	}

	template<>
	void Scene::OnComponentAdded<BoxShapeComponent>(Entity entity, BoxShapeComponent& component)
	{
	}

    template<>
	void Scene::OnComponentAdded<IDComponent>(Entity entity, IDComponent& component)
	{
	}

    template<>
	void Scene::OnComponentAdded<PlaneShapeComponent>(Entity entity, PlaneShapeComponent& component)
	{
	}

    template<>
	void Scene::OnComponentAdded<ModelComponent>(Entity entity, ModelComponent& component)
	{
	}
}
