#include <pch.hpp>

#include "core.hpp"
#include <scene/scene_serializer.hpp>
#include "scene/components.hpp"
#include "scene/entity.hpp"

#include <fstream>
#include <yaml-cpp/emittermanip.h>
#include <yaml-cpp/yaml.h>

namespace YAML {

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

            static bool decode(const Node& node, glm::vec3& rhs)
            {
                if (!node.IsSequence() || node.size() != 3)
                    return false;

                rhs.x = node[0].as<float>();
                rhs.y = node[1].as<float>();
                rhs.z = node[2].as<float>();
                return true;
            }
        };

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

            static bool decode(const Node& node, glm::vec4& rhs)
            {
                if (!node.IsSequence() || node.size() != 4)
                    return false;

                rhs.x = node[0].as<float>();
                rhs.y = node[1].as<float>();
                rhs.z = node[2].as<float>();
                rhs.w = node[3].as<float>();
                return true;
            }
        };
}

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

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

    SceneSerializer::SceneSerializer(const Ref<Scene>& scene)
        : context(scene)
    {

    }

    static void SerializeEntity(YAML::Emitter& out, Entity entity)
    {
        out << YAML::BeginMap;
        out << YAML::Key << "Entity" << YAML::Value << "412741205"; // Needs random

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

            auto& tag = entity.GetComponents<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& tc = entity.GetComponents<TransformComponent>();
            out << YAML::Key << "Translation" << YAML::Value << tc.translation;
            out << YAML::Key << "Rotation" << YAML::Value << tc.rotation;
            out << YAML::Key << "Scale" << YAML::Value << tc.scale;

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

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

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

            out << YAML::Key << "Camera" << YAML::Value;
            out << YAML::BeginMap; // Camera
            out << YAML::Key << "ProjectionType" << YAML::Value << (int)camera.GetProjectionType();
            out << YAML::Key << "PerspectiveFOV" << YAML::Value << camera.GetVerticalFov();
            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::EndMap; // Camera

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

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

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

            auto& spriteRendererComponent = entity.GetComponents<SpriteRendererComponent>();
            out << YAML::Key << "Color" << YAML::Value << spriteRendererComponent.color;

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

        out << YAML::EndMap;
    }

    void SceneSerializer::Serialize(const std::string& file_path)
    {
        YAML::Emitter out;

        out << YAML::BeginMap;
        out << YAML::Key << "Scene" << YAML::Value << "N/A";
        out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq;
        for (auto entity_id : context->registry.view<entt::entity>()) {
            Entity entity = { entity_id, context.get()};
            if (!entity)
                return;

            SerializeEntity(out, entity);
        }
        out << YAML::EndSeq;
        out << YAML::EndMap;

        std::ofstream fout(file_path);
        fout << out.c_str();
    }
    void SceneSerializer::SerializeRuntime(const std::string& file_path)
    {
        OE_CORE_ASSERT(false, "Not implemented yet");
    }

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

		YAML::Node data = YAML::Load(strStream.str());
		if (!data["Scene"])
			return false;

		std::string sceneName = data["Scene"].as<std::string>();
		OE_CORE_TRACE("Deserializing scene '{0}'", sceneName);

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

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

				OE_CORE_TRACE("Deserialized entity with ID = {0}, name = {1}", uuid, name);

				Entity deserializedEntity = context->CreateEntity(name);

				auto transformComponent = entity["TransformComponent"];
				if (transformComponent)
				{
					auto& tc = deserializedEntity.AddComponents<TransformComponent>();
					tc.translation = transformComponent["Translation"].as<glm::vec3>();
					tc.rotation = transformComponent["Rotation"].as<glm::vec3>();
					tc.scale = transformComponent["Scale"].as<glm::vec3>();
				}

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

					auto camera_props = cameraComponent["Camera"];
					cc.camera.SetProjectionType((SceneCamera::ProjectionType)camera_props["ProjectionType"].as<int>());

					cc.camera.SetVerticalFov(camera_props["PerspectiveFOV"].as<float>());
					cc.camera.SetPerspectiveNearClip(camera_props["PerspectiveNear"].as<float>());
					cc.camera.SetPerspectiveFarClip(camera_props["PerspectiveFar"].as<float>());

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

					cc.primary = cameraComponent["Primary"].as<bool>();
					cc.fixed_aspect_ratio = cameraComponent["FixedAspectRatio"].as<bool>();
				}

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

		return true;
    }
    bool SceneSerializer::DeserializeRuntime(const std::string& file_path)
    {
        OE_CORE_ASSERT(false, "Not implemented yet");

        return false;
    }
}
