/******************************************************************************
*	File		-	components.h
*	Author		-	Joey Pollack
*	Date		-	2022/05/24 (y/m/d)
*	Mod Date	-	2022/05/24 (y/m/d)
*	Description	-	ECS component declarations
******************************************************************************/

/*
    STEPS FOR CREATING A NEW COMPONENT:

    1) add the struct to components.h
    2) add the gui code to component_guis.h
    3) add the code to call the gui function to properties_view.cpp
    4) add the code to add a new instance of the component to selected entity in properties_view's ADD_COMPONENT popup (using PRESENT_COMP_CHOICE)
    5) add serialize/deserialize code to entity.cpp
    
    Situational:
    6) add code (system) to use the component in World
    7) add a foreign class to the scripting system to access/manipulate the component
*/

#ifndef LUNARIUM_COMPONENTS_H_
#define LUNARIUM_COMPONENTS_H_

#include <core/common_defs.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>

#include <renderer/orthographic_camera.h>

#include <string>
#include <vector>


namespace lunarium
{
    enum class ComponentType
    {
        TagComponent,
        TransformComponent,
        VelocityComponent,
        CameraComponent,
        BlockOutComponent,
        SpriteRendererComponent,
        ScriptComponent,
        RigidBodyComponent,
        CollisionComponent,

        UUIDComponent,
        ParentEntityComponent,
        ChildrenComponent,

    };

    struct TagComponent
    {
        std::string Info;

        TagComponent(std::string _info = "")
        {
            Info.reserve(256);
            Info.insert(0, _info);
        }

        TagComponent(const TagComponent&) = default;
    };

    struct TransformComponent
    {
        glm::vec3 Position = { 0.0f, 0.0f, 0.0f };
        glm::vec3 Rotation = { 0.0f, 0.0f, 0.0f };
        glm::vec3 Scale = { 1.0f, 1.0f, 1.0f };

        TransformComponent(glm::vec3 p = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 r = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 s = glm::vec3(1.0f, 1.0f, 1.0f))
            : Position(p), Rotation(r), Scale(s)
        {
            
        }

        TransformComponent(const TransformComponent&) = default;

        glm::mat4 GetTransform() 
        { 
            // Quaternion code taken from Hazel engine
            // https://github.com/TheCherno/Hazel/blob/dev/Hazel/src/Hazel/Scene/Components.h
            return (glm::translate(glm::mat4(1.0f), Position) * glm::toMat4(glm::quat(Rotation)) * glm::scale(glm::mat4(1.0f), Scale )); 
        }
    };

    // TODO: Deprecate in favor of using the physics system for all motion
    //       Or perhaps manually control the Transform component
    struct VelocityComponent
    {
        glm::vec3 Velocity = { 0.0f, 0.0f, 0.0f};
        
        VelocityComponent() = default;
        VelocityComponent(const VelocityComponent&) = default;
    };

    struct CameraComponent
    {
        OrthographicCamera Camera;

        CameraComponent() = default;
        CameraComponent(const CameraComponent&) = default;
    };

    struct BlockOutComponent
    {
        glm::vec4 Color;
        glm::vec2 Size;
        int RenderLayer;

        BlockOutComponent() = default;
        BlockOutComponent(const BlockOutComponent&) = default;
    };

    struct SpriteRendererComponent
    {
        LUUID ImageID;
        int RenderLayer;
        glm::vec2 FrameSize;
        glm::vec2 FramePos;
        Color TintColor;

        SpriteRendererComponent() = default;
        SpriteRendererComponent(const SpriteRendererComponent&) = default;
    };

    struct ScriptComponent
    {
        LUUID ScriptID;
        
        ScriptComponent() = default;
        ScriptComponent(const ScriptComponent&) = default;
    };

    struct RigidBodyComponent
    {
        b2Body* pBody = nullptr;
        bool IsStatic = false;
        float Mass = 1.0f;
        b2BodyDef Defintion;
        
        RigidBodyComponent() = default;
        RigidBodyComponent(const RigidBodyComponent&) = default;
    };

    struct CollisionComponent
    {
        b2Shape* pShape = nullptr;
        enum class ShapeType { Circle, Box };
        ShapeType Type;
        float Radius = 1.0f;    // only applies to circles
        float Width = 1.0f;     // only applies to boxes
        float Height = 1.0f;    // only applies to boxes

        bool IsSensor = false;  // Does not generate collision responses (a trigger)
        int CollisionGroup = 0; // Only objects in the same Collision Group can collide
        float Density = 1.0f;
        float Friction = 0.25f;
        float Restitution = 0.25f;

        
        CollisionComponent() = default;
        CollisionComponent(const CollisionComponent&) = default;
    };

    /////////////////////////////////////////////////////////////////////
    //				UTILITY COMPONENTS
    /////////////////////////////////////////////////////////////////////
    // These components are for internal use only. They do not show up in the editor.

    struct UUIDComponent
    {
        LUUID UUID;

        UUIDComponent() = default;
        UUIDComponent(const UUIDComponent&) = default;
    };

    struct ParentEntityComponent
    {
        LUUID Parent;

        ParentEntityComponent() = default;
        ParentEntityComponent(const ParentEntityComponent&) = default;
    };

    struct ChildrenComponent
    {
        std::vector<LUUID> Children;
        
        ChildrenComponent() = default;
        ChildrenComponent(const ChildrenComponent&) = default;
    };
}

#endif  // LUNARIUM_COMPONENTS_H_