/*
 * Copyright 2023 Rive
 */

#include "gm.hpp"
#include "gr_inner_fan_triangulator.hpp"
#include "common/testing_window.hpp"
#include "rive/renderer.hpp"
#include "rive/renderer/draw.hpp"
#include "rive/renderer/render_context.hpp"
#include "../src/rive_render_paint.hpp"
#include "../src/rive_render_path.hpp"
#include "../src/shaders/constants.glsl"

using namespace rivegm;
using namespace rive;
using namespace rive::gpu;

constexpr static std::array<Vec2D, 3> kTris[] = {
    {Vec2D{10, 10}, Vec2D{50, 10}, Vec2D{10, 120}},
    {Vec2D{10, 290}, Vec2D{290, 10}, Vec2D{270, 280}},
    {Vec2D{60, 60}, Vec2D{30, 190}, Vec2D{250, 20}},
};
constexpr static size_t kNumTriangles = sizeof(kTris) / sizeof(kTris[0]);

rcp<RiveRenderPath> make_nonempty_placeholder_path()
{
    auto path = make_rcp<RiveRenderPath>();
    path->moveTo(0, 0);
    return path;
}

class PushRetrofittedTrianglesGMDraw : public PathDraw
{
public:
    PushRetrofittedTrianglesGMDraw(RenderContext* context,
                                   const RiveRenderPaint* paint) :
        PathDraw(FULLSCREEN_PIXEL_BOUNDS,
                 Mat2D(),
                 make_nonempty_placeholder_path(),
                 context->frameDescriptor().clockwiseFillOverride
                     ? FillRule::clockwise
                     : FillRule::nonZero,
                 paint,
                 SelectCoverageType(paint,
                                    1,
                                    context->platformFeatures(),
                                    context->frameInterlockMode()),
                 context->frameDescriptor())
    {
        m_resourceCounts.pathCount = 1;
        m_resourceCounts.contourCount = 1;
        m_resourceCounts.maxTessellatedSegmentCount = kNumTriangles;
        m_resourceCounts.outerCubicTessVertexCount =
            context->frameInterlockMode() != gpu::InterlockMode::msaa
                ? gpu::kOuterCurvePatchSegmentSpan * kNumTriangles * 2
                : gpu::kOuterCurvePatchSegmentSpan * kNumTriangles;
        m_triangulator = context->make<GrInnerFanTriangulator>(
            RawPath(),
            Mat2D(),
            GrTriangulator::Comparator::Direction::kHorizontal,
            FillRule::nonZero,
            &context->perFrameAllocator());
    }

    void countSubpasses() override
    {
        assert(m_prepassCount == 0);
        assert(m_subpassCount == 1);
    }

    bool allocateResources(RenderContext::LogicalFlush* flush) override
    {
        if (!PathDraw::allocateResources(flush))
        {
            return false;
        }
        return true;
    }

    void pushToRenderContext(RenderContext::LogicalFlush* flush,
                             int subpassIndex) override
    {
        // Make sure the rawPath in our path reference hasn't changed since we
        // began holding!
        assert(m_rawPathMutationID == m_pathRef->getRawPathMutationID());
        assert(!m_pathRef->getRawPath().empty());
        assert(subpassIndex == 0);

        uint32_t tessVertexCount = math::lossless_numeric_cast<uint32_t>(
            m_resourceCounts.outerCubicTessVertexCount);
        if (tessVertexCount > 0)
        {
            m_pathID = flush->pushPath(this);

            uint32_t tessLocation =
                flush->allocateOuterCubicTessVertices(tessVertexCount);

            uint32_t forwardTessVertexCount, forwardTessLocation,
                mirroredTessVertexCount, mirroredTessLocation;
            if (m_contourDirections ==
                gpu::ContourDirections::reverseThenForward)
            {
                forwardTessVertexCount = mirroredTessVertexCount =
                    tessVertexCount / 2;
                forwardTessLocation = mirroredTessLocation =
                    tessLocation + tessVertexCount / 2;
            }
            else
            {
                assert(m_contourDirections == gpu::ContourDirections::forward);
                forwardTessVertexCount = tessVertexCount;
                forwardTessLocation = tessLocation;
                mirroredTessVertexCount = mirroredTessLocation = 0;
            }

            RenderContext::TessellationWriter tessWriter(
                flush,
                m_pathID,
                m_contourDirections,
                forwardTessVertexCount,
                forwardTessLocation,
                mirroredTessVertexCount,
                mirroredTessLocation);

            uint32_t contourID = tessWriter.pushContour(
                {0, 0},
                /*isStroke=*/false,
                /*closed=*/true,
                0 /* gpu::kOuterCurvePatchSegmentSpan - 2 */);
            for (const auto& pts : kTris)
            {
                Vec2D tri[4] = {pts[0], pts[1], {0, 0}, pts[2]};
                tessWriter.pushCubic(tri,
                                     m_contourDirections,
                                     {0, 0},
                                     gpu::kOuterCurvePatchSegmentSpan - 1,
                                     1,
                                     1,
                                     contourID |
                                         RETROFITTED_TRIANGLE_CONTOUR_FLAG);
            }

            if (flush->frameDescriptor().clockwiseFillOverride)
            {
                m_drawContents |= gpu::DrawContents::clockwiseFill;
            }

            flush->pushOuterCubicsDraw(this,
                                       m_coverageType == CoverageType::msaa
                                           ? gpu::DrawType::msaaOuterCubics
                                           : gpu::DrawType::outerCurvePatches,
                                       tessVertexCount,
                                       tessLocation,
                                       gpu::ShaderMiscFlags::none);
        }
    }
};

// Checks that RenderContext properly draws single triangles when using the
// "kRetrofittedTriangle" flag.
class RetrofittedCubicTrianglesGM : public GM
{
public:
    RetrofittedCubicTrianglesGM() : GM(300, 300) {}

protected:
    void onDraw(Renderer* renderer) override
    {
        TestingWindow::Get()->endFrame(nullptr);
        gpu::RenderContext* renderContext =
            TestingWindow::Get()->renderContext();
        if (!renderContext)
        {
            TestingWindow::Get()->beginFrame({.clearColor = 0xffff0000});
        }
        else
        {
            TestingWindow::Get()->beginFrame({.clearColor = 0xff000000});
            RiveRenderPaint paint;
            paint.color(0xffffffff);
            DrawUniquePtr draw(
                renderContext->make<PushRetrofittedTrianglesGMDraw>(
                    renderContext,
                    &paint));
            bool success RIVE_MAYBE_UNUSED = renderContext->pushDraws(&draw, 1);
            assert(success);
        }
    }
};

GMREGISTER(retrofittedcubictriangles, return new RetrofittedCubicTrianglesGM;)
