#include "Transformable.hpp"

#include "IdGenerator.hpp"
#include "MathF.hpp"
#include "BehaviourScript.hpp"
#include "Layer.hpp"
#include "Debug.hpp"

namespace TSE
{
    Transformable::Transformable()
    : position(0, 0, 0), scale(1, 1, 1), rotation(), name("")
    {
        id = GenerateRandomUUID();
        objectEntries[id] = this;
    }

    Transformable::Transformable(uuids::uuid _id)
    : id(_id), position(0, 0, 0), scale(1, 1, 1), rotation(), name("")
    {
        objectEntries[_id] = this;
    }

    Transformable::Transformable(const string &name)
    : position(0, 0, 0), scale(1, 1, 1), rotation(), name(name)
    {
        id = GenerateRandomUUID();
        objectEntries[id] = this;
    }

    Transformable::Transformable(const string &name, uuids::uuid _id)
    : id(_id), position(0, 0, 0), scale(1, 1, 1), rotation(), name(name) 
    {
        objectEntries[_id] = this;
    }

    Transformable::~Transformable()
    {
    }

    Vector3 Transformable::forward() const
    {
        return Vector3::Normalize(GetGlobalMatrix() * Vector3::forward - GetGlobalMatrix() * Vector3::zero);
    }

    Vector3 Transformable::right() const
    {
        return Vector3::Normalize(GetGlobalMatrix() * Vector3::right - GetGlobalMatrix() * Vector3::zero);
    }

    Vector3 Transformable::up() const
    {
        return Vector3::Normalize(GetGlobalMatrix() * Vector3::up - GetGlobalMatrix() * Vector3::zero);
    }

    Vector3 Transformable::GetPosition() const
    {
        return position;
    }

    void Transformable::SetPosition(const Vector3 &pos)
    {
        position = pos;
    }

    Quaternion Transformable::GetRotation() const
    {
        return rotation;
    }

    void Transformable::SetRotation(const Quaternion &rot)
    {
        rotation = rot;
    }

    Vector3 Transformable::GetScale() const
    {
        return scale;
    }

    void Transformable::SetScale(const Vector3 &s)
    {
        scale = s;
    }

    string Transformable::GetName() const
    {
        return name;
    }

    uuids::uuid Transformable::GetId() const
    {
        return id;
    }

    Vector3 Transformable::GetEuler()
    {
        return rotation.ToEulerAngles();
    }

    void Transformable::SetEuler(const Vector3 &euler)
    {
        rotation = Quaternion::FromEulerAngles(euler);
    }

    int Transformable::GetComponentCount()
    {
        return components.size();
    }

    Matrix4x4 Transformable::GetLocalMatrix() const
    {
        return Matrix4x4::ToTranslationMatrix(position) * Matrix4x4::ToRotationMatrix(rotation) * Matrix4x4::ToScaleMatrix(scale);
    }

    Matrix4x4 Transformable::GetGlobalMatrix() const
    {
        if (parent != nullptr)
            return parent->GetGlobalMatrix() * GetLocalMatrix();
        return GetLocalMatrix();
    }

    Vector3 Transformable::GetGlobalPosition() const
    {
        return LocalToGlobalPosition(Vector3::zero);
    }

    Vector3 Transformable::GlobalToLocalPosition(const Vector3& global) const
    {
        Matrix4x4 globalMatrix = GetGlobalMatrix();
        if(globalMatrix.IsAffine())
        {
            globalMatrix.InvertAffine();
        }
        else
        {
            globalMatrix.Invert();
        }
        return globalMatrix * global;
    }

    Vector3 Transformable::LocalToGlobalPosition(const Vector3& local) const
    {
        return GetGlobalMatrix() * local;
    }

    void Transformable::LookAt_2D(const Vector3& globalTarget)
    {
        Vector3 to = globalTarget - GetGlobalPosition();
        Vector3 toXY = {to.x, to.y, 0.0f};
        if(Vector3::Dot(toXY, toXY) < TSE_EPSILON) return;
        toXY.Normalize();

        Vector3 fwdWorld = rotation.Rotate(Vector3::up);
        Vector3 fwdXY = Vector3(fwdWorld.x, fwdWorld.y, 0);
        fwdXY.Normalize();

        float cosA = std::clamp(Vector3::Dot(fwdXY, toXY), -1.0f, 1.0f);
        float sinA = fwdXY.x * toXY.y - fwdXY.y * toXY.x;
        float angle = atan2(sinA, cosA);

        Quaternion twist = Quaternion::FromAngleAxis(angle, Vector3::forward);
        rotation = twist * rotation;
    }

    void Transformable::SetParent(Transformable *p)
    {
        Transformable* t(this);
        if (parent != nullptr && parent != p) {

            auto it = parent->children.begin();
            for (int i = 0; i < parent->children.size(); i++)
            {
                if(**it == *t)
                {
                    parent->children.erase(it);
                    break;
                }
                it++;
            }
        }
        if (p != nullptr && p != parent) {
            p->children.push_back(t);
        }
        parent = p;
    }

    Transformable *Transformable::GetParent() const
    {
        return parent;
    }

    const std::vector<Transformable *> &Transformable::GetChildren() const
    {
        return children;
    }

    bool Transformable::IsMyChild(Transformable *other)
    {
        for (auto child : children)
        {
            if(child->id == other->id)
            {
                return true;
            }
            else if(child->IsMyChild(other))
            {
                return true;
            }
        }
        return false;
    }

    void Transformable::MoveUp(Layer *l)
    {
        if(l != nullptr)
        {
            auto it = l->GetAllObjects().begin();
            for(int i = 0; i < l->GetAllObjects().size(); i++)
            {
                if((*it)->id == id)
                {
                    if(i == 0) return;
                    std::swap(l->GetAllObjects()[i - 1], l->GetAllObjects()[i]);
                }
                it++;
            }
            return;
        }
        auto it = parent->children.begin();
        for(int i = 0; i < parent->children.size(); i++)
        {
            if((*it)->id == id)
            {
                if(i == 0) return;
                std::swap(parent->children[i - 1], parent->children[i]);
            }
            it++;
        }
    }

    void Transformable::MoveDown(Layer *l)
    {
        if(l != nullptr)
        {
            auto it = l->GetAllObjects().begin();
            for(int i = 0; i < l->GetAllObjects().size(); i++)
            {
                if((*it)->id == id)
                {
                    if(i == l->GetAllObjects().size() - 1) return;
                    std::swap(l->GetAllObjects()[i + 1], l->GetAllObjects()[i]);
                }
                it++;
            }
            return;
        }
        auto it = parent->children.begin();
        for(int i = 0; i < parent->children.size(); i++)
        {
            if((*it)->id == id)
            {
                if(i == parent->children.size() - 1) return;
                std::swap(parent->children[i + 1], parent->children[i]);
            }
            it++;
        }
    }

    bool Transformable::IsEnabled() const
    {
        return _enabled;
    }

    void Transformable::SetEnabled(bool v)
    {
        if (_enabled != v) {
            _enabled = v;
            if (_enabled) OnEnable();
            else OnDisable();
        }
    }

    BehaviourScript *Transformable::AddBehaviourScript(BehaviourScript *script)
    {
        if (!script) return nullptr;
        const std::string key = typeid(*script).name();
        components.emplace(key, script);
        script->SetBaseObject(this);
        script->Start();
        return script;
    }

    BehaviourScript *Transformable::GetBehaviourScript(const string &name) const
    {
        auto it = components.find(name);
        return (it != components.end()) ? it->second : nullptr;
    }

    BehaviourScript *Transformable::GetBehaviourScriptAt(const int i) const
    {
        auto it = components.begin();
        for (int j = 0; j < i; j++)
        {
            it++;
            
        }
        
        return (*it).second;
    }

    std::vector<BehaviourScript *> Transformable::GetAllBehaviourScripts(const string &name) const
    {
        std::vector<BehaviourScript *> res;

        auto it = components.begin();
        while(it != components.end())
        {
            if((*it).first == name)
            {
                res.push_back((*it).second);
            }
        }
        
        return res;
    }

    void Transformable::RemoveBehaviourScript(BehaviourScript *script)
    {
        for (auto it = components.begin(); it != components.end(); ++it) {
            if (it->second == script) {
                components.erase(it);
                break;
            }
        }
    }

    bool Transformable::HasBehaviourScript(const string &name) const
    {
        auto it = components.find(name);
        return it != components.end();
    }

    void Transformable::Update()
    {
        for (auto& [name, script] : components) {
            if (script && script->IsEnabled()) script->OnUpdate();
        }
        for (auto child : children)
        {
            child->Update();
        }
    }

    void Transformable::Delete(Transformable *t)
    {
        objectEntries.erase(t->id);
        delete t;
    }

    void Transformable::Delete(const uuids::uuid id)
    {
        if(!Exists(id)) return;
        Transformable* t = objectEntries.at(id);
        objectEntries.erase(id);
        delete t;
    }

    void Transformable::HardDelete(Transformable *t, bool onlyThis)
    {
        //deleting children
        if(!onlyThis)
        {
            for(auto child : t->children)
            {
                HardDelete(child, onlyThis);
            }
            t->children.clear();
        }
        
        //deleting atteched scripts
        for (auto& [_, script] : t->components)
        {
            delete script;
        }
        t->components.clear();

        //deleting self
        Delete(t);
    }

    void Transformable::HardDelete(const uuids::uuid id, bool onlyThis)
    {
        if(!Exists(id)) return;
        Transformable* t = objectEntries.at(id);
        //deleting children
        if(!onlyThis)
            for(auto child : t->children)
            {
                HardDelete(child);
            }
        
        //deleting atteched scripts
        for (auto& [_, script] : t->components)
            delete script;

        //deleting self
        Delete(t);
    }

    void Transformable::DeleteAll()
    {
        auto it = objectEntries.begin();
        for(int i = 0; i < objectEntries.size(); i++)
        {
            delete it->second;
            it++;
        }
        objectEntries.clear();
    }

    bool Transformable::Exists(const uuids::uuid id)
    {
        auto it = objectEntries.find(id);
        return it != objectEntries.end();
    }

    bool Transformable::operator==(const Transformable &other) const
    {
        return other.id == id;
    }

    bool Transformable::operator!=(const Transformable &other) const
    {
        return other.id != id;
    }

    void Transformable::OnEnable()
    {
        for (auto& [_, script] : components)
            if (script && script->IsEnabled()) script->OnEnable();
    }

    void Transformable::OnDisable()
    {
        for (auto& [_, script] : components)
            if (script && script->IsEnabled()) script->OnDisable();
    }

    int Transformable::GetTansformableCount()
    {
        return objectEntries.size();
    }

    Transformable *Transformable::GetTansformableAt(int i)
    {
        int x = 0;
        for(auto obj : objectEntries)
        {
            if(i == x++)
            {
                return obj.second;
            }
        }
        return nullptr;
    }

    Transformable *Transformable::Find(string name)
    {
        for(auto obj : objectEntries)
        {
            if(obj.second->name == name)
            {
                return obj.second;
            }
        }
        return nullptr;
    }

    Transformable *Transformable::Find(uuids::uuid id)
    {
        for(auto obj : objectEntries)
        {
            if(obj.second->id == id)
            {
                return obj.second;
            }
        }
        return nullptr;
    }

} // namespace TSE
