#include "rive/tess/segmented_contour.hpp"
#include "rive/math/raw_path.hpp"
#include "rive/math/cubic_utilities.hpp"

using namespace rive;

SegmentedContour::SegmentedContour(float threshold) :
    m_bounds(AABB::forExpansion()),
    m_threshold(threshold),
    m_thresholdSquared(threshold * threshold)
{}

float SegmentedContour::threshold() const { return m_threshold; }
void SegmentedContour::threshold(float value)
{
    m_threshold = value;
    m_thresholdSquared = value * value;
}
const AABB& SegmentedContour::bounds() const { return m_bounds; }
void SegmentedContour::addVertex(Vec2D vertex)
{
    m_contourPoints.push_back(vertex);
    AABB::expandTo(m_bounds, vertex);
}

const std::size_t SegmentedContour::contourSize() const
{
    return m_contourPoints.size();
}

const Span<const Vec2D> SegmentedContour::contourPoints(
    uint32_t endOffset) const
{
    assert(endOffset <= m_contourPoints.size());
    return Span<const Vec2D>(m_contourPoints.data(),
                             m_contourPoints.size() - endOffset);
}

void SegmentedContour::segmentCubic(const Vec2D& from,
                                    const Vec2D& fromOut,
                                    const Vec2D& toIn,
                                    const Vec2D& to,
                                    float t1,
                                    float t2)
{
    if (CubicUtilities::shouldSplitCubic(from, fromOut, toIn, to, m_threshold))
    {
        float halfT = (t1 + t2) / 2.0f;

        Vec2D hull[6];
        CubicUtilities::computeHull(from, fromOut, toIn, to, 0.5f, hull);

        segmentCubic(from, hull[0], hull[3], hull[5], t1, halfT);

        segmentCubic(hull[5], hull[4], hull[2], to, halfT, t2);
    }
    else
    {
        if (Vec2D::distanceSquared(from, to) > m_thresholdSquared)
        {
            addVertex(Vec2D(
                CubicUtilities::cubicAt(t2, from.x, fromOut.x, toIn.x, to.x),
                CubicUtilities::cubicAt(t2, from.y, fromOut.y, toIn.y, to.y)));
        }
    }
}

void SegmentedContour::contour(const RawPath& rawPath, const Mat2D& transform)
{
    m_contourPoints.clear();

    // Possible perf consideration: could add second path that doesn't transform
    // if transform is the identity.
    for (auto tuple : rawPath)
    {
        PathVerb verb = std::get<0>(tuple);
        const Vec2D* pts = std::get<1>(tuple);

        switch (verb)
        {
            case PathVerb::move:
                addVertex(transform * pts[0]);
                break;
            case PathVerb::line:
                addVertex(transform * pts[1]);
                break;
            case PathVerb::cubic:
                segmentCubic(transform * pts[0],
                             transform * pts[1],
                             transform * pts[2],
                             transform * pts[3],
                             0.0f,
                             1.0f);
                break;
            case PathVerb::close:
                break;
            case PathVerb::quad:
                // TODO: not currently used by render paths, however might be
                // necessary for fonts.
                break;
        }
    }
}
