/*
 * Copyright 2022 Rive
 */

#include "gm.hpp"
#include "gmutils.hpp"
#include "rive/math/hit_test.hpp"

using namespace rivegm;

using V2D = rive::Vec2D;

class IPathSink
{
public:
    virtual ~IPathSink() {}

    virtual void move(V2D) = 0;
    virtual void line(V2D) = 0;
    virtual void cubic(V2D, V2D, V2D) = 0;
    virtual void close() = 0;
};

class RenderPathSink : public IPathSink
{
    rive::RenderPath* m_Path;

public:
    RenderPathSink(rive::RenderPath* rp) : m_Path(rp) {}

    void move(V2D v) override { m_Path->move(v); }
    void line(V2D v) override { m_Path->line(v); }
    void cubic(V2D a, V2D b, V2D c) override { m_Path->cubic(a, b, c); }
    void close() override { m_Path->close(); }
};

class HitTesterSink : public IPathSink
{
    rive::HitTester* m_HT;

public:
    HitTesterSink(rive::HitTester* ht) : m_HT(ht) {}

    void move(V2D v) override { m_HT->move(v); }
    void line(V2D v) override { m_HT->line(v); }
    void cubic(V2D a, V2D b, V2D c) override { m_HT->cubic(a, b, c); }
    void close() override { m_HT->close(); }
};

class BounderSink : public IPathSink
{
    rive::AABB m_Bounds;
    bool m_First = true;

    void join(V2D v)
    {
        assert(!m_First);
        m_Bounds.minX = std::min(m_Bounds.minX, v.x);
        m_Bounds.minY = std::min(m_Bounds.minY, v.y);
        m_Bounds.maxX = std::max(m_Bounds.maxX, v.x);
        m_Bounds.maxY = std::max(m_Bounds.maxY, v.y);
    }

public:
    BounderSink() {}

    rive::AABB bounds() const
    {
        assert(!m_First);
        return m_Bounds;
    }

    void move(V2D v) override
    {
        if (m_First)
        {
            m_Bounds.minX = m_Bounds.maxX = v.x;
            m_Bounds.minY = m_Bounds.maxY = v.y;
            m_First = false;
        }
        else
        {
            this->join(v);
        }
    }
    void line(V2D v) override { this->join(v); }
    void cubic(V2D a, V2D b, V2D c) override
    {
        this->join(a);
        this->join(b);
        this->join(c);
    }
    void close() override {}
};

class HitTestGM : public GM
{
    rive::FillRule m_FillRule;

public:
    HitTestGM(rive::FillRule fr) : GM(320, 460), m_FillRule(fr) {}

    void onDraw(rive::Renderer* renderer) override
    {
        auto make = [](IPathSink& sink) {
            sink.move({100, 10});
            sink.line({200, 100});
            sink.line({50, 230});
            sink.close();

            sink.move({120, 100});
            sink.line({300, 40});
            sink.line({250, 200});
            sink.close();

            sink.move({10, 300});
            sink.cubic({110, 150}, {210, 450}, {310, 300});
            sink.close();
        };

        Path path;
        path->fillRule(m_FillRule);
        {
            RenderPathSink rps(path.get());
            make(rps);
        }

        BounderSink bounder;
        make(bounder);

        const int tol = 4;
        const auto bounds = bounder.bounds().round().inset(-tol, -tol);

        Paint paint;
        paint->color(0xFFDDDDDD);
        draw_rect(renderer, bounds, paint.get());

        paint->color(0xFFFF0000);
        renderer->drawPath(path.get(), paint.get());

        rive::HitTester tester;
        HitTesterSink hts(&tester);

        paint->color(0x800000FF);
        for (int y = bounds.top; y < bounds.bottom; ++y)
        {
            for (int x = bounds.left; x < bounds.right; ++x)
            {
                const int t = 1;
                rive::IAABB r = {x, y, x + 1, y + 1};
                tester.reset(r.inset(-t, -t));

                make(hts);
                if (tester.test(m_FillRule))
                {
                    draw_rect(renderer, r, paint.get());
                }
            }
        }
    }
};

GMREGISTER(hittest_evenOdd, return new HitTestGM(rive::FillRule::evenOdd))
GMREGISTER(hittest_nonZero, return new HitTestGM(rive::FillRule::nonZero))
