#include "rive/bones/skin.hpp"
#include "rive/bones/bone.hpp"
#include "rive/bones/skinnable.hpp"
#include "rive/bones/tendon.hpp"
#include "rive/shapes/vertex.hpp"
#include "rive/shapes/path_vertex.hpp"
#include "rive/constraints/constraint.hpp"

using namespace rive;

Skin::~Skin() { delete[] m_BoneTransforms; }

StatusCode Skin::onAddedDirty(CoreContext* context)
{
    StatusCode code = Super::onAddedDirty(context);
    if (code != StatusCode::Ok)
    {
        return code;
    }
    m_WorldTransform[0] = xx();
    m_WorldTransform[1] = xy();
    m_WorldTransform[2] = yx();
    m_WorldTransform[3] = yy();
    m_WorldTransform[4] = tx();
    m_WorldTransform[5] = ty();

    m_Skinnable = Skinnable::from(parent());
    if (m_Skinnable == nullptr)
    {
        return StatusCode::MissingObject;
    }

    m_Skinnable->skin(this);

    return StatusCode::Ok;
}

void Skin::update(ComponentDirt value)
{
    int bidx = 6;
    for (auto tendon : m_Tendons)
    {
        auto world = tendon->bone()->worldTransform() * tendon->inverseBind();
        m_BoneTransforms[bidx++] = world[0];
        m_BoneTransforms[bidx++] = world[1];
        m_BoneTransforms[bidx++] = world[2];
        m_BoneTransforms[bidx++] = world[3];
        m_BoneTransforms[bidx++] = world[4];
        m_BoneTransforms[bidx++] = world[5];
    }
}

void Skin::buildDependencies()
{
    // depend on bones from tendons
    for (auto tendon : m_Tendons)
    {
        auto bone = tendon->bone();
        bone->addDependent(this);
        for (auto constraint : bone->peerConstraints())
        {
            constraint->parent()->addDependent(this);
        }
    }

    // Make sure no-one is calling this twice.
    assert(m_BoneTransforms == nullptr);
    // We can now init the bone buffer.
    auto size = (m_Tendons.size() + 1) * 6;
    m_BoneTransforms = new float[size];
    m_BoneTransforms[0] = 1;
    m_BoneTransforms[1] = 0;
    m_BoneTransforms[2] = 0;
    m_BoneTransforms[3] = 1;
    m_BoneTransforms[4] = 0;
    m_BoneTransforms[5] = 0;
}

void Skin::deform(Span<Vertex*> vertices)
{
    for (auto vertex : vertices)
    {
        vertex->deform(m_WorldTransform, m_BoneTransforms);
    }
}
void Skin::addTendon(Tendon* tendon) { m_Tendons.push_back(tendon); }

void Skin::onDirty(ComponentDirt dirt)
{
    if (m_Skinnable != nullptr)
    {
        m_Skinnable->markSkinDirty();
    }
}
