#include "Object.h"
#include "../Components/Component.h"
#include "../Components/SpriteComponent.h"
#include "../Components/CameraComponent.h"
#include "../Components/LightComponent.h"
#include "../Components/TilemapComponent.h"
#include "../Components/TextComponent.h"
#include "../Components/ScriptComponent.h"
#include "../Components/ParticleComponent.h"
#include "../Components/AnimationComponent.h"
#include "../Components/AudioPlayerComponent.h"


#include "../core/utils/Logging.h"
#include "../utils/UID.h"

#include <algorithm>

Object::Object(const std::string &name)
    : name(name), localPosition(0.0f, 0.0f), localRotationDeg(0.0f), uid(), visable(true) {}

Object::~Object() {}

void Object::SetParent(Object *newParent)
{
    if (parent)
        parent->RemoveChild(this);
    parent = newParent;
}

void Object::AddChild(std::shared_ptr<Object> child)
{
    child->SetParent(this);
    children.push_back(child);
}

void Object::RemoveChild(Object *child)
{
    children.erase(std::remove_if(children.begin(), children.end(),
                                  [child](const std::shared_ptr<Object> &obj)
                                  {
                                      return obj.get() == child;
                                  }),
                   children.end());
}

glm::vec2 Object::GetLocalPosition() const
{
    return localPosition;
}

void Object::SetLocalPosition(glm::vec2 pos)
{
    localPosition = pos;
}

glm::vec2 Object::GetWorldPosition() const
{
    if (parent)
    {
        float parentRotation = glm::radians(parent->GetWorldRotation());
        glm::vec2 rotated = glm::rotate(localPosition, parentRotation);
        return parent->GetWorldPosition() + rotated;
    }
    return localPosition;
}

float Object::GetLocalRotation() const
{
    return localRotationDeg;
}

void Object::SetLocalRotation(float deg)
{
    localRotationDeg = deg;
}

float Object::GetWorldRotation() const
{
    if (parent)
        return parent->GetWorldRotation() + localRotationDeg;
    return localRotationDeg;
}

bool Object::GetVisable() const
{
    return visable;
}

void Object::SetVisable(bool state)
{
    visable = state;
}

const std::string &Object::GetName() const { return name; }
void Object::SetName(const std::string &n) { name = n; }
std::vector<std::shared_ptr<Object>> &Object::GetChildren() { return children; }
Object *Object::GetParent() const { return parent; }

void Object::Save(YAML::Emitter &out) const
{
    Logger::LogVerbose("[LoadScene] Saving Object: [%s, %d]", name.c_str(), uid.id);

    out << YAML::BeginMap;
    out << YAML::Key << "name" << YAML::Value << name;
    out << YAML::Key << "uid" << YAML::Value << uid.uuid;
    out << YAML::Key << "id" << YAML::Value << uid.id;
    out << YAML::Key << "position" << YAML::Flow << YAML::BeginSeq << localPosition.x << localPosition.y << YAML::EndSeq;
    out << YAML::Key << "rotation" << YAML::Value << localRotationDeg;
    out << YAML::Key << "layer" << YAML::Value << layer;
    out << YAML::Key << "visable" << YAML::Value << visable;

    out << YAML::Key << "components" << YAML::Value << YAML::BeginSeq;
    for (const auto &comp : components)
    {
        Logger::LogVerbose("[LoadScene] Saving Component: %s", comp->GetName().c_str());
        comp->Save(out);
    }
    out << YAML::EndSeq;

    out << YAML::Key << "children" << YAML::Value << YAML::BeginSeq;
    for (const auto &child : children)
        child->Save(out);
    out << YAML::EndSeq;

    out << YAML::EndMap;
}

void Object::Load(const YAML::Node &node)
{
    name = node["name"].as<std::string>();
    uid.uuid = node["uid"] ? node["uid"].as<std::string>() : GenerateUUID();
    uid.id = node["id"] ? node["id"].as<int>() : 0;

    auto pos = node["position"];
    if (pos && pos.IsSequence() && pos.size() == 2)
        localPosition = {pos[0].as<float>(), pos[1].as<float>()};

    localRotationDeg = node["rotation"] ? node["rotation"].as<float>() : 0.0f;
    layer = node["layer"] ? node["layer"].as<int>() : 0;
    visable = node["visable"] ? node["visable"].as<bool>() : true;

    Logger::LogVerbose("[LoadScene] Loading Object: ['%s', %d]", name.c_str(), uid.id);

    components.clear();
    if (node["components"])
    {
        for (const auto &compNode : node["components"])
        {
            try
            {
                std::string type = compNode["type"].as<std::string>();

                if (type == "SpriteComponent")
                    AddComponent<SpriteComponent>()->Load(compNode);
                else if (type == "CameraComponent")
                    AddComponent<CameraComponent>()->Load(compNode);
                else if (type == "LightComponent")
                    AddComponent<LightComponent>()->Load(compNode);
                else if (type == "TilemapComponent")
                    AddComponent<TilemapComponent>()->Load(compNode);
                else if (type == "TextComponent")
                    AddComponent<TextComponent>()->Load(compNode);
                else if (type == "ScriptComponent")
                    AddComponent<ScriptComponent>()->Load(compNode);
                else if (type == "ParticleComponent")
                    AddComponent<ParticleComponent>()->Load(compNode);
                else if (type == "AnimationComponent")
                    AddComponent<AnimationComponent>()->Load(compNode);
                else if (type == "AudioPlayerComponent")
                    AddComponent<AudioPlayerComponent>()->Load(compNode);
                else
                {
                    Logger::LogError("Invalid Componenet type '%s' found in component '%s'(%d)", type.c_str(), name.c_str(), uid.id);
                }
            }
            catch (const std::exception &e)
            {
                Logger::LogError("Failed to load '%s' (ID: %d). %s", name.c_str(), uid.id, e.what());
            }
        }
    }

    children.clear();
    if (node["children"])
    {
        for (const auto &childNode : node["children"])
        {
            auto child = std::make_shared<Object>("Child");
            child->Load(childNode);
            AddChild(child);
        }
    }
}