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

#include "SceneSerializer.h"

#include <fstream>

#include "Components.h"
#include "Entity.h"
#include "yaml-cpp/yaml.h"

namespace YAML
{
    template<>
    struct convert<glm::vec2>
    {
        static Node encode(const glm::vec2& out)
        {
            Node node;
            node.push_back(out.x);
            node.push_back(out.y);
            node.SetStyle(EmitterStyle::Flow);
            return node;
        }

        static bool decode(const Node& node, glm::vec2& out)
        {
            if (node.size() != 2 && !node.IsSequence())
                return false;
            out.x = node[0].as<float>();
            out.y = node[1].as<float>();
            return true;
        }
    };

    template<>
    struct convert<glm::vec3>
    {
        static Node encode(const glm::vec3& out)
        {
            Node node;
            node.push_back(out.x);
            node.push_back(out.y);
            node.push_back(out.z);
            node.SetStyle(EmitterStyle::Flow);
            return node;
        }

        static bool decode(const Node& node, glm::vec3& out)
        {
            if (node.size() != 3 && !node.IsSequence())
                return false;
            out.x = node[0].as<float>();
            out.y = node[1].as<float>();
            out.z = node[2].as<float>();
            return true;
        }
    };

    template<>
    struct convert<glm::vec4>
    {
        static Node encode(const glm::vec4& out)
        {
            Node node;
            node.push_back(out.x);
            node.push_back(out.y);
            node.push_back(out.z);
            node.push_back(out.w);
            node.SetStyle(EmitterStyle::Flow);
            return node;
        }

        static bool decode(const Node& node, glm::vec4& out)
        {
            if (node.size() != 4 && !node.IsSequence())
                return false;
            out.x = node[0].as<float>();
            out.y = node[1].as<float>();
            out.z = node[2].as<float>();
            out.w = node[3].as<float>();
            return true;
        }
    };

    static std::string RigidBody2DTypeToString(Hazel::RigidBody2DComponent::BodyType type)
    {
        switch (type)
        {
            case Hazel::RigidBody2DComponent::BodyType::Static: return "Static";
            case Hazel::RigidBody2DComponent::BodyType::Dynamic: return "Dynamic";
            case Hazel::RigidBody2DComponent::BodyType::Kinematic: return "Kinematic";
        }
        HZ_CORE_ERROR("Unknown body type!");
        return {};
    }

    static Hazel::RigidBody2DComponent::BodyType RigidBody2DStringToType(const std::string& type)
    {
        if (type == "Static") return Hazel::RigidBody2DComponent::BodyType::Static;
        if (type == "Dynamic") return Hazel::RigidBody2DComponent::BodyType::Dynamic;
        if (type == "Kinematic") return Hazel::RigidBody2DComponent::BodyType::Kinematic;
        HZ_CORE_ERROR("Unknown body type!");
        return Hazel::RigidBody2DComponent::BodyType::Static;
    }

    Emitter& operator<<(Emitter& out, const glm::vec2& value)
    {
        out << Flow;
        out << BeginSeq << value.x << value.y << EndSeq;
        return out;
    }

    Emitter& operator<<(Emitter& out, const glm::vec3& value)
    {
        out << Flow;
        out << BeginSeq << value.x << value.y << value.z << EndSeq;
        return out;
    }

    Emitter& operator<<(Emitter& out, const glm::vec4& value)
    {
        out << Flow;
        out << BeginSeq << value.x << value.y << value.z << value.w << EndSeq;
        return out;
    }
}

namespace Hazel
{



    SceneSerializer::SceneSerializer(const Ref<Scene> scene)
        : m_Scene(scene)
    {
    }

    static void SerializeEntity(YAML::Emitter& out, Entity entity)
    {
        if (!entity.HasComponent<IDComponent>())
        {
            HZ_CORE_ERROR("Entity Could not find UUID!");
            assert(-1);
        }

        out << YAML::BeginMap; // entity
        out << YAML::Key << "Entity" << YAML::Value << entity.GetUUID();

        if (entity.HasComponent<TagComponent>())
        {
            out << YAML::Key << "TagComponent";

            out << YAML::BeginMap;      // TagComponent

            const auto& tag = entity.GetComponent<TagComponent>().Tag;
            out << YAML::Key << "Tag" << YAML::Value << tag;

            out << YAML::EndMap;        // TagComponent
        }

        if (entity.HasComponent<TransformComponent>())
        {
            out << YAML::Key << "TransformComponent";
            out << YAML::BeginMap;      // TransformComponent

            auto& transform = entity.GetComponent<TransformComponent>();
            out << YAML::Key << "Translation" << YAML::Value << transform.Translation;
            out << YAML::Key << "Rotation" << YAML::Value << transform.Rotation;
            out << YAML::Key << "Scale" << YAML::Value << transform.Scale;

            out << YAML::EndMap;        // TransformComponent
        }

        if (entity.HasComponent<CameraComponent>())
        {
            out << YAML::Key << "CameraComponent";

            out << YAML::BeginMap;      // CameraComponent

            auto& cameraComponent = entity.GetComponent<CameraComponent>();
            auto& camera = cameraComponent.Camera;

            out << YAML::Key << "Camera" << YAML::Value;      // CameraComponent
            out << YAML::BeginMap;      // Camera

            out << YAML::Key << "ProjectionType" << YAML::Value << (int)camera.GetProjectionType();
            out << YAML::Key << "PerspectiveVerticalFOV" << YAML::Value << camera.GetPerspectiveVerticalFOV();
            out << YAML::Key << "PerspectiveNear" << YAML::Value << camera.GetPerspectiveNearCLip();
            out << YAML::Key << "PerspectiveFar" << YAML::Value << camera.GetPerspectiveFarCLip();
            out << YAML::Key << "OrthographicSize" << YAML::Value << camera.GetOrthographicSize();
            out << YAML::Key << "OrthographicNear" << YAML::Value << camera.GetOrthographicNearCLip();
            out << YAML::Key << "OrthographicFar" << YAML::Value << camera.GetOrthographicFarCLip();

            out << YAML::Key << "Primary" << YAML::Value << cameraComponent.Primary;
            out << YAML::Key << "FixedAspectRatio" << YAML::Value << cameraComponent.FixedAspectRatio;

            out << YAML::EndMap;      // Camera
            out << YAML::EndMap;        // CameraComponent
        }

        if (entity.HasComponent<CircleRendererComponent>())
        {
            out << YAML::Key << "CircleRendererComponent";

            out << YAML::BeginMap;      // CircleRendererComponent
            auto& circleRendererComponent = entity.GetComponent<CircleRendererComponent>();
            out << YAML::Key << "Color" << YAML::Value << circleRendererComponent.Color;
            out << YAML::Key << "Radius" << YAML::Value << circleRendererComponent.Radius;
            out << YAML::Key << "Thickness" << YAML::Value << circleRendererComponent.Thickness;
            out << YAML::Key << "Fade" << YAML::Value << circleRendererComponent.Fade;
            out << YAML::EndMap;        // CircleRendererComponent
        }

        if (entity.HasComponent<SpriteRendererComponent>())
        {
            out << YAML::Key << "SpriteRendererComponent";

            out << YAML::BeginMap;      // SpriteRendererComponent
            auto& spriteRendererComponent = entity.GetComponent<SpriteRendererComponent>();
            out << YAML::Key << "Color" << YAML::Value << spriteRendererComponent.Color;
            out << YAML::EndMap;        // SpriteRendererComponent
        }

        if (entity.HasComponent<RigidBody2DComponent>())
        {
            out << YAML::Key << "RigidBody2DComponent";

            out << YAML::BeginMap;      // RigidBody2DComponent
            auto& rb2dComponent = entity.GetComponent<RigidBody2DComponent>();
            out << YAML::Key << "BodyType" << YAML::Value << YAML::RigidBody2DTypeToString(rb2dComponent.Type);
            out << YAML::Key << "FixedRotation" << YAML::Value << rb2dComponent.FixedRotation;
            out << YAML::EndMap;        // RigidBody2DComponent
        }

        if (entity.HasComponent<BoxCollider2DComponent>())
        {
            out << YAML::Key << "BoxCollider2DComponent";

            out << YAML::BeginMap;      // BoxCollider2DComponent
            auto& bc2dComponent = entity.GetComponent<BoxCollider2DComponent>();
            out << YAML::Key << "Offset" << YAML::Value << bc2dComponent.Offset;
            out << YAML::Key << "Size" << YAML::Value << bc2dComponent.Size;
            out << YAML::Key << "Density" << YAML::Value << bc2dComponent.Density;
            out << YAML::Key << "Friction" << YAML::Value << bc2dComponent.Friction;
            out << YAML::Key << "Restitution" << YAML::Value << bc2dComponent.Restitution;
            out << YAML::Key << "Restitution" << YAML::Value << bc2dComponent.Restitution;
            out << YAML::Key << "RestitutionThreshold" << YAML::Value << bc2dComponent.RestitutionThreshold;
            out << YAML::EndMap;        // BoxCollider2DComponent
        }

        if (entity.HasComponent<CircleCollider2DComponent>())
        {
            out << YAML::Key << "CircleCollider2DComponent";

            out << YAML::BeginMap;      // CircleCollider2DComponent
            auto& cc2dComponent = entity.GetComponent<CircleCollider2DComponent>();
            out << YAML::Key << "Offset" << YAML::Value << cc2dComponent.Offset;
            out << YAML::Key << "Radius" << YAML::Value << cc2dComponent.Radius;
            out << YAML::Key << "Density" << YAML::Value << cc2dComponent.Density;
            out << YAML::Key << "Friction" << YAML::Value << cc2dComponent.Friction;
            out << YAML::Key << "Restitution" << YAML::Value << cc2dComponent.Restitution;
            out << YAML::Key << "Restitution" << YAML::Value << cc2dComponent.Restitution;
            out << YAML::Key << "RestitutionThreshold" << YAML::Value << cc2dComponent.RestitutionThreshold;
            out << YAML::EndMap;        // CircleCollider2DComponent
        }

        out << YAML::EndMap; // entity
    }

    void SceneSerializer::Serialize(const std::string& filepath) const
    {
        YAML::Emitter out;
        out << YAML::BeginMap;
        out << YAML::Key << "Scene" << YAML::Value << "Untitled";
        out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq;
        m_Scene->m_Registry.view<entt::entity>().each([&](auto entityID)
        {
            const Entity entity = {entityID, m_Scene.get()};
            if (!entity)
                return;

            SerializeEntity(out, entity);
        });

        std::ofstream fout(filepath);
        fout << out.c_str();
    }

    void SceneSerializer::SerializeRuntime(const std::string& filepath)
    {
    }

    bool SceneSerializer::Deserialize(const std::string& filepath)
    {
        const std::ifstream stream(filepath);
        std::stringstream strStream;
        strStream << stream.rdbuf();

        YAML::Node data = YAML::Load(strStream.str());
        if (!data["Scene"])
        {
            HZ_CORE_WARN("cannot find key: Scene");
            return false;
        }


        auto sceneName = data["Scene"].as<std::string>();

        HZ_CORE_TRACE("Deserializing Scene {}", strStream.str());

        auto entities = data["Entities"];
        if (entities)
        {
            for (auto entity : entities)
            {
                uint64_t uuid = entity["Entity"].as<uint64_t>();

                std::string name;
                auto TagComponent = entity["TagComponent"];
                if (TagComponent)
                    name = TagComponent["Tag"].as<std::string>();

                HZ_CORE_TRACE("Deserializing Entity: {0}:({1})", name, uuid);

                Entity deserializedEntity = m_Scene->CreateEntityWithUUID(uuid, name);

                auto transformComponent = entity["TransformComponent"];
                if (transformComponent)
                {
                    auto& transform = deserializedEntity.GetComponent<TransformComponent>();
                    transform.Translation = transformComponent["Translation"].as<glm::vec3>();
                    transform.Rotation = transformComponent["Rotation"].as<glm::vec3>();
                    transform.Scale = transformComponent["Scale"].as<glm::vec3>();
                }

                auto cameraComponent = entity["CameraComponent"];
                if (cameraComponent)
                {
                    auto& camera = deserializedEntity.AddComponent<CameraComponent>();

                    auto cameraProps = cameraComponent["Camera"];
                    camera.Camera.SetProjectionType((ScenceCamera::ProjectionType)cameraProps["ProjectionType"].as<int>());

                    camera.Camera.SetPerspectiveVerticalFOV(cameraProps["PerspectiveVerticalFOV"].as<float>());
                    camera.Camera.SetPerspectiveNearClip(cameraProps["PerspectiveNear"].as<float>());
                    camera.Camera.SetPerspectiveFarClip(cameraProps["PerspectiveFar"].as<float>());

                    camera.Camera.SetOrthographicSize(cameraProps["OrthographicSize"].as<float>());
                    camera.Camera.SetOrthographicNearClip(cameraProps["OrthographicNear"].as<float>());
                    camera.Camera.SetOrthographicFarClip(cameraProps["OrthographicFar"].as<float>());

                    camera.Primary = cameraProps["Primary"].as<bool>();
                    camera.FixedAspectRatio = cameraProps["FixedAspectRatio"].as<bool>();
                }

                auto spriteRendererComponent = entity["SpriteRendererComponent"];
                if (spriteRendererComponent)
                {
                    auto& sprite = deserializedEntity.AddComponent<SpriteRendererComponent>();
                    sprite.Color = spriteRendererComponent["Color"].as<glm::vec4>();
                }

                auto circleRendererComponent = entity["CircleRendererComponent"];
                if (circleRendererComponent)
                {
                    auto& circle = deserializedEntity.AddComponent<CircleRendererComponent>();
                    circle.Color = circleRendererComponent["Color"].as<glm::vec4>();
                    circle.Radius = circleRendererComponent["Radius"].as<float>();
                    circle.Thickness = circleRendererComponent["Thickness"].as<float>();
                    circle.Fade = circleRendererComponent["Fade"].as<float>();
                }

                auto rigidBody2DComponent = entity["RigidBody2DComponent"];
                if (rigidBody2DComponent)
                {
                    auto& rb2d = deserializedEntity.AddComponent<RigidBody2DComponent>();
                    rb2d.Type = YAML::RigidBody2DStringToType(rigidBody2DComponent["BodyType"].as<std::string>());
                    rb2d.FixedRotation = rigidBody2DComponent["FixedRotation"].as<bool>();
                }

                auto boxCollider2DComponent = entity["BoxCollider2DComponent"];
                if (boxCollider2DComponent)
                {
                    auto& bc2d = deserializedEntity.AddComponent<BoxCollider2DComponent>();
                    bc2d.Offset = boxCollider2DComponent["Offset"].as<glm::vec2>();
                    bc2d.Size = boxCollider2DComponent["Size"].as<glm::vec2>();
                    bc2d.Density = boxCollider2DComponent["Density"].as<float>();
                    bc2d.Friction = boxCollider2DComponent["Friction"].as<float>();
                    bc2d.Restitution = boxCollider2DComponent["Restitution"].as<float>();
                    bc2d.RestitutionThreshold = boxCollider2DComponent["RestitutionThreshold"].as<float>();
                }

                auto circleCollider2DComponent = entity["CircleCollider2DComponent"];
                if (circleCollider2DComponent)
                {
                    auto& cc2d = deserializedEntity.AddComponent<CircleCollider2DComponent>();
                    cc2d.Offset = circleCollider2DComponent["Offset"].as<glm::vec2>();
                    cc2d.Radius = circleCollider2DComponent["Radius"].as<float>();
                    cc2d.Density = circleCollider2DComponent["Density"].as<float>();
                    cc2d.Friction = circleCollider2DComponent["Friction"].as<float>();
                    cc2d.Restitution = circleCollider2DComponent["Restitution"].as<float>();
                    cc2d.RestitutionThreshold = circleCollider2DComponent["RestitutionThreshold"].as<float>();
                }
            }
        }

        return true;
    }

    bool SceneSerializer::DeserializeRuntime(const std::string& filepath)
    {
        return false;
    }

}
