/******************************************************************************
*	File		-	entity.cpp
*	Author		-	Joey Pollack
*	Date		-	2022/05/23 (y/m/d)
*	Mod Date	-	2022/05/23 (y/m/d)
*	Description	-	Provides functionality to work with world entities
******************************************************************************/

#include "entity.h"
#include "world.h"
#include "components.h"
#include <utils/logger.h>

namespace lunarium
{
    Entity::Entity(World& w)
        : mHandle(entt::null), mWorld(w), mParent(0), mParentSet(false)
    {
        Init();
    }

    // Entity::Entity(World& w, entt::entity handle)
    //     : mHandle(handle), mWorld(w)
    // {
    //     Init();
    // }

    void Entity::Init()
    {
        auto prev = mHandle;
        mHandle = mWorld.GetEntityRegistry()->create(mHandle);

        if (prev != mHandle && prev != entt::null)
        {
            Logger::Warn(LogCategory::GAME_SYSTEM, "Requested entity handle was not used");
        }

        AddComponent<UUIDComponent>(UUID::GetNewID());
    }

    LUUID Entity::GetUUID()
    {
        return GetComponent<UUIDComponent>().UUID;
        //   return mUUID;
    }
    
    entt::entity Entity::GetEnttHandle() const
    {
        return mHandle;
    }

    std::string Entity::GetName() const
    {
        return mName;
    }

    void Entity::SetName(std::string name)
    {
        mName = name;
    }

    bool Entity::HasParent()
    {
        return HasComponent<ParentEntityComponent>();
    }

    LUUID Entity::GetParent()
    {
        if (HasParent())
        {
            return GetComponent<ParentEntityComponent>().Parent;
        }

        return 0;
    }
    
    bool Entity::HasChildren()
    {
        return HasComponent<ChildrenComponent>();
        //return mChildren.size() > 0;
    }

    
    void Entity::AddChild(LUUID child)
    {
        Entity* e = mWorld.GetEntity(child);
        e->AddComponent<ParentEntityComponent>(GetUUID());

        if (!HasComponent<ChildrenComponent>())
        {
            AddComponent<ChildrenComponent>();
        }

        ChildrenComponent& ccomp = GetComponent<ChildrenComponent>();
        ccomp.Children.push_back(child);

        //mChildren.push_back(child);
    }

    
    void Entity::RemoveChild(LUUID child)
    {
        Entity* e = mWorld.GetEntity(child);
        e->ClearParent();

        if (!HasChildren())
        {
            Logger::Error(LogCategory::GAME_SYSTEM, "RemoveChild called on entity that does not have children");
            return;
        }
        
        ChildrenComponent& ccomp = GetComponent<ChildrenComponent>();
        for (auto iter = ccomp.Children.begin(); iter != ccomp.Children.end(); iter++)
        {
            if ((*iter) == child)
            {
                ccomp.Children.erase(iter);
                break;
            }
        }

        if (ccomp.Children.size() < 1)
        {
            RemoveComponent<ChildrenComponent>();
        }
    }

    
    std::vector<LUUID>& Entity::GetChildren()
    {
        if (!HasChildren())
        {
            Logger::Error(LogCategory::GAME_SYSTEM, "GetChildren called on entity that does not have children");
            return std::move(std::vector<LUUID>());
        }
        
        ChildrenComponent& ccomp = GetComponent<ChildrenComponent>();
        return ccomp.Children;
    }

    // bool Entity::HasParent() const
    // {
    //     return mParentSet;
    // }

    // LUUID Entity::GetParent() const
    // {
    //     return mParent;
    // }

    // void Entity::SetParent(LUUID parent)
    // {
    //     mParent = parent;
    // }
    
    void Entity::ClearParent()
    {
        mParentSet = false;
        RemoveComponent<ParentEntityComponent>();
    }


    /////////////////////////////////////////////////////////////////////
    //				SERIALIZING
    /////////////////////////////////////////////////////////////////////
    OpRes Entity::Serialize(nlohmann::ordered_json& node)
    {
        #if !BUILD_NO_EDITOR // Only does this when this is an editor build

       // node["UUID"] = mUUID;
        node["name"] = mName;

        auto& components = node["components"];

        if (HasComponent<UUIDComponent>())
        {
            nlohmann::ordered_json uuid;
            uuid["type_name"] = "UUIDComponent";
            uuid["UUID"] = (u64)GetComponent<UUIDComponent>().UUID;
            components.emplace_back(uuid);
        }

        if (HasComponent<TagComponent>())
        {
            nlohmann::ordered_json tag;
            tag["type_name"] = "TagComponent";
            tag["info"] = GetComponent<TagComponent>().Info;
            components.emplace_back(tag);
        }

        if (HasComponent<TransformComponent>())
        {
            nlohmann::ordered_json transform;
            TransformComponent& comp = GetComponent<TransformComponent>();
            transform["type_name"] = "TransformComponent";

            auto& pos =  transform["position"];
            pos["x"] = comp.Position.x;
            pos["y"] = comp.Position.y;
            pos["z"] = comp.Position.z;

            auto& rot =  transform["rotation"];
            rot["x"] = comp.Rotation.x;
            rot["y"] = comp.Rotation.y;
            rot["z"] = comp.Rotation.z;

            auto& scale =  transform["scale"];
            scale["x"] = comp.Scale.x;
            scale["y"] = comp.Scale.y;
            scale["z"] = comp.Scale.z;

            components.emplace_back(transform);
        }

        if (HasComponent<VelocityComponent>())
        {
            nlohmann::ordered_json velocity;
            VelocityComponent& comp = GetComponent<VelocityComponent>();
            velocity["type_name"] = "VelocityComponent";

            auto& pos =  velocity["velocity"];
            pos["x"] = comp.Velocity.x;
            pos["y"] = comp.Velocity.y;
            pos["z"] = comp.Velocity.z;

            components.emplace_back(velocity);
        }

        if (HasComponent<CameraComponent>())
        {
            nlohmann::ordered_json camera;
            CameraComponent& comp = GetComponent<CameraComponent>();
            camera["type_name"] = "CameraComponent";

            auto& pos =  camera["position"];
            pos["x"] = comp.Camera.GetPosition().X;
            pos["y"] = comp.Camera.GetPosition().Y;
            pos["z"] = 0.0f;

            auto& rot = camera["rotation"];
            rot["degrees"] = comp.Camera.GetRotation();

            auto& vpsize = camera["viewport_size"];
            vpsize["width"] = comp.Camera.GetViewportSize().Width;
            vpsize["height"] = comp.Camera.GetViewportSize().Height;

            components.emplace_back(camera);
        }

        if (HasComponent<BlockOutComponent>())
        {
            nlohmann::ordered_json blockout;
            BlockOutComponent& comp = GetComponent<BlockOutComponent>();
            blockout["type_name"] = "BlockOutComponent";

            auto& color = blockout["color"];
            color["r"] = comp.Color.x;
            color["g"] = comp.Color.y;
            color["b"] = comp.Color.z;
            color["a"] = comp.Color.w;

            auto& size = blockout["size"];
            size["width"] = comp.Size.x;
            size["height"] = comp.Size.y;

            blockout["render_layer"] = comp.RenderLayer;

            components.emplace_back(blockout);
        }

        if (HasComponent<SpriteRendererComponent>())
        {
            nlohmann::ordered_json sprite;
            SpriteRendererComponent& comp = GetComponent<SpriteRendererComponent>();
            sprite["type_name"] = "SpriteRendererComponent";

            sprite["image_id"] = comp.ImageID;

            sprite["render_layer"] = comp.RenderLayer;

            auto& frame_size = sprite["frame_size"];
            frame_size["width"] = comp.FrameSize.x;
            frame_size["height"] = comp.FrameSize.y;

            auto& frame_pos = sprite["frame_pos"];
            frame_pos["x"] = comp.FramePos.x;
            frame_pos["y"] = comp.FramePos.y;

            auto& color = sprite["color"];
            color["r"] = comp.TintColor.R;
            color["g"] = comp.TintColor.G;
            color["b"] = comp.TintColor.B;
            color["a"] = comp.TintColor.A;

            components.emplace_back(sprite);
        }

        if (HasComponent<ScriptComponent>())
        {
            
            nlohmann::ordered_json script;
            ScriptComponent& comp = GetComponent<ScriptComponent>();
            script["type_name"] = "ScriptComponent";

            script["script_id"] = comp.ScriptID;
            components.emplace_back(script);
        }

        if (HasComponent<RigidBodyComponent>())
        {
            nlohmann::ordered_json rigid_body;
            RigidBodyComponent& comp = GetComponent<RigidBodyComponent>();
            rigid_body["type_name"] = "RigidBodyComponent";

            rigid_body["is_static"] = comp.IsStatic;
            rigid_body["mass"] = comp.Mass;
            rigid_body["freeze_rotation"] = comp.Defintion.fixedRotation;
            rigid_body["is_high_speed"] = comp.Defintion.bullet;

            components.emplace_back(rigid_body);
        }

        if (HasComponent<CollisionComponent>())
        {
            nlohmann::ordered_json collision;
            CollisionComponent& comp = GetComponent<CollisionComponent>();
            collision["type_name"] = "CollisionComponent";

            collision["radius"] = comp.Radius;
            collision["width"] = comp.Width;
            collision["height"] = comp.Height;

            collision["shape_type"] = (int)comp.Type;
            collision["is_sensor"] = comp.IsSensor;
            collision["collision_group"] = comp.CollisionGroup;
            collision["density"] = comp.Density;
            collision["friction"] = comp.Friction;
            collision["restitution"] = comp.Restitution;
             
            components.emplace_back(collision);
        }

        if (HasComponent<ParentEntityComponent>())
        {
            nlohmann::ordered_json parent;
            ParentEntityComponent& comp = GetComponent<ParentEntityComponent>();
            parent["type_name"] = "ParentEntityComponent";
            parent["UUID"] = (u64)comp.Parent;
            components.emplace_back(parent);
        }

        if (HasComponent<ChildrenComponent>())
        {
            nlohmann::ordered_json children_node;
            ChildrenComponent& comp = GetComponent<ChildrenComponent>();
            children_node["type_name"] = "ChildrenComponent";

            auto& children = children_node["children"];
            for (int i = 0; i < comp.Children.size(); i++)
            {
                nlohmann::ordered_json child;
                child["UUID"] = comp.Children[i];
                children.emplace_back(child);
            }
            
            components.emplace_back(children_node);
        }

        // TODO: ADD CODE TO SERIALIZE ANY NEW COMPONENTS



        // Children
        // auto& children = node["children"];
        // for (int i = 0; i < mChildren.size(); i++)
        // {
        //     nlohmann::ordered_json child;
        //     child["UUID"] = mChildren[i];
        //     children.emplace_back(child);
        // }

        #endif
        return OpRes::OK();
    }

    OpRes Entity::Deserialize(nlohmann::ordered_json& node)
    {
        #if !BUILD_NO_EDITOR // Only does this when this is an editor build

        //mUUID = node["UUID"].get<u64>();
        mName = node["name"].get<std::string>();

        // TODO: Load components
        auto& components = node["components"];
        for (auto iter = components.begin(); iter != components.end(); iter++)
        {
            auto& comp = *iter;

            std::string comp_type_name = comp["type_name"].get<std::string>();

            if ("UUIDComponent" == comp_type_name)
            {
                LUUID uuid = comp["UUID"].get<u64>();
                //AddComponent<UUIDComponent>(uuid);
                if (HasComponent<UUIDComponent>())
                {
                    GetComponent<UUIDComponent>().UUID = uuid;
                }
            }

            if ("TagComponent" == comp_type_name)
            {
                std::string info = comp["info"].get<std::string>();
                AddComponent<TagComponent>(info);
            }

            if ("TransformComponent" == comp_type_name)
            {
                auto& pos = comp["position"];
                float x = pos["x"].get<f32>();
                float y = pos["y"].get<f32>();
                float z = pos["z"].get<f32>();
                glm::vec3 position(x, y, z);

                auto& rot = comp["rotation"];
                x = rot["x"].get<f32>();
                y = rot["y"].get<f32>();
                z = rot["z"].get<f32>();
                glm::vec3 rotation(x, y, z);

                auto& sc = comp["scale"];
                x = sc["x"].get<f32>();
                y = sc["y"].get<f32>();
                z = sc["z"].get<f32>();
                glm::vec3 scale(x, y, z);

                AddComponent<TransformComponent>(position, rotation, scale);
            }

            if ("VelocityComponent" == comp_type_name)
            {
                auto& vel = comp["velocity"];
                float x = vel["x"].get<f32>();
                float y = vel["y"].get<f32>();
                float z = vel["z"].get<f32>();
                glm::vec3 velocity(x, y, z);

                AddComponent<VelocityComponent>(velocity);
            }

            if ("CameraComponent" == comp_type_name)
            {
                auto& pos = comp["position"];
                float x = pos["x"].get<f32>();
                float y = pos["y"].get<f32>();
                float z = pos["z"].get<f32>();
                glm::vec3 position(x, y, z);

                auto& rot = comp["rotation"];
                float deg = rot["degrees"];

                auto& vps = comp["viewport_size"];
                float width = vps["width"].get<f32>();
                float height = vps["height"].get<f32>();

                OrthographicCamera cam({x, y}, {width, height});
                cam.SetRotation(deg);

                AddComponent<CameraComponent>(cam);
            }


            if ("BlockOutComponent" == comp_type_name)
            {
                auto& color = comp["color"];
                glm::vec4 Color(color["r"].get<f32>(), color["g"].get<f32>(), color["b"].get<f32>(), color["a"].get<f32>());
                
                auto& size = comp["size"];
                glm::vec2 Size(size["width"].get<f32>(), size["height"].get<f32>());

                int layer = comp["render_layer"].get<int>();

                AddComponent<BlockOutComponent>(Color, Size, layer);
            }

            if ("SpriteRendererComponent" == comp_type_name)
            {
                LUUID image = comp["image_id"];
                
                int layer = comp["render_layer"].get<int>();
                
                auto& frame_size = comp["frame_size"];
                glm::vec2 FrameSize(frame_size["width"].get<f32>(), frame_size["height"].get<f32>());

                auto& frame_pos = comp["frame_pos"];
                glm::vec2 FramePos(frame_pos["x"].get<f32>(), frame_pos["y"].get<f32>());

                auto& color = comp["color"];
                Color c(color["r"].get<f32>(), color["g"].get<f32>(), color["b"].get<f32>(), color["a"].get<f32>());

                AddComponent<SpriteRendererComponent>(image, layer, FrameSize, FramePos, c );
            }

            if ("ScriptComponent" == comp_type_name)
            {
                LUUID id = comp["script_id"];

                AddComponent<ScriptComponent>(id);
            }

            if ("RigidBodyComponent" ==  comp_type_name)
            {
                bool is_static = comp["is_static"].get<bool>();
                float mass = comp["mass"].get<float>();
                bool freeze_rot = comp["freeze_rotation"].get<bool>();
                bool is_high_speed = comp["is_high_speed"].get<bool>();

                b2BodyDef definition;
                definition.fixedRotation = freeze_rot;
                definition.bullet = is_high_speed;
                AddComponent<RigidBodyComponent>(nullptr, is_static, mass, definition);
            }

            if ("CollisionComponent" ==  comp_type_name)
            {
                CollisionComponent::ShapeType type = (CollisionComponent::ShapeType)comp["shape_type"].get<int>();
                float radius = comp["radius"].get<float>();
                float width = comp["width"].get<float>();
                float height = comp["height"].get<float>();
                bool is_sensor = comp["is_sensor"].get<bool>();
                int collision_group = comp["collision_group"].get<int>();
                float density = comp["density"].get<float>();
                float friction = comp["friction"].get<float>();
                float restitution = comp["restitution"].get<float>();

                AddComponent<CollisionComponent>(nullptr, type, radius, width, height, is_sensor, collision_group, density, friction, restitution);
            }


            if ("ParentEntityComponent" == comp_type_name)
            {
                LUUID parent = comp["UUID"].get<u64>();
                AddComponent<ParentEntityComponent>(parent);
            }

            if ("ChildrenComponent" == comp_type_name)
            {
                auto& children = comp["children"];
                std::vector<LUUID> children_vec;
                for (auto iter = children.begin(); iter != children.end(); iter++)
                {
                    auto& child = *iter;

                    LUUID ne = child["UUID"].get<u64>();
                    children_vec.push_back(ne);
                }

                AddComponent<ChildrenComponent>(children_vec);
            }

            // TODO: ADD CODE TO DESERIALIZE ANY NEW COMPONENTS
        }

        

        // TODO: Load children
        // auto& children = node["children"];
        // for (auto iter = children.begin(); iter != children.end(); iter++)
        // {
        //     auto& child = *iter;

        //     LUUID ne = child["UUID"].get<u64>();
        //     mChildren.push_back(ne);
        // }

        #endif
        return OpRes::OK();
    }
    
    bool Entity::IsValidNode(nlohmann::ordered_json& node)
    {
        // if (node["UUID"].is_null()) { return false; }
        // if (!node["UUID"].is_number()) { return false; }

        return true;
    }
    
    nlohmann::ordered_json Entity::AsJSON()
    {
        #if !BUILD_NO_EDITOR // Only does this when this is an editor build
        nlohmann::ordered_json node;

        Serialize(node).LogIfFailed(LogCategory::GAME_SYSTEM);

        return node;
        #endif

        return nlohmann::ordered_json();
    }
}