/*
 * Copyright 2011 Google Inc.
 * Copyright 2022 Rive
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "gm.hpp"
#include "gmutils.hpp"
#include "rive/renderer.hpp"
#include "common/rand.hpp"

using namespace rivegm;
using namespace rive;

namespace
{

class SkDoOnce
{
public:
    SkDoOnce() { fDidOnce = false; }

    bool alreadyDone() const { return fDidOnce; }
    void accomplished()
    {
        assert(!fDidOnce);
        fDidOnce = true;
    }

private:
    bool fDidOnce;
};

class ConvexPathsGM : public GM
{
public:
    ConvexPathsGM() : GM(1200, 1100) {}

private:
    SkDoOnce fOnce;

    void makePaths()
    {

        if (fOnce.alreadyDone())
        {
            return;
        }
        fOnce.accomplished();

        PathBuilder b;
        fPaths.push_back(
            b.moveTo(0, 0).quadTo(50, 100, 0, 100).lineTo(0, 0).detach());

        fPaths.push_back(b.moveTo(0, 50)
                             .quadTo(50, 0, 100, 50)
                             .quadTo(50, 100, 0, 50)
                             .detach());

        fPaths.push_back(
            PathBuilder::Rect({0, 0, 100, 100}, rivegm::PathDirection::cw));
        fPaths.push_back(
            PathBuilder::Rect({0, 0, 100, 100}, rivegm::PathDirection::ccw));
        fPaths.push_back(
            PathBuilder::Oval({0, 0, 100, 100}, rivegm::PathDirection::cw));
        fPaths.push_back(
            PathBuilder::Oval({0, 0, 50, 100}, rivegm::PathDirection::cw));
        fPaths.push_back(
            PathBuilder::Oval({0, 0, 100, 5}, rivegm::PathDirection::ccw));
        fPaths.push_back(
            PathBuilder::Oval({0, 0, 1, 100}, rivegm::PathDirection::ccw));
        fPaths.push_back(PathBuilder::RRect({0, 0, 100, 100}, 40, 20));

        // large number of points
        constexpr static int kLength = 100;
        constexpr static int kPtsPerSide = (1 << 12);
        b.moveTo(0, 0);
        for (int i = 1; i < kPtsPerSide; ++i)
        { // skip the first point due to moveTo.
            b.lineTo(kLength * (float)(i) / kPtsPerSide, 0);
        }
        for (int i = 0; i < kPtsPerSide; ++i)
        {
            b.lineTo(kLength, kLength * (float)(i) / kPtsPerSide);
        }
        for (int i = kPtsPerSide; i > 0; --i)
        {
            b.lineTo(kLength * (float)(i) / kPtsPerSide, kLength);
        }
        for (int i = kPtsPerSide; i > 0; --i)
        {
            b.lineTo(0, kLength * (float)(i) / kPtsPerSide);
        }
        fPaths.push_back(b.detach());

        // shallow diagonals
        fPaths.push_back(b.moveTo(0, 0)
                             .lineTo(100, 1)
                             .lineTo(98, 100)
                             .lineTo(3, 96)
                             .detach());
        fPaths.push_back(PathBuilder::Oval({0, 0, 50, 100}));

        // cubics
        fPaths.push_back(b.cubicTo(1, 1, 10, 90, 0, 100).detach());
        fPaths.push_back(b.cubicTo(100, 50, 20, 100, 0, 0).detach());

        // path that has a cubic with a repeated first control point and
        // a repeated last control point.
        fPaths.push_back(b.moveTo(10, 10)
                             .cubicTo(10, 10, 10, 0, 20, 0)
                             .lineTo(40, 0)
                             .cubicTo(40, 0, 50, 0, 50, 10)
                             .detach());

        // path that has two cubics with repeated middle control points.
        fPaths.push_back(b.moveTo(10, 10)
                             .cubicTo(10, 0, 10, 0, 20, 0)
                             .lineTo(40, 0)
                             .cubicTo(50, 0, 50, 0, 50, 10)
                             .detach());

        // cubic where last three points are almost a line
        fPaths.push_back(b.moveTo(0, 228.0f / 8)
                             .cubicTo(628.0f / 8,
                                      82.0f / 8,
                                      1255.0f / 8,
                                      141.0f / 8,
                                      1883.0f / 8,
                                      202.0f / 8)
                             .detach());

        // flat cubic where the at end point tangents both point outward.
        fPaths.push_back(b.moveTo(10, 0).cubicTo(0, 1, 30, 1, 20, 0).detach());

        // flat cubic where initial tangent is in, end tangent out
        fPaths.push_back(b.moveTo(0, 0).cubicTo(10, 1, 30, 1, 20, 0).detach());

        // flat cubic where initial tangent is out, end tangent in
        fPaths.push_back(b.moveTo(10, 0).cubicTo(0, 1, 20, 1, 30, 0).detach());

        // triangle where one edge is a degenerate quad
        fPaths.push_back(b.moveTo(8.59375f, 45)
                             .quadTo(16.9921875f, 45, 31.25f, 45)
                             .lineTo(100, 100)
                             .lineTo(8.59375f, 45)
                             .detach());

        // triangle where one edge is a quad with a repeated point
        fPaths.push_back(
            b.moveTo(0, 25).lineTo(50, 0).quadTo(50, 50, 50, 50).detach());

        // triangle where one edge is a cubic with a 2x repeated point
        fPaths.push_back(b.moveTo(0, 25)
                             .lineTo(50, 0)
                             .cubicTo(50, 0, 50, 50, 50, 50)
                             .detach());

        // triangle where one edge is a quad with a nearly repeated point
        fPaths.push_back(
            b.moveTo(0, 25).lineTo(50, 0).quadTo(50, 49.95f, 50, 50).detach());

        // triangle where one edge is a cubic with a 3x nearly repeated point
        fPaths.push_back(b.moveTo(0, 25)
                             .lineTo(50, 0)
                             .cubicTo(50, 49.95f, 50, 49.97f, 50, 50)
                             .detach());

        // triangle where there is a point degenerate cubic at one corner
        fPaths.push_back(b.moveTo(0, 25)
                             .lineTo(50, 0)
                             .lineTo(50, 50)
                             .cubicTo(50, 50, 50, 50, 50, 50)
                             .detach());

        // point line
        fPaths.push_back(b.moveTo(50, 50).lineTo(50, 50).detach());

        // point quad
        fPaths.push_back(b.moveTo(50, 50).quadTo(50, 50, 50, 50).detach());

        // point cubic
        fPaths.push_back(
            b.moveTo(50, 50).cubicTo(50, 50, 50, 50, 50, 50).detach());

        // moveTo only paths
        fPaths.push_back(b.moveTo(0, 0)
                             .moveTo(0, 0)
                             .moveTo(1, 1)
                             .moveTo(1, 1)
                             .moveTo(10, 10)
                             .detach());

        fPaths.push_back(b.moveTo(0, 0).moveTo(0, 0).detach());

        // line degenerate
        fPaths.push_back(b.lineTo(100, 100).detach());
        fPaths.push_back(b.quadTo(100, 100, 0, 0).detach());
        fPaths.push_back(b.quadTo(100, 100, 50, 50).detach());
        fPaths.push_back(b.quadTo(50, 50, 100, 100).detach());
        fPaths.push_back(b.cubicTo(0, 0, 0, 0, 100, 100).detach());

        // skbug.com/8928
        fPaths.push_back(Path());
        fPaths.back()->addRenderPath(
            b.moveTo(16.875f, 192.594f)
                .cubicTo(45.625f,
                         192.594f,
                         74.375f,
                         192.594f,
                         103.125f,
                         192.594f)
                .cubicTo(88.75f, 167.708f, 74.375f, 142.823f, 60, 117.938f)
                .cubicTo(45.625f, 142.823f, 31.25f, 167.708f, 16.875f, 192.594f)
                .close()
                .detach(),
            rive::Mat2D(0.1f, 0, 0, 0.115207f, -1, -2.64977f));

        // small circle. This is listed last so that it has device coords far
        // from the origin (small area relative to x,y values).
        fPaths.push_back(PathBuilder::Oval({-1.2f, -1.2f, 1.2f, 1.2f},
                                           rivegm::PathDirection::ccw));
    }

    ColorInt clearColor() const override { return 0xFF000000; }

    void onDraw(Renderer* renderer) override
    {
        this->makePaths();

        Paint paint;
        Rand rand;
        renderer->translate(20, 20);

        // As we've added more paths this has gotten pretty big. Scale the whole
        // thing down.
        renderer->scale(2.0f / 3, 2.0f / 3);

        for (size_t i = 0; i < fPaths.size(); ++i)
        {
            renderer->save();
            // position the path, and make it at off-integer coords.
            renderer->translate(200.0f * (i % 5) + 1.0f / 10,
                                200.0f * (int)(i / 5) + 9.0f / 10);
            ColorInt color = rand.u32();
            color |= 0xff000000;
            paint->color(color);
#if 0 // This hitting on 32bit Linux builds for some paths. Temporarily
      // disabling while it is debugged.
            SkASSERT(fPaths[i].isConvex());
#endif
            renderer->drawPath(fPaths[i], paint);
            renderer->restore();
        }
    }

    std::vector<Path> fPaths;
};
} // namespace

GMREGISTER(convexpaths, return new ConvexPathsGM;)
