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

using namespace rivegm;
using namespace rive;

/**
 * Renders overlapping shapes with colorburn against a checkerboard.
 */
class DstReadShuffle : public GM
{
public:
    DstReadShuffle() : GM(530, 690) {}

protected:
    enum ShapeType
    {
        kCircle_ShapeType,
        kRoundRect_ShapeType,
        kRect_ShapeType,
        kConvexPath_ShapeType,
        kPolygonFan_ShapeType,
        kConcavePath_ShapeType,
        kNumShapeTypes
    };

    static void fan_polygon(PathBuilder& builder,
                            float a0,
                            float a1,
                            float x,
                            float y,
                            float rx,
                            float ry,
                            int depth)
    {
        if (depth <= 0)
        {
            return;
        }
        float ai = (a0 + a1) / 2;
        builder.moveTo(cosf(a0) * rx + x, sinf(a0) * ry + y);
        builder.lineTo(cosf(ai) * rx + x, sinf(ai) * ry + y);
        builder.lineTo(cosf(a1) * rx + x, sinf(a1) * ry + y);
        builder.close();
        fan_polygon(builder, a0, ai, x, y, rx, ry, depth - 1);
        fan_polygon(builder, ai, a1, x, y, rx, ry, depth - 1);
    }

    void drawShape(Renderer* renderer, const Paint& paint, ShapeType type)
    {
        const float l = 0, t = 0, r = 75, b = 85;
        const AABB kRect(l, t, r, b);
        PathBuilder builder;
        switch (type)
        {
            case kCircle_ShapeType:
                builder.addOval(kRect);
                break;
            case kRoundRect_ShapeType:
                builder.addRRect(kRect, 15.f, 15.f);
                break;
            case kRect_ShapeType:
                builder.addRect(kRect);
                break;
            case kConvexPath_ShapeType:
            {
                builder.moveTo(l, t);
                builder.quadTo(r, t, r, b);
                builder.quadTo(l, b, l, t);
                break;
            }
            case kPolygonFan_ShapeType:
            {
                float a0 = 0, a1 = 2 * math::PI / 3, a2 = 4 * math::PI / 3,
                      a3 = 2 * math::PI;
                float x = (l + r) / 2, y = (t + b) / 2;
                float rx = (r - l) / 2, ry = (b - t) / 2;
                builder.moveTo(cosf(a0) * rx + x, sinf(a0) * ry + y);
                builder.lineTo(cosf(a1) * rx + x, sinf(a1) * ry + y);
                builder.lineTo(cosf(a2) * rx + x, sinf(a2) * ry + y);
                builder.lineTo(cosf(a3) * rx + x, sinf(a3) * ry + y);
                builder.close();
                fan_polygon(builder, a0, a1, x, y, rx, ry, 5);
                fan_polygon(builder, a1, a2, x, y, rx, ry, 5);
                fan_polygon(builder, a2, a3, x, y, rx, ry, 5);
                break;
            }
            case kConcavePath_ShapeType:
            {
                Vec2D points[5] = {
                    {70.f * cosf(-math::PI / 2), 70.f * sinf(-math::PI / 2)}};
                Mat2D rot = Mat2D::fromRotation(2 * math::PI / 5);
                for (int i = 1; i < 5; ++i)
                {
                    rot.mapPoints(points + i, points + i - 1, 1);
                }
                builder.moveTo(points[0].x + 50, points[0].y + 70);
                for (int i = 0; i < 5; ++i)
                {
                    auto [x, y] = points[(2 * i) % 5];
                    builder.lineTo(x + 50, y + 70);
                }
                builder.fillRule(FillRule::evenOdd);
                break;
            }
            default:
                break;
        }
        renderer->drawPath(builder.detach(), paint);
    }

    static ColorInt GetColor(Rand* random)
    {
        return 0x80000000 | (random->u32() & 0xffffff);
    }

    // static void DrawHairlines(Renderer* renderer) {
    //     if (canvas->imageInfo().alphaType() == kOpaque_SkAlphaType) {
    //         draw_clear(renderer, kBackground);
    //     } else {
    //         canvas->clear(SK_ColorTRANSPARENT);
    //     }
    //     SkPaint hairPaint;
    //     hairPaint.setStyle(SkPaint::kStroke_Style);
    //     hairPaint.setStrokeWidth(0);
    //     hairPaint.setAntiAlias(true);
    //     static constexpr int kNumHairlines = 12;
    //     SkPoint pts[] = {{3.f, 7.f}, {29.f, 7.f}};
    //     Rand colorRandom;
    //     SkMatrix rot;
    //     rot.setRotate(360.f / kNumHairlines, 15.5f, 12.f);
    //     rot.postTranslate(3.f, 0);
    //     for (int i = 0; i < 12; ++i) {
    //         hairPaint.setColor(GetColor(&colorRandom));
    //         canvas->drawLine(pts[0], pts[1], hairPaint);
    //         rot.mapPoints(pts, 2);
    //     }
    // }

    void onDraw(Renderer* renderer) override
    {
        Paint p;
        p->color(0x80808080);
        draw_rect(renderer, {0, 0, 530, 690}, p);

        float y = 5;
        for (int i = 0; i < kNumShapeTypes; i++)
        {
            Rand colorRandom;
            ShapeType shapeType = static_cast<ShapeType>(i);
            float x = 5;
            for (int r = 0; r <= 15; r++)
            {
                Paint p;
                p->color(GetColor(&colorRandom));
                // In order to get some op combining on the GPU backend we do 2
                // src over for each xfer mode which requires a dst read
                constexpr static BlendMode blendModes[] = {
                    BlendMode::srcOver,
                    BlendMode::screen,
                    BlendMode::overlay,
                    BlendMode::darken,
                    BlendMode::lighten,
                    BlendMode::colorDodge,
                    BlendMode::colorBurn,
                    BlendMode::hardLight,
                    BlendMode::softLight,
                    BlendMode::exclusion,
                    BlendMode::hue,
                    BlendMode::saturation,
                    BlendMode::color,
                    BlendMode::luminosity,
                };
                p->blendMode(blendModes[r % std::size(blendModes)]);
                renderer->save();
                renderer->translate(x, y);
                this->drawShape(renderer, p, shapeType);
                renderer->restore();
                x += 15;
            }
            y += 110;
        }
        // // Draw hairlines to a surface and then draw that to the main canvas
        // with a zoom so that
        // // it is easier to see how they blend.
        // SkImageInfo info;
        // // Recording canvases don't have a color type.
        // if (SkColorType::kUnknown_SkColorType ==
        // canvas->imageInfo().colorType()) {
        //     info = SkImageInfo::MakeN32Premul(35, 35);
        // } else {
        //     info = SkImageInfo::Make(35, 35,
        //                              canvas->imageInfo().colorType(),
        //                              canvas->imageInfo().alphaType(),
        //                              canvas->imageInfo().refColorSpace());
        // }
        // auto surf = canvas->makeSurface(info);
        // if (!surf) {
        //     // Fall back to raster. Raster supports only one of the 8 bit
        //     per-channel RGBA or BGRA
        //     // formats. This fall back happens when running with
        //     --preAbandonGpuContext. if ((info.colorType() ==
        //     kRGBA_8888_SkColorType ||
        //          info.colorType() == kBGRA_8888_SkColorType) &&
        //         info.colorType() != kN32_SkColorType) {
        //         info = SkImageInfo::Make(35, 35,
        //                                  kN32_SkColorType,
        //                                  canvas->imageInfo().alphaType(),
        //                                  canvas->imageInfo().refColorSpace());
        //     }
        //     surf = SkSurface::MakeRaster(info);
        //     SkASSERT(surf);
        // }
        // canvas->scale(5.f, 5.f);
        // canvas->translate(67.f, 10.f);
        // DrawHairlines(surf->getCanvas());
        // canvas->drawImage(surf->makeImageSnapshot(), 0.f, 0.f);
    }

private:
    static constexpr ColorInt kBackground = 0xFFCCCCCC;
    using INHERITED = GM;
};

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

GMREGISTER(dstreadshuffle, return new DstReadShuffle;)
