#include "rive/layout_component.hpp"
#include "rive/layout/n_sliced_node.hpp"
#include "rive/math/math_types.hpp"
#include "rive/math/n_slicer_helpers.hpp"

using namespace rive;

void NSlicedNode::markPathDirtyRecursive(bool sendToLayout)
{
    // Tell all Shape descendants to re-render when the n slicer changes (e.g.
    // when axis offset animates).
    addDirt(ComponentDirt::NSlicer, true);

#ifdef WITH_RIVE_LAYOUT
    if (sendToLayout)
    {
        for (ContainerComponent* p = parent(); p != nullptr; p = p->parent())
        {
            if (p->is<LayoutComponent>())
            {
                p->as<LayoutComponent>()->markLayoutNodeDirty();
                break;
            }
        }
    }
#endif
}

void NSlicedNode::axisChanged() { markPathDirtyRecursive(); }
void NSlicedNode::widthChanged() { markPathDirtyRecursive(); }
void NSlicedNode::heightChanged() { markPathDirtyRecursive(); }

AABB NSlicedNode::localBounds() const
{
    return AABB(0, 0, initialWidth(), initialHeight());
}

void NSlicedNode::update(ComponentDirt value)
{
    Super::update(value);

    // Update whenever children axes change, or our world transform changes
    // in case any dependent needs local path (we need to first map to the
    // NSlicer space using world transform, then back).
    if (hasDirt(value, ComponentDirt::NSlicer | ComponentDirt::WorldTransform))
    {
        updateMapWorldPoint();
    }
}

void NSlicedNode::updateMapWorldPoint()
{
    const Mat2D& world = worldTransform();
    Mat2D inverseWorld;
    if (!world.invert(&inverseWorld) || initialHeight() <= 0 ||
        initialWidth() <= 0)
    {
        mapWorldPoint = [](Vec2D& v) {};
        return;
    }

    Vec2D size = Vec2D(initialWidth(), initialHeight());
    Vec2D scale = scaleForNSlicer();

    std::vector<float> xPxStops = NSlicerHelpers::pxStops(xs(), size.x);
    std::vector<float> yPxStops = NSlicerHelpers::pxStops(ys(), size.y);

    std::vector<float> xUVStops = NSlicerHelpers::uvStops(xs(), size.x);
    std::vector<float> yUVStops = NSlicerHelpers::uvStops(ys(), size.y);

    ScaleInfo xScaleInfo =
        NSlicerHelpers::analyzeUVStops(xUVStops, size.x, std::abs(scale.x));
    ScaleInfo yScaleInfo =
        NSlicerHelpers::analyzeUVStops(yUVStops, size.x, std::abs(scale.y));

    mapWorldPoint = [this,
                     world,
                     inverseWorld,
                     scale,
                     xPxStops,
                     xScaleInfo,
                     yPxStops,
                     yScaleInfo](Vec2D& worldP) {
        // 1. Map from world space to the effective NSlicer's space
        Vec2D localP = inverseWorld * worldP;

        // 2. N-Slice it in the NSlicer's space
        Vec2D slicedP =
            Vec2D(scale.x == 0 ? 0.0
                               : NSlicerHelpers::mapValue(xPxStops,
                                                          xScaleInfo,
                                                          std::abs(width()),
                                                          localP.x) *
                                     std::copysign(1.0f, scale.x),
                  scale.y == 0 ? 0.0
                               : NSlicerHelpers::mapValue(yPxStops,
                                                          yScaleInfo,
                                                          std::abs(height()),
                                                          localP.y) *
                                     std::copysign(1.0f, scale.y));

        // 3. Back to world space
        worldP = world * slicedP;
    };
}

void NSlicedNode::deformWorldRenderPath(RawPath& path) const
{
    NSlicerHelpers::deformWorldRenderPathWithNSlicer(*this, path);
}

void NSlicedNode::deformLocalRenderPath(RawPath& path,
                                        const Mat2D& world,
                                        const Mat2D& inverseWorld) const
{
    NSlicerHelpers::deformLocalRenderPathWithNSlicer(*this,
                                                     path,
                                                     world,
                                                     inverseWorld);
}

Vec2D NSlicedNode::deformLocalPoint(Vec2D point,
                                    const Mat2D& worldTransform,
                                    const Mat2D& inverseWorld) const
{
    Vec2D worldP = worldTransform * point;
    Vec2D deformedWorldP = deformWorldPoint(worldP);
    return inverseWorld * deformedWorldP;
}

Vec2D NSlicedNode::deformWorldPoint(Vec2D point) const
{
    Vec2D result = point;
    mapWorldPoint(result);
    return result;
}

Vec2D NSlicedNode::scaleForNSlicer() const
{
    return Vec2D(width() / initialWidth(), height() / initialHeight());
}

// We are telling a Layout what our size is
Vec2D NSlicedNode::measureLayout(float width,
                                 LayoutMeasureMode widthMode,
                                 float height,
                                 LayoutMeasureMode heightMode)
{
    return Vec2D(std::min((widthMode == LayoutMeasureMode::undefined
                               ? std::numeric_limits<float>::max()
                               : width),
                          NSlicedNode::width()),
                 std::min((heightMode == LayoutMeasureMode::undefined
                               ? std::numeric_limits<float>::max()
                               : height),
                          NSlicedNode::height()));
}

// We are told by a Layout to be a particular size
void NSlicedNode::controlSize(Vec2D size,
                              LayoutScaleType widthScaleType,
                              LayoutScaleType heightScaleType,
                              LayoutDirection direction)
{
    width(size.x);
    height(size.y);
    markWorldTransformDirty();
    markPathDirtyRecursive(false);
}
