//
// Created by sfd on 25-5-26.
//

#include "Scene.h"

#include <spirv_common.hpp>
#include <Hazel/Renderer/Renderer2D.h>

#include "Components.h"
#include "ScriptableEntity.h"

#include "Entity.h"
#include "../../../vendor/box2d/src/physics_world.h"
#include "box2d/box2d.h"
#include "box2d/id.h"


namespace Hazel
{
    static b2BodyType RigidBodyTypeToBox2DBodyType(RigidBody2DComponent::BodyType type)
    {
        switch (type)
        {
        case RigidBody2DComponent::BodyType::Static:      return b2_staticBody;
        case RigidBody2DComponent::BodyType::Dynamic:     return b2_dynamicBody;
        case RigidBody2DComponent::BodyType::Kinematic:   return b2_kinematicBody;
        }

        HZ_CORE_ERROR("Unknown body type");
        return b2_staticBody;
    }

    Scene::Scene()
    {
    }

    Scene::~Scene()
    {
        delete m_PhysicsWorld;
    }

    template<typename... Component>
    static void CopyComponent(entt::registry& dst, entt::registry& src, const std::unordered_map<UUID, entt::entity>& enttMap)
    {

        ([&]()
        {
            auto view = src.view<Component>();
            for (auto srcEntity : view)
            {
                entt::entity dstEntity = enttMap.at(src.get<IDComponent>(srcEntity).ID);

                auto& srcComponent = src.get<Component>(srcEntity);
                dst.emplace_or_replace<Component>(dstEntity, srcComponent);
            }
        }(), ...);
    }

    template<typename... Component>
    static void CopyComponent(ComponentGroup<Component ...>, entt::registry& dst, entt::registry& src, const std::unordered_map<UUID, entt::entity>& enttMap)
    {
        CopyComponent<Component ...>(dst, src, enttMap);
    }

    template<typename... Component>
    static void CopyComponentIfExists(Entity dst, Entity src)
    {
        ([&](){
            if (src.HasComponent<Component>())
                dst.AddOrReplaceComponent<Component>(src.GetComponent<Component>());
        }(), ...);
    }

    template<typename... Component>
    static void CopyComponentIfExists(ComponentGroup<Component ...>, Entity dst, Entity src)
    {
        CopyComponentIfExists<Component ...>(dst, src);
    }

    Ref<Scene> Scene::Copy(Ref<Scene> other)
    {
        Ref<Scene> newScene = CreateRef<Scene>();

        newScene->m_ViewportWidth = other->m_ViewportWidth;
        newScene->m_ViewportHeight = other->m_ViewportHeight;

        std::unordered_map<UUID, entt::entity> enttMap;

        auto& srcSceneRegistry = other->m_Registry;
        auto& dstSceneRegistry = newScene->m_Registry;

        // Create entity in new Secne
        auto idView = srcSceneRegistry.view<IDComponent>();
        for (auto e : idView)
        {
            UUID uuid = srcSceneRegistry.get<IDComponent>(e).ID;
            const auto& name = srcSceneRegistry.get<TagComponent>(e).Tag;
            Entity newEntity = newScene->CreateEntityWithUUID(uuid, name);
            enttMap[uuid] = (entt::entity)newEntity;
        }

        // Copy all components except IDComponent and TagComponent
        CopyComponent(AllComponents{}, dstSceneRegistry, srcSceneRegistry, enttMap);
        /*
        CopyComponent<TransformComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
        CopyComponent<SpriteRendererComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
        CopyComponent<CircleRendererComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
        CopyComponent<CameraComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
        CopyComponent<NativeScriptComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
        CopyComponent<RigidBody2DComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
        CopyComponent<BoxCollider2DComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
        CopyComponent<CircleCollider2DComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
        */

        return newScene;
    }

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

    Entity Scene::CreateEntityWithUUID(UUID uuid, const std::string& name)
    {
        Entity entity = {m_Registry.create(), this};
        entity.AddComponent<IDComponent>(uuid);
        entity.AddComponent<TransformComponent>();
        entity.AddComponent<TagComponent>(name.empty() ? "Entity" : name);

        return entity;

    }

    void Scene::DestoryEntity(const Entity entity)
    {
        m_Registry.destroy(entity);
    }




    void Scene::OnPhysics2DStart()
    {
        b2WorldDef worldDef = b2DefaultWorldDef();
        worldDef.gravity = b2Vec2(0.0f, -9.81f);
        m_PhysicsWorld = new b2WorldId(b2CreateWorld( &worldDef ));

        auto view = m_Registry.view<RigidBody2DComponent>();
        for (auto e : view)
        {
            Entity entity = {e, this};
            auto &transform = entity.GetComponent<TransformComponent>();
            auto &rb2D = entity.GetComponent<RigidBody2DComponent>();

            b2BodyDef bodyDef = b2DefaultBodyDef();
            bodyDef.type = RigidBodyTypeToBox2DBodyType(rb2D.Type);
            bodyDef.position = {transform.Translation.x, transform.Translation.y};
            float angle = transform.Rotation.z;
            bodyDef.rotation = b2Rot{cos(angle), sin(angle)};

            // bodyDef.fixedRotation = true;

            b2BodyId body = b2CreateBody(*m_PhysicsWorld, &bodyDef);

            rb2D.RuntimeBody = body;
            if (entity.HasComponent<BoxCollider2DComponent>())
            {
                auto& bc2d = entity.GetComponent<BoxCollider2DComponent>();

                float hx = bc2d.Size.x * transform.Scale.x;
                float hy = bc2d.Size.y * transform.Scale.y;

                float offsetX = bc2d.Offset.x * transform.Scale.x;
                float offsetY = bc2d.Offset.y * transform.Scale.y;
                auto rot = b2Rot(cos(0), sin(0));
                b2Polygon boxShape = b2MakeOffsetBox(hx, hy, b2Vec2(offsetX, offsetY), rot);

                b2ShapeDef shapedef = b2DefaultShapeDef();
                shapedef.density = bc2d.Density;
                shapedef.material.friction = bc2d.Friction;
                shapedef.material.restitution = bc2d.Restitution;

                b2CreatePolygonShape(body, &shapedef,&boxShape);
            }

            if (entity.HasComponent<CircleCollider2DComponent>())
            {
                auto& cc2d = entity.GetComponent<CircleCollider2DComponent>();

                b2Circle circle;
                circle.center.x = transform.Scale.x * cc2d.Offset.x;
                circle.center.y = transform.Scale.y * cc2d.Offset.y;
                circle.radius = transform.Scale.x * cc2d.Radius;

                b2ShapeDef shapedef = b2DefaultShapeDef();
                shapedef.density = cc2d.Density;
                shapedef.material.friction = cc2d.Friction;
                shapedef.material.restitution = cc2d.Restitution;

                b2CreateCircleShape(body, &shapedef,&circle);

            }
        }
    }

    void Scene::OnPhysics2DStop()
    {
        b2DestroyWorld( *m_PhysicsWorld );

        m_PhysicsWorld = nullptr;
    }

    void Scene::RenderScene(const EditorCamera& camera)
    {
        Renderer2D::BeginScene(camera);

        // Draw Sprites
        auto group = m_Registry.group<TransformComponent>(entt::get<SpriteRendererComponent>);
        for (auto entity : group)
        {
            auto [transform, sprite] = group.get<TransformComponent, SpriteRendererComponent>(entity);

            // Renderer2D::DrawQuad(transform.GetTransform(), sprite.Color);
            Renderer2D::DrawSprite(transform.GetTransform(), sprite, (int)entity);
        }

        // Draw Circles
        auto view = m_Registry.view<TransformComponent, CircleRendererComponent>();
        for (auto entity : view)
        {
            auto [transform, circle] = view.get<TransformComponent, CircleRendererComponent>(entity);

            // Renderer2D::DrawQuad(transform.GetTransform(), sprite.Color);
            Renderer2D::DrawCircle(transform.GetTransform(), circle.Color, circle.Thickness, circle.Fade, (int)entity);
        }

        Renderer2D::EndScene();
    }

    void Scene::OnRuntimeStart()
    {
        OnPhysics2DStart();
    }

    void Scene::OnSimulationStart()
    {
        OnPhysics2DStart();
    }

    void Scene::OnSimulationStop()
    {
        OnPhysics2DStop();
    }

    void Scene::OnRuntimeStop()
    {
        OnPhysics2DStop();
    }

    void Scene::OnUpdateRuntime(TimeStep& ts)
    {

        // Update scripts
        {
            m_Registry.view<NativeScriptComponent>().each([=](auto entity, auto& nsc)
            {
                // TODO: move to Scene::OnScenePlay
                if (!nsc.Instance)
                {
                    nsc.Instance = nsc.InstantiateScript();
                    nsc.Instance->m_Entity = Entity{entity, this};
                    nsc.Instance->OnCreate();
                }

                nsc.Instance->OnUpdate(ts);

            });
        }

        // Physics
        {

            b2World_Step(*m_PhysicsWorld, ts, 4);

            auto view = m_Registry.view<RigidBody2DComponent>();
            for (auto e : view)
            {
                Entity entity = {e, this};
                auto& transform = entity.GetComponent<TransformComponent>();
                auto& rb2D = entity.GetComponent<RigidBody2DComponent>();

                b2BodyId &body = rb2D.RuntimeBody;

                const auto& position = b2Body_GetPosition(body);
                transform.Translation.x = position.x;
                transform.Translation.y = position.y;
                const float angle = b2Rot_GetAngle(b2Body_GetRotation(body));
                transform.Rotation.z = angle;
            }
        }

        // Renderer 2D
        Camera* mainCamera = nullptr;
        glm::mat4 cameraTranform;
        {
            for (auto view = m_Registry.view<TransformComponent, CameraComponent>(); const auto entity : view)
            {
                auto&& [transform, camera] = view.get<TransformComponent, CameraComponent>(entity);

                if (camera.Primary)
                {
                    mainCamera = &camera.Camera;
                    cameraTranform = transform.GetTransform();
                    break;
                }
            }
        }


        if (mainCamera)
        {
            Renderer2D::BeginScene(mainCamera->GetProjection(), cameraTranform);

            // Draw Sprites
            auto group = m_Registry.group<TransformComponent>(entt::get<SpriteRendererComponent>);
            for (auto entity : group)
            {
                auto [transform, sprite] = group.get<TransformComponent, SpriteRendererComponent>(entity);

                // Renderer2D::DrawQuad(transform.GetTransform(), sprite.Color);
                Renderer2D::DrawSprite(transform.GetTransform(), sprite, (int)entity);
            }

            // Draw Circles
            auto view = m_Registry.view<TransformComponent, CircleRendererComponent>();
            for (auto entity : view)
            {
                auto [transform, circle] = view.get<TransformComponent, CircleRendererComponent>(entity);

                // Renderer2D::DrawQuad(transform.GetTransform(), sprite.Color);
                Renderer2D::DrawCircle(transform.GetTransform(), circle.Color, circle.Thickness, circle.Fade, (int)entity);
            }

            Renderer2D::EndScene();
        }
    }

    void Scene::OnUpdateSimulation(TimeStep& ts, const EditorCamera& camera)
    {
        // Physics
        {
            b2World_Step(*m_PhysicsWorld, ts, 4);

            auto view = m_Registry.view<RigidBody2DComponent>();
            for (auto e : view)
            {
                Entity entity = {e, this};
                auto& transform = entity.GetComponent<TransformComponent>();
                auto& rb2D = entity.GetComponent<RigidBody2DComponent>();

                b2BodyId &body = rb2D.RuntimeBody;

                const auto& position = b2Body_GetPosition(body);
                transform.Translation.x = position.x;
                transform.Translation.y = position.y;
                const float angle = b2Rot_GetAngle(b2Body_GetRotation(body));
                transform.Rotation.z = angle;
            }
        }

        // Render
        RenderScene(camera);
    }


    void Scene::OnUpdateEditor(TimeStep & ts, const EditorCamera & camera)
    {
        // Render
        RenderScene(camera);
    }

    void Scene::OnViewportResize(uint32_t width, uint32_t height)
    {
        m_ViewportWidth = width;
        m_ViewportHeight = height;

        //Reisze our non-fixedAspectRatio Cameras

        auto view = m_Registry.view<CameraComponent>();
        for (auto entity : view)
        {
            auto& cameraComponent = view.get<CameraComponent>(entity);
            if (!cameraComponent.FixedAspectRatio)
            {
                cameraComponent.Camera.SetViewPortSize(width, height);
            }
        }

    }

    void Scene::DuplicateEntity(Entity entity)
    {
        Entity newEntity = CreateEntity(entity.GetName() + " copy");

        CopyComponentIfExists(AllComponents{}, newEntity, entity);
        /*
        CopyComponentIfExists<TransformComponent>(newEntity, entity);
        CopyComponentIfExists<SpriteRendererComponent>(newEntity, entity);
        CopyComponentIfExists<CircleRendererComponent>(newEntity, entity);
        CopyComponentIfExists<CameraComponent>(newEntity, entity);
        CopyComponentIfExists<NativeScriptComponent>(newEntity, entity);
        CopyComponentIfExists<RigidBody2DComponent>(newEntity, entity);
        CopyComponentIfExists<BoxCollider2DComponent>(newEntity, entity);
        CopyComponentIfExists<CircleCollider2DComponent>(newEntity, entity);
        */
    }

    Entity Scene::GetPrimaryCameraEntity()
    {
        auto view = m_Registry.view<CameraComponent>();
        for (auto entity : view)
        {
            auto& cameraComponent = view.get<CameraComponent>(entity);
            if (cameraComponent.Primary)
                return Entity{entity, this};
        }
        return {};
    }

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

    }

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

    template<>
    void Scene::OnComponentAdded<CameraComponent>(Entity entity, CameraComponent& component)
    {
        if (m_ViewportWidth > 0 && m_ViewportHeight > 0)
            component.Camera.SetViewPortSize(m_ViewportWidth, m_ViewportHeight);
    }

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

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

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

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

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

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

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


    template HAZEL_API void Scene::OnComponentAdded<IDComponent>(Entity, IDComponent&);
    template HAZEL_API void Scene::OnComponentAdded<TransformComponent>(Entity, TransformComponent&);
    template HAZEL_API void Scene::OnComponentAdded<CameraComponent>(Entity, CameraComponent&);
    template HAZEL_API void Scene::OnComponentAdded<TagComponent>(Entity, TagComponent&);
    template HAZEL_API void Scene::OnComponentAdded<SpriteRendererComponent>(Entity, SpriteRendererComponent&);
    template HAZEL_API void Scene::OnComponentAdded<CircleRendererComponent>(Entity, CircleRendererComponent&);
    template HAZEL_API void Scene::OnComponentAdded<NativeScriptComponent>(Entity, NativeScriptComponent&);
    template HAZEL_API void Scene::OnComponentAdded<RigidBody2DComponent>(Entity, RigidBody2DComponent&);
    template HAZEL_API void Scene::OnComponentAdded<BoxCollider2DComponent>(Entity, BoxCollider2DComponent&);
    template HAZEL_API void Scene::OnComponentAdded<CircleCollider2DComponent>(Entity, CircleCollider2DComponent&);
}
