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

using namespace DirectX;

namespace ecs {

class TransformComponent : public Component {
public:

    /**
     * Builder for the TransformComponent class.
     * Initializes the matrices to identity matrices.
     */
    TransformComponent() {
        m_ScaleMatrix = XMMatrixIdentity();
        m_RotateMatrix = XMMatrixIdentity();
        m_TranslateMatrix = XMMatrixIdentity();
        m_WorldMatrix = XMMatrixIdentity();
    }

    ~TransformComponent() = default;

    /**
     * Set the position of the object in world space.
     * @param position 
     */
    void SetPosition(XMVECTOR position) {
        XMFLOAT4X4 matrix;
        XMStoreFloat4x4(&matrix, m_TranslateMatrix);
        matrix._41 = XMVectorGetX(position);
        matrix._42 = XMVectorGetY(position);
        matrix._43 = XMVectorGetZ(position);
        m_TranslateMatrix = XMLoadFloat4x4(&matrix);
        UpdateWorldMatrix();
    }

    /**
     * Set the rotation of the object in world space.
     * @param rotation The rotation as an XMVECTOR (roll, pitch, yaw).
     */
    void SetRotation(XMVECTOR rotation) {
        m_RotateMatrix = XMMatrixRotationRollPitchYawFromVector(rotation);
        UpdateWorldMatrix();
    }

    /**
     * Set the scale of the object in world space.
     * @param scale The scale as an XMVECTOR (x, y, z).
     */
    void SetScale(XMVECTOR scale) {
        XMFLOAT4X4 matrix;
        XMStoreFloat4x4(&matrix, m_ScaleMatrix);
        matrix._11 = XMVectorGetX(scale);
        matrix._22 = XMVectorGetY(scale);
        matrix._33 = XMVectorGetZ(scale);
        m_ScaleMatrix = XMLoadFloat4x4(&matrix);
        UpdateWorldMatrix();
    }

    /**
     * Get the position of the object in world space.
     * @return The position as an XMVECTOR (x, y, z, (w is O.0f)).
     */
    XMVECTOR GetPosition() const {
        XMFLOAT4X4 matrix;
        XMStoreFloat4x4(&matrix, m_TranslateMatrix);
        return XMVectorSet(matrix._41, matrix._42, matrix._43, 0.0f);
    }

    /**
     * Get the rotation of the object in world space.
     * @return The rotation as an XMVECTOR (roll, pitch, yaw, (w is O.0f)).
     */
    XMVECTOR GetRotation() const {
        XMFLOAT4X4 matrix;
        XMStoreFloat4x4(&matrix, m_RotateMatrix);
        float rotationX = atan2f(matrix._32, matrix._33);
        float rotationY = atan2f(-matrix._31, sqrtf(matrix._32 * matrix._32 + matrix._33 * matrix._33));
        float rotationZ = atan2f(matrix._21, matrix._11);
        return XMVectorSet(rotationX, rotationY, rotationZ, 0.0f);
    }

    /**
     * Get the scale of the object in world space.
     * @return The scale as an XMVECTOR (x, y, z, (w is O.0f)).
     */
    XMVECTOR GetScale() const {
        XMFLOAT4X4 matrix;
        XMStoreFloat4x4(&matrix, m_ScaleMatrix);

        XMVECTOR row1 = XMLoadFloat3(reinterpret_cast<XMFLOAT3*>(&matrix._11));
        XMVECTOR row2 = XMLoadFloat3(reinterpret_cast<XMFLOAT3*>(&matrix._21));
        XMVECTOR row3 = XMLoadFloat3(reinterpret_cast<XMFLOAT3*>(&matrix._31));

        XMVECTOR scale = XMVectorSet(
            XMVectorGetX(XMVector3Length(row1)),
            XMVectorGetX(XMVector3Length(row2)),
            XMVectorGetX(XMVector3Length(row3)),
            0.0f
        );

        return scale;
    }

    /**
     * Update the world matrix based on the current scale, rotation, and translation matrices.
     * This method is called whenever one of the matrices is modified.
     */
    void UpdateWorldMatrix() {
        m_WorldMatrix = m_ScaleMatrix * m_RotateMatrix * m_TranslateMatrix;
    }

    /**
     * Get the scale matrix.
     * @return The scale matrix as an XMMATRIX.
     */
    XMMATRIX GetScaleMatrix() const { return m_ScaleMatrix; }
    /**
     * Get the rotation matrix.
     * @return The rotation matrix as an XMMATRIX.
     */
    XMMATRIX GetRotateMatrix() const { return m_RotateMatrix; }
    /**
     * Get the translation matrix.
     * @return The translation matrix as an XMMATRIX.
     */
    XMMATRIX GetTranslateMatrix() const { return m_TranslateMatrix; }
    /**
     * Get the world matrix.
     * This matrix combines scale, rotation, and translation to represent the object's position in world space.
     * @return The world matrix as an XMMATRIX.
     */
    XMMATRIX GetWorldMatrix() const { return m_WorldMatrix; }

    /**
     * Set the scale matrix directly.
     * This method allows setting the scale matrix without modifying the individual scale components.
     * @param matrix The scale matrix to set as an XMMATRIX.
     */
    void SetScaleMatrix(XMMATRIX matrix) { m_ScaleMatrix = matrix; UpdateWorldMatrix(); }
    /**
     * Set the rotation matrix directly.
     * This method allows setting the rotation matrix without modifying the individual rotation components.
     * @param matrix The rotation matrix to set as an XMMATRIX.
     */
    void SetRotateMatrix(XMMATRIX matrix) { m_RotateMatrix = matrix; UpdateWorldMatrix(); }
    /**
     * Set the translation matrix directly.
     * This method allows setting the translation matrix without modifying the individual translation components.
     * @param matrix The translation matrix to set as an XMMATRIX.
     */
    void SetTranslateMatrix(XMMATRIX matrix) { m_TranslateMatrix = matrix; UpdateWorldMatrix(); }

    /**
     * Serialize the transform component's state to a string.
     * The format is: "TransformComponent:posX:posY:posZ:rotX:rotY:rotZ:scaleX:scaleY:scaleZ"
     * @return A string representation of the transform component's state.
     */
    std::string Serialize() const override
    {
        XMFLOAT3 position, rotation, scale;
        XMStoreFloat3(&position, GetPosition());
        XMStoreFloat3(&rotation, GetRotation());
        XMStoreFloat3(&scale, GetScale());

        std::stringstream ss;
        ss  << "TransformComponent:"
            << position.x << ":" << position.y << ":" << position.z << ":"
            << rotation.x << ":" << rotation.y << ":" << rotation.z << ":"
            << scale.x << ":" << scale.y << ":" << scale.z;
        return ss.str();
    }

    /**
     * Deserialize the transform component's state from a string.
     * The expected format is: "TransformComponent:posX:posY:posZ:rotX:rotY:rotZ:scaleX:scaleY:scaleZ"
     * @param data The string representation of the transform component's state.
     * @return True if deserialization was successful, otherwise false.
     */
    bool Deserialize(const std::string& data) override
    {
        std::stringstream ss(data);
        std::string type;
        std::getline(ss, type, ':');

        if (type != "TransformComponent") R_FALSE

        std::string token;
        XMFLOAT3 position, rotation, scale;

        std::getline(ss, token, ':'); position.x = std::stof(token);
        std::getline(ss, token, ':'); position.y = std::stof(token);
        std::getline(ss, token, ':'); position.z = std::stof(token);

        std::getline(ss, token, ':'); rotation.x = std::stof(token);
        std::getline(ss, token, ':'); rotation.y = std::stof(token);
        std::getline(ss, token, ':'); rotation.z = std::stof(token);

        std::getline(ss, token, ':'); scale.x = std::stof(token);
        std::getline(ss, token, ':'); scale.y = std::stof(token);
        std::getline(ss, token, ':'); scale.z = std::stof(token);

        SetPosition(XMLoadFloat3(&position));
        SetRotation(XMLoadFloat3(&rotation));
        SetScale(XMLoadFloat3(&scale));
        R_TRUE
    }

    /**
     * Render the ImGui interface for editing the transform component.
     * This method provides draggable controls for position, rotation, and scale.
     */
    void OnImGuiRender() override {
        XMFLOAT3 position, rotation, scale;
        XMStoreFloat3(&position, GetPosition());
        XMStoreFloat3(&rotation, GetRotation());
        XMStoreFloat3(&scale, GetScale());
        
        if (ImGui::DragFloat3("Position", &position.x, 0.1f)) {
            SetPosition(XMLoadFloat3(&position));
        }
        
        if (ImGui::DragFloat3("Rotation", &rotation.x, 0.01f)) {
            SetRotation(XMLoadFloat3(&rotation));
        }
        
        if (ImGui::DragFloat3("Scale", &scale.x, 0.1f, 0.01f, 100.0f)) {
            SetScale(XMLoadFloat3(&scale));
        }
    }
    

private:
    XMMATRIX m_ScaleMatrix;
    XMMATRIX m_RotateMatrix;
    XMMATRIX m_TranslateMatrix;
    XMMATRIX m_WorldMatrix;
};

}  // namespace ecs
