#pragma once
#include "../component.h"
#include <DirectXMath.h>

using namespace DirectX;

namespace ecs {

class PhysicsComponent : public Component {
public:
    /**
     * Builder for the PhysicsComponent class.
     * Use default values for velocity, acceleration, mass, bounding radius, and grounded state.
     */
    PhysicsComponent() {
        m_Velocity = XMVectorZero();
        m_Acceleration = XMVectorZero();
        m_PreviousPosition = XMVectorZero();
        m_Mass = 1.0f;
        m_BoundingRadius = 1.0f;
        m_IsGrounded = false;
        m_IsPhysicsEnabled = false;
        m_GravityEnabled = true;
    }

    ~PhysicsComponent() = default;

    /**
     * Initialize the component.
     * This method is called when the component is added to an entity.
     * It can be used to set up initial values or perform any necessary setup.
     */
    void Initialize() override {
        // Initialisation du composant physique
    }

    /**
     * Update the physics component.
     * This method is called every frame to update the physics state.
     *
     * This method is not the final update method
     * It will be called by the EntityManager's in the physics Thread.
     * This is due to the fact that the physics system is not updated every frame.
     * The physics thread is called at a fixed time step (50 FPS by default).
     * 
     * @param deltaTime The time elapsed since the last frame.
     */
    void Update(float deltaTime) override {
        if (!m_IsPhysicsEnabled) return;

        // Mise à jour de la vélocité basée sur l'accélération
        m_Velocity = m_Velocity + m_Acceleration * deltaTime;
        
        // Si la physique est activée et qu'une fonction de mise à jour de position est définie
        if (m_UpdatePositionCallback) {
            m_UpdatePositionCallback(m_Velocity * deltaTime);
        }
    }

    /**
     * Launch an object with a spring-like force.
     * This method calculates the initial velocity based on the angle, initial stretch, and spring constant.
     *
     * This method will be removed in the future
     *
     * @param alpha The launch angle in degrees.
     * @param initialStretch The initial stretch of the spring.
     * @param springConstant The spring constant.
     */
    void LaunchObject(float alpha, float initialStretch, float springConstant) {
        // Constants
        const float gravity = -9.81f;
    
        // Convert alpha from degrees to radians if needed
        float alphaRadians = alpha * (XM_PI / 180.0f);
    
        // Scale factors to make the physics simulation more visible
        float scaleFactor = 200.0f;  // Adjust this based on your world scale
    
        // Calculate initial velocity magnitude
        float velocityMagnitude = initialStretch * sqrtf(springConstant / m_Mass) * 
                                  sqrtf(1.0f - powf((m_Mass * gravity * sinf(alphaRadians) / 
                                  (springConstant * initialStretch)), 2.0f));
    
        // Apply scale factor
        velocityMagnitude *= scaleFactor;
    
        // Calculate velocity components
        XMVECTOR velocity = XMVectorSet(
            velocityMagnitude * cosf(alphaRadians),  // vx = v0 * cos(alpha)
            velocityMagnitude * sinf(alphaRadians),  // vy = v0 * sin(alpha)
            0.0f,                                   // z-component (0 for 2D trajectory)
            0.0f
        );
    
        // Apply velocity
        SetVelocity(velocity);
        
        // Enable physics and reset grounded state
        SetPhysicsEnabled(true);
        SetGrounded(false);
    }

    /**
     * Set the velocity of the object.
     * @param velocity 
     */
    void SetVelocity(XMVECTOR velocity) { m_Velocity = velocity; }
    /** 
     * Set the acceleration of the object.
     * @param acceleration 
     */
    void SetAcceleration(XMVECTOR acceleration) { m_Acceleration = acceleration; }
    /**
     * Set the mass of the object.
     * @param mass The mass to set.
     */
    void SetMass(float mass) { m_Mass = mass; }
    /**
     * Set the grounded state of the object.
     * @param isGrounded True if the object is grounded, false otherwise.
     */
    void SetGrounded(bool isGrounded) { m_IsGrounded = isGrounded; }
    /**
     * Enable or disable physics for the object.
     * @param enabled True to enable physics, false to disable.
     */
    void SetPhysicsEnabled(bool enabled) { m_IsPhysicsEnabled = enabled; }
    /**
     * Set the bounding radius of the object.
     * @param radius The bounding radius to set.
     */
    void SetBoundingRadius(float radius) { m_BoundingRadius = radius; }
    /**
     * Set the previous position of the object.
     * @param position The previous position to set.
     */
    void SetPreviousPosition(XMVECTOR position) { m_PreviousPosition = position; }
    /**
     * Enable or disable gravity for the object.
     * @param enabled True to enable gravity, false to disable.
     */
    void SetGravityEnabled(bool enabled) { m_GravityEnabled = enabled; }
    /**
     * Set the callback to update the position of the object.
     * This callback will be connected to the TransformComponent to update the position.
     * @param callback The callback function that takes an XMVECTOR as a parameter.
     */
    void SetUpdatePositionCallback(std::function<void(XMVECTOR)> callback) { m_UpdatePositionCallback = callback; }

    /**
     * Get the current velocity of the object.
     * @return The velocity as an XMVECTOR.
     */
    XMVECTOR GetVelocity() const { return m_Velocity; }
    /**
     * Get the current acceleration of the object.
     * @return The acceleration as an XMVECTOR.
     */
    XMVECTOR GetAcceleration() const { return m_Acceleration; }
    /**
     * Get the mass of the object.
     * @return The mass as a float.
     */
    float GetMass() const { return m_Mass; }
    /**
     * Get the grounded state of the object.
     * @return True if the object is grounded, false otherwise.
     */
    bool IsGrounded() const { return m_IsGrounded; }
    /**
     * Check if physics is enabled for the object.
     * @return True if physics is enabled, false otherwise.
     */
    bool IsPhysicsEnabled() const { return m_IsPhysicsEnabled; }
    /**
     * Get the bounding radius of the object.
     * @return The bounding radius as a float.
     */
    float GetBoundingRadius() const { return m_BoundingRadius; }
    /**
     * Get the previous position of the object.
     * This is used to calculate the movement and collision detection.
     * @return The previous position as an XMVECTOR.
     */
    XMVECTOR GetPreviousPosition() const { return m_PreviousPosition; }
    /**
     * Check if gravity is enabled for the object.
     * @return True if gravity is enabled, false otherwise.
     */
    bool IsGravityEnabled() const { return m_GravityEnabled; }
    /**
     * Get the current position of the object.
     * This method should be connected to the TransformComponent to retrieve the current position.
     * @return The current position as an XMVECTOR.
     */

    std::string Serialize() const override
    {
        std::stringstream ss;
        ss  << "PhysicsComponent:"
            << GetMass() << ":"
            << GetBoundingRadius() << ":"
            << (IsPhysicsEnabled() ? "1" : "0") << ":"
            << (IsGravityEnabled() ? "1" : "0") << ":"
            << (IsGrounded() ? "1" : "0");
        return ss.str();
    }

    bool Deserialize(const std::string& data) override
    {
        std::stringstream ss(data);
        std::string type;
        std::getline(ss, type, ':');

        if (type != "PhysicsComponent") return false;

        std::string token;
        float mass, boundingRadius;
        bool physicsEnabled, gravityEnabled, isGrounded;

        std::getline(ss, token, ':'); mass = std::stof(token);
        std::getline(ss, token, ':'); boundingRadius = std::stof(token);
        std::getline(ss, token, ':'); physicsEnabled = (token == "1");
        std::getline(ss, token, ':'); gravityEnabled = (token == "1");
        std::getline(ss, token, ':'); isGrounded = (token == "1");

        SetMass(mass);
        SetBoundingRadius(boundingRadius);
        SetPhysicsEnabled(physicsEnabled);
        SetGravityEnabled(gravityEnabled);
        SetGrounded(isGrounded);
        return true;
    }

    void OnImGuiRender() override {
        float mass = GetMass();
        if (ImGui::DragFloat("Mass", &mass, 0.1f, 0.1f, 100.0f)) {
            SetMass(mass);
        }

        float radius = GetBoundingRadius();
        if (ImGui::DragFloat("Bounding Radius", &radius, 0.1f, 0.1f, 100.0f)) {
            SetBoundingRadius(radius);
        }

        bool physicsEnabled = IsPhysicsEnabled();
        if (ImGui::Checkbox("Physics Enabled", &physicsEnabled)) {
            SetPhysicsEnabled(physicsEnabled);
        }

        bool gravityEnabled = IsGravityEnabled();
        if (ImGui::Checkbox("Gravity Enabled", &gravityEnabled)) {
            SetGravityEnabled(gravityEnabled);
        }

        bool isGrounded = IsGrounded();
        if (ImGui::Checkbox("Is Grounded", &isGrounded)) {
            SetGrounded(isGrounded);
        }

        XMFLOAT3 velocity;
        XMStoreFloat3(&velocity, GetVelocity());
        if (ImGui::DragFloat3("Velocity", &velocity.x, 0.1f)) {
            SetVelocity(XMLoadFloat3(&velocity));
        }

        XMFLOAT3 acceleration;
        XMStoreFloat3(&acceleration, GetAcceleration());
        if (ImGui::DragFloat3("Acceleration", &acceleration.x, 0.1f)) {
            SetAcceleration(XMLoadFloat3(&acceleration));
        }
    }

private:
    XMVECTOR m_Velocity;
    XMVECTOR m_Acceleration;
    XMVECTOR m_PreviousPosition;
    float m_Mass;
    float m_BoundingRadius;
    bool m_IsGrounded;
    bool m_IsPhysicsEnabled;
    bool m_GravityEnabled;
    
    // Callback pour mettre à jour la position (sera connecté au TransformComponent)
    std::function<void(XMVECTOR)> m_UpdatePositionCallback;
};

}  // namespace ecs
