/*
 * Copyright 2018 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 "rive/math/vec2d.hpp"
#include "rive/math/bezier_utils.hpp"
#include "rive/math/math_types.hpp"
#include "common/rand.hpp"

using namespace rivegm;
using namespace rive;
using namespace rive::math;

// Atomic mode uses 6:11 fixed point and clockwiseAtomic uses 7:8, so the
// winding number breaks if a shape has more than +/-32 [ == +/-2^(6-1)] levels
// of self overlap at any point.
constexpr static float SLICE_LENGTH = 2;

// Slices paths into sliver-size contours shaped like ice cream cones.
class MandolineSlicer
{
public:
    MandolineSlicer(Vec2D anchorPt, Vec2D p0) { this->reset(anchorPt, p0); }

    void reset(Vec2D anchorPt, Vec2D p0)
    {
        fPath = Path();
        fPath->fillRule(FillRule::evenOdd);
        fAnchorPt = anchorPt;
        fLastPt = p0;
    }

    void sliceLine(Vec2D pt)
    {
        if ((pt - fLastPt).length() < SLICE_LENGTH)
        {
            fPath->moveTo(fAnchorPt.x, fAnchorPt.y);
            fPath->lineTo(fLastPt.x, fLastPt.y);
            fPath->lineTo(pt.x, pt.y);
            fPath->close();
            fLastPt = pt;
            return;
        }
        float T = .5f;
        if (0 == T)
        {
            return;
        }
        Vec2D midpt = fLastPt * (1 - T) + pt * T;
        this->sliceLine(midpt);
        this->sliceLine(pt);
    }

    void sliceQuadratic(Vec2D p1, Vec2D p2)
    {
        if ((p2 - fLastPt).length() < SLICE_LENGTH)
        {
            fPath->moveTo(fAnchorPt.x, fAnchorPt.y);
            fPath->lineTo(fLastPt.x, fLastPt.y);
            fPath->cubicTo(fLastPt.x + (p1.x - fLastPt.x) * (2 / 3.f),
                           fLastPt.y + (p1.y - fLastPt.y) * (2 / 3.f),
                           p2.x + (p1.x - p2.x) * (2 / 3.f),
                           p2.y + (p1.y - p2.y) * (2 / 3.f),
                           p2.x,
                           p2.y);
            fPath->close();
            fLastPt = p2;
            return;
        }
        Vec2D P[4] = {fLastPt,
                      Vec2D::lerp(fLastPt, p1, 2 / 3.f),
                      Vec2D::lerp(p2, p1, 2 / 3.f),
                      p2},
              PP[7];
        math::chop_cubic_at(P, PP, .5f);

        this->sliceCubic(PP[1], PP[2], PP[3]);
        this->sliceCubic(PP[4], PP[5], PP[6]);
    }

    void sliceCubic(Vec2D p1, Vec2D p2, Vec2D p3)
    {
        if ((p3 - fLastPt).length() < SLICE_LENGTH)
        {
            fPath->moveTo(fAnchorPt.x, fAnchorPt.y);
            fPath->lineTo(fLastPt.x, fLastPt.y);
            fPath->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
            fPath->close();
            fLastPt = p3;
            return;
        }
        Vec2D P[4] = {fLastPt, p1, p2, p3}, PP[7];
        math::chop_cubic_at(P, PP, .5f);
        this->sliceCubic(PP[1], PP[2], PP[3]);
        this->sliceCubic(PP[4], PP[5], PP[6]);
    }

    rive::RenderPath* path() const { return fPath; }

private:
    Rand fRand;
    Path fPath;
    Vec2D fAnchorPt;
    Vec2D fLastPt;
};

class MandolineGM : public GM
{
public:
    MandolineGM() : GM(560, 475) {}

protected:
    ColorInt clearColor() const override { return 0xff000000; }

    void onDraw(Renderer* renderer) override
    {
        Paint paint;
        paint->color(0xc0c0c0c0);

        MandolineSlicer mandoline({0, -500}, {41, 43});
        mandoline.sliceCubic({5, 277}, {381, -74}, {243, 162});
        mandoline.sliceLine({41, 43});
        renderer->drawPath(mandoline.path(), paint);

        mandoline.reset({700, 700}, {357.049988f, 446.049988f});
        mandoline.sliceCubic({472.750000f, -71.950012f},
                             {639.750000f, 531.950012f},
                             {309.049988f, 347.950012f});
        mandoline.sliceLine({309.049988f, 419});
        mandoline.sliceLine({357.049988f, 446.049988f});
        renderer->drawPath(mandoline.path(), paint);

        renderer->save();
        renderer->translate(421, 105);
        renderer->scale(100, 81);
        mandoline.reset(
            {0, 500},
            {-cosf(degreesToRadians(-60)), sinf(degreesToRadians(-60))});
        mandoline.sliceQuadratic(
            {-2, 0},
            {-cosf(degreesToRadians(60)), sinf(degreesToRadians(60))});
        mandoline.sliceQuadratic(
            {-cosf(degreesToRadians(120)) * 2, sinf(degreesToRadians(120)) * 2},
            {1, 0});
        mandoline.sliceLine({0, 0});
        mandoline.sliceLine(
            {-cosf(degreesToRadians(-60)), sinf(degreesToRadians(-60))});
        renderer->drawPath(mandoline.path(), paint);
        renderer->restore();

        renderer->save();
        renderer->translate(150, 300);
        renderer->scale(75, 75);
        mandoline.reset({0, 0}, {1, 0});
        constexpr int nquads = 5;
        for (int i = 0; i < nquads; ++i)
        {
            float theta1 = 2 * PI / nquads * (i + .5f);
            float theta2 = 2 * PI / nquads * (i + 1);
            mandoline.sliceQuadratic({cosf(theta1) * 2, sinf(theta1) * 2},
                                     {cosf(theta2), sinf(theta2)});
        }
        renderer->drawPath(mandoline.path(), paint);
        renderer->restore();
    }
};

GMREGISTER(mandoline, return new MandolineGM;)
