/*
 * 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/shapes/paint/color.hpp"
#include "rive/math/mat2d.hpp"
#include "rive/renderer.hpp"

using namespace rivegm;
using namespace rive;

// https://bug.skia.org/1316 shows that this cubic, when slightly clipped,
// creates big (incorrect) changes to its control points.
class ClippedCubicGM : public GM
{
public:
    ClippedCubicGM() : GM(600, 500) {}

private:
    void onDraw(Renderer* canvas) override
    {
        canvas->translate(25, 25);
        Path path;
        path->moveTo(0, 0);
        path->cubicTo(140, 150, 40, 10, 170, 150);

        Paint paint;
        AABB bounds = {0, 0, 170, 150}; // path.getBounds();

        for (float dy = -1; dy <= 1; dy += 1)
        {
            canvas->save();
            for (float dx = -1; dx <= 1; dx += 1)
            {
                canvas->save();
                canvas->clipPath(PathBuilder::Rect(bounds));
                canvas->translate(dx, dy);
                canvas->drawPath(path, paint);
                canvas->restore();

                canvas->translate(bounds.width(), 0);
            }
            canvas->restore();
            canvas->translate(0, bounds.height());
        }
    }
};

class ClippedCubic2GM : public GM
{
public:
    ClippedCubic2GM() : GM(725, 325) {}

private:
    void onDraw(Renderer* canvas) override
    {
        canvas->translate(25, 25);
        canvas->save();
        canvas->translate(-2, 120);
        drawOne(canvas, fPath, {0, 0, 80, 150});
        canvas->translate(170, 0);
        drawOne(canvas, fPath, {0, 0, 80, 100});
        canvas->translate(170, 0);
        drawOne(canvas, fPath, {0, 0, 30, 150});
        canvas->translate(170, 0);
        drawOne(canvas, fPath, {0, 0, 10, 150});
        canvas->restore();
        canvas->save();
        canvas->translate(20, -2);
        drawOne(canvas, fFlipped, {0, 0, 150, 80});
        canvas->translate(170, 0);
        drawOne(canvas, fFlipped, {0, 0, 100, 80});
        canvas->translate(170, 0);
        drawOne(canvas, fFlipped, {0, 0, 150, 30});
        canvas->translate(170, 0);
        drawOne(canvas, fFlipped, {0, 0, 150, 10});
        canvas->restore();
    }

    void drawOne(Renderer* canvas, const Path& path, const AABB& clip)
    {
        Paint framePaint, fillPaint;
        framePaint->style(RenderPaintStyle::stroke);
        canvas->drawPath(PathBuilder::Rect(clip), framePaint);
        canvas->drawPath(path, framePaint);
        canvas->save();
        canvas->clipPath(PathBuilder::Rect(clip));
        canvas->drawPath(path, fillPaint);
        canvas->restore();
    }

    void onOnceBeforeDraw() override
    {
        fPath->moveTo(69.7030518991886f, 0);
        fPath->cubicTo(69.7030518991886f,
                       21.831149999999997f,
                       58.08369508178456f,
                       43.66448333333333f,
                       34.8449814469765f,
                       65.5f);
        fPath->cubicTo(11.608591683531916f,
                       87.33115f,
                       -0.010765133872116195f,
                       109.16448333333332f,
                       -0.013089005235602302f,
                       131);
        fPath->close();
        Mat2D matrix;
        matrix[0] = 0.0f;
        matrix[1] = 1.0f;
        matrix[2] = 1.0f;
        matrix[3] = 0.0f;
        fFlipped->addRenderPath(fPath, matrix);
    }

    Path fPath;
    Path fFlipped;
};

class CubicPathGM : public GM
{
public:
    CubicPathGM() : GM(825, 175) {}

private:
    void drawPath(Path& path,
                  Renderer* canvas,
                  ColorInt color,
                  const AABB& clip,
                  StrokeCap cap,
                  StrokeJoin join,
                  RenderPaintStyle style,
                  FillRule fill,
                  float strokeWidth)
    {
        path->fillRule(fill);
        Paint paint;
        paint->cap(cap);
        paint->thickness(strokeWidth);
        paint->join(join);
        paint->color(color);
        paint->style(style);
        canvas->save();
        canvas->clipPath(PathBuilder::Rect(clip));
        canvas->drawPath(path, paint);
        canvas->restore();
    }

    void onDraw(Renderer* canvas) override
    {
        struct FillAndName
        {
            FillRule fFill;
            const char* fName;
        };
        constexpr FillAndName gFills[] = {
            {FillRule::nonZero, "Winding"},
            {FillRule::evenOdd, "Even / Odd"},
        };
        struct StyleAndName
        {
            RenderPaintStyle fStyle;
            const char* fName;
        };
        constexpr StyleAndName gStyles[] = {
            {RenderPaintStyle::fill, "Fill"},
            {RenderPaintStyle::stroke, "Stroke"},
        };
        struct CapAndName
        {
            StrokeCap fCap;
            StrokeJoin fJoin;
            const char* fName;
        };
        constexpr CapAndName gCaps[] = {
            {StrokeCap::butt, StrokeJoin::bevel, "Butt"},
            {StrokeCap::round, StrokeJoin::round, "Round"},
            {StrokeCap::square, StrokeJoin::bevel, "Square"}};
        struct PathAndName
        {
            Path fPath;
            const char* fName;
        };
        PathAndName path;
        path.fPath->moveTo(25 * 1.0f, 10 * 1.0f);
        path.fPath->cubicTo(40 * 1.0f,
                            20 * 1.0f,
                            60 * 1.0f,
                            20 * 1.0f,
                            75 * 1.0f,
                            10 * 1.0f);
        path.fName = "moveTo-cubic";

        AABB rect = {0, 0, 100 * 1.0f, 30 * 1.0f};
        canvas->save();
        canvas->translate(10 * 1.0f, 30 * 1.0f);
        canvas->save();
        for (size_t cap = 0; cap < std::size(gCaps); ++cap)
        {
            if (0 < cap)
            {
                canvas->translate((rect.width() + 40 * 1.0f) *
                                      std::size(gStyles),
                                  0);
            }
            canvas->save();
            for (size_t fill = 0; fill < std::size(gFills); ++fill)
            {
                if (0 < fill)
                {
                    canvas->translate(0, rect.height() + 40 * 1.0f);
                }
                canvas->save();
                for (size_t style = 0; style < std::size(gStyles); ++style)
                {
                    if (0 < style)
                    {
                        canvas->translate(rect.width() + 40 * 1.0f, 0);
                    }

                    ColorInt color = 0xff007000;
                    this->drawPath(path.fPath,
                                   canvas,
                                   color,
                                   rect,
                                   gCaps[cap].fCap,
                                   gCaps[cap].fJoin,
                                   gStyles[style].fStyle,
                                   gFills[fill].fFill,
                                   1.0f * 10);

                    Paint rectPaint;
                    rectPaint->color(0xFF000000);
                    rectPaint->style(RenderPaintStyle::stroke);
                    rectPaint->thickness(-1);
                    canvas->drawPath(PathBuilder::Rect(rect), rectPaint);
                }
                canvas->restore();
            }
            canvas->restore();
        }
        canvas->restore();
        canvas->restore();
    }
};

class CubicClosePathGM : public GM
{
public:
    CubicClosePathGM() : GM(825, 175) {}

private:
    void drawPath(Path& path,
                  Renderer* canvas,
                  ColorInt color,
                  const AABB& clip,
                  StrokeCap cap,
                  StrokeJoin join,
                  RenderPaintStyle style,
                  FillRule fill,
                  float strokeWidth)
    {
        path->fillRule(fill);
        Paint paint;
        paint->cap(cap);
        paint->thickness(strokeWidth);
        paint->join(join);
        paint->color(color);
        paint->style(style);
        canvas->save();
        canvas->clipPath(PathBuilder::Rect(clip));
        canvas->drawPath(path, paint);
        canvas->restore();
    }

    void onDraw(Renderer* canvas) override
    {
        struct FillAndName
        {
            FillRule fFill;
            const char* fName;
        };
        constexpr FillAndName gFills[] = {
            {FillRule::nonZero, "Winding"},
            {FillRule::evenOdd, "Even / Odd"},
        };
        struct StyleAndName
        {
            RenderPaintStyle fStyle;
            const char* fName;
        };
        constexpr StyleAndName gStyles[] = {
            {RenderPaintStyle::fill, "Fill"},
            {RenderPaintStyle::stroke, "Stroke"},
        };
        struct CapAndName
        {
            StrokeCap fCap;
            StrokeJoin fJoin;
            const char* fName;
        };
        constexpr CapAndName gCaps[] = {
            {StrokeCap::butt, StrokeJoin::bevel, "Butt"},
            {StrokeCap::round, StrokeJoin::round, "Round"},
            {StrokeCap::square, StrokeJoin::bevel, "Square"}};
        struct PathAndName
        {
            Path fPath;
            const char* fName;
        };
        PathAndName path;
        path.fPath->moveTo(25 * 1.0f, 10 * 1.0f);
        path.fPath->cubicTo(40 * 1.0f,
                            20 * 1.0f,
                            60 * 1.0f,
                            20 * 1.0f,
                            75 * 1.0f,
                            10 * 1.0f);
        path.fPath->close();
        path.fName = "moveTo-cubic-close";

        AABB rect = {0, 0, 100 * 1.0f, 30 * 1.0f};
        canvas->save();
        canvas->translate(10 * 1.0f, 30 * 1.0f);
        canvas->save();
        for (size_t cap = 0; cap < std::size(gCaps); ++cap)
        {
            if (0 < cap)
            {
                canvas->translate((rect.width() + 40 * 1.0f) *
                                      std::size(gStyles),
                                  0);
            }
            canvas->save();
            for (size_t fill = 0; fill < std::size(gFills); ++fill)
            {
                if (0 < fill)
                {
                    canvas->translate(0, rect.height() + 40 * 1.0f);
                }
                canvas->save();
                for (size_t style = 0; style < std::size(gStyles); ++style)
                {
                    if (0 < style)
                    {
                        canvas->translate(rect.width() + 40 * 1.0f, 0);
                    }

                    ColorInt color = 0xff007000;
                    this->drawPath(path.fPath,
                                   canvas,
                                   color,
                                   rect,
                                   gCaps[cap].fCap,
                                   gCaps[cap].fJoin,
                                   gStyles[style].fStyle,
                                   gFills[fill].fFill,
                                   1.0f * 10);

                    Paint rectPaint;
                    rectPaint->color(0xFF000000);
                    rectPaint->style(RenderPaintStyle::stroke);
                    rectPaint->thickness(-1);
                    canvas->drawPath(PathBuilder::Rect(rect), rectPaint);
                }
                canvas->restore();
            }
            canvas->restore();
        }
        canvas->restore();
        canvas->restore();
    }
};

DEF_SIMPLE_GM(bug5099, 50, 50, canvas)
{
    Paint p;
    p->color(0xFFFF0000);
    p->style(RenderPaintStyle::stroke);
    p->thickness(10);

    Path path;
    path->moveTo(6, 27);
    path->cubicTo(31.5f, 1.5f, 3.5f, 4.5f, 29, 29);
    canvas->drawPath(path, p);
}

DEF_SIMPLE_GM(bug6083, 100, 50, canvas)
{
    Paint p;
    p->color(0xFFFF0000);
    p->style(RenderPaintStyle::stroke);
    p->thickness(15);
    canvas->translate(-500, -130);
    Path path;
    path->moveTo(500.988f, 155.200f);
    path->lineTo(526.109f, 155.200f);
    Vec2D p1 = {526.109f, 155.200f};
    Vec2D p2 = {525.968f, 212.968f};
    Vec2D p3 = {526.109f, 241.840f};
    path->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
    canvas->drawPath(path, p);
    canvas->translate(50, 0);
    path = Path();
    p2 = Vec2D(525.968f, 213.172f);
    path->moveTo(500.988f, 155.200f);
    path->lineTo(526.109f, 155.200f);
    path->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
    canvas->drawPath(path, p);
}

//////////////////////////////////////////////////////////////////////////////

GMREGISTER(cubicpath, return new CubicPathGM;)
GMREGISTER(cubicclosepath, return new CubicClosePathGM;)
GMREGISTER(clippedcubic, return new ClippedCubicGM;)
GMREGISTER(clippedcubic2, return new ClippedCubic2GM;)
