// Copyright 2023 Google LLC
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

#include "modules/bentleyottmann/include/EventQueue.h"
#include "tests/Test.h"

namespace bentleyottmann {
class EventQueueTestingPeer {
public:
    static Event NextEvent(EventQueue* eq) {
        SkASSERT(eq->hasMoreEvents());

        auto firstElement = eq->fQueue.begin();

        // Extract event at the beginning of the queue.
        Event event = *firstElement;

        // Remove the beginning element from the queue.
        eq->fQueue.erase(firstElement);

        return event;
    }
};
}  // namespace bentleyottmann

using namespace bentleyottmann;

DEF_TEST(BO_EventQueueOrdering, reporter) {
    { // Check that event types are ordered correctly.
        EventQueue::Queue q;

        // Insert the events in reverse order.
        Point eventPoint = {100, 100};
        Segment s = {{100, 100}, {200, 200}};
        q.insert(Event{eventPoint, Upper{s}});
        Segment s0 = {{50, 50}, {150, 150}},
                s1 = {{150, 50}, {50, 150}};
        q.insert(Event{eventPoint, Cross{s0, s1}});
        q.insert(Event{eventPoint, Lower{}});

        // Make sure that the events are in the right order.
        auto cursor = q.begin();
        REPORTER_ASSERT(reporter, std::holds_alternative<Lower>(cursor->type));
        ++cursor;
        REPORTER_ASSERT(reporter, std::holds_alternative<Cross>(cursor->type));
        ++cursor;
        REPORTER_ASSERT(reporter, std::holds_alternative<Upper>(cursor->type));
    }
}

DEF_TEST(BO_EventQueueBasic, reporter) {
    {
        EventQueue::Queue q;
        EventQueue eq{std::move(q)};
        REPORTER_ASSERT(reporter, !eq.hasMoreEvents());
    }
    {
        EventQueue::Queue q;
        Point eventPoint = {100, 100};
        q.insert({eventPoint, Lower{} });
        EventQueue eq{std::move(q)};
        {
            REPORTER_ASSERT(reporter, eq.hasMoreEvents());
            Event e = EventQueueTestingPeer::NextEvent(&eq);
            REPORTER_ASSERT(reporter, e.where == eventPoint);
            REPORTER_ASSERT(reporter, !eq.hasMoreEvents());
        }
    }
    { // Check that Lower events are de-duplicated.
        EventQueue::Queue q;
        Point eventPoint = {100, 100};
        q.insert({eventPoint, Lower{}});
        q.insert({eventPoint, Lower{}});
        EventQueue eq{std::move(q)};
        {
            // There should be only one lower because of queue de-duplication
            REPORTER_ASSERT(reporter, eq.hasMoreEvents());
            auto [p, _] = EventQueueTestingPeer::NextEvent(&eq);
            REPORTER_ASSERT(reporter, p == eventPoint);
            REPORTER_ASSERT(reporter, !eq.hasMoreEvents());
        }
    }
    { // Check that Lower distinct Lower events are distinct.
        EventQueue::Queue q;
        Point eventPoint1 = {100, 100};
        Point eventPoint2 = {100, 101};

        q.insert({eventPoint1, Lower{}});
        q.insert({eventPoint2, Lower{}});
        EventQueue eq{std::move(q)};
        {
            // There should be only one lower because of queue de-duplication
            REPORTER_ASSERT(reporter, eq.hasMoreEvents());
            auto [p, _] = EventQueueTestingPeer::NextEvent(&eq);
            REPORTER_ASSERT(reporter, p == eventPoint1);
        }
        {
            // There should be only one lower because of queue de-duplication
            REPORTER_ASSERT(reporter, eq.hasMoreEvents());
            auto [p, _] = EventQueueTestingPeer::NextEvent(&eq);
            REPORTER_ASSERT(reporter, p == eventPoint2);
            REPORTER_ASSERT(reporter, !eq.hasMoreEvents());
        }
    }
    { // Check that non-Lower events are separate.
        EventQueue::Queue q;
        Segment s0 {{0, 0}, {100, 100}};
        Segment s1 {{0, 0}, {-100, 100}};
        q.insert({Point{0, 0}, Upper{s0}});
        q.insert({Point{0, 0}, Upper{s1}});
        EventQueue eq{std::move(q)};
        {
            REPORTER_ASSERT(reporter, eq.hasMoreEvents());
            Event e = EventQueueTestingPeer::NextEvent(&eq);
            Point upperPt = Point{0, 0};
            REPORTER_ASSERT(reporter, e.where == upperPt);
            REPORTER_ASSERT(reporter, e.type.index() == 2);
            Upper upper = std::get<Upper>(e.type);
            REPORTER_ASSERT(reporter, !(upper < Upper{s1}) && !(Upper{s1} < upper));
            Event e2 = EventQueueTestingPeer::NextEvent(&eq);
            REPORTER_ASSERT(reporter, e2.where == upperPt);
            REPORTER_ASSERT(reporter, e2.type.index() == 2);
            Upper upper2 = std::get<Upper>(e2.type);
            REPORTER_ASSERT(reporter, !(upper2 < Upper{s0}) && !(Upper{s0} < upper2));
            REPORTER_ASSERT(reporter, !eq.hasMoreEvents());
        }
    }
}

enum HasDeletions {
    kHasDeletions,   // The handleDeletions call should be called.
    kHasNoDeletions  // The handleDeletions call should not be called.
};

struct TestEventHandler : public SweepLineInterface {
    TestEventHandler(skiatest::Reporter*r,
                     Point eventPoint,
                     SkSpan<const Segment> deletions,
                     SkSpan<const Segment> insertions,
                     SkSpan<const Crossing> crossings,
                     HasDeletions hasDeletions = kHasDeletions)
        : fR(r)
        , fCandidateEventPoint{eventPoint}
        , fDeletions{deletions.begin(), deletions.end()}
        , fInsertions{insertions.begin(), insertions.end()}
        , fCrossings{crossings.begin(), crossings.end()}
        , fHasDeletions{hasDeletions} {}

    void handleDeletions(Point eventPoint,
                         const DeletionSegmentSet& removing) override {
        if (fHasDeletions == kHasNoDeletions) {
            REPORTER_ASSERT(fR, false, "There should be no deletions.");
            return;
        }

        REPORTER_ASSERT(fR, eventPoint == fCandidateEventPoint);

        REPORTER_ASSERT(fR, removing.size() == fDeletions.size());

        for (const Segment& s : fDeletions) {
            REPORTER_ASSERT(fR, removing.find(s) != removing.end());
        }
    }

    void
    handleInsertionsAndCheckForNewCrossings(Point eventPoint,
                                            const InsertionSegmentSet& inserting,
                                            EventQueueInterface* queue) override {
        REPORTER_ASSERT(fR, eventPoint == fCandidateEventPoint);

        REPORTER_ASSERT(fR, inserting.size() == fInsertions.size());

        for (const Segment& s : fInsertions) {
            REPORTER_ASSERT(fR, inserting.find(s) != inserting.end());
        }

        for (const Crossing& crossing : fCrossings) {
            auto [s0, s1, pt] = crossing;
            queue->addCrossing(pt, s0, s1);
        }
    }

    skiatest::Reporter* const fR;
    const Point fCandidateEventPoint;
    std::vector<Segment> fDeletions;
    std::vector<Segment> fInsertions;
    std::vector<Crossing> fCrossings;
    const HasDeletions fHasDeletions;
};

DEF_TEST(BO_EventQueueHandlerInterface, reporter) {
    { // Check that a Lower event is added while processing the Upper event.
        EventQueue::Queue q;
        static constexpr Point eventPoint = {100, 100};
        static constexpr Point endPoint = {200, 200};
        static constexpr Segment s = {eventPoint, endPoint};
        q.insert(Event{eventPoint, Upper{s}});
        EventQueue eq{std::move(q)};

        REPORTER_ASSERT(reporter, eq.hasMoreEvents());


        TestEventHandler eh1{reporter, eventPoint, {}, {{s}}, {}, kHasNoDeletions};
        eq.handleNextEventPoint(&eh1);

        TestEventHandler eh2{reporter, endPoint, {}, {}, {}};
        eq.handleNextEventPoint(&eh2);

        REPORTER_ASSERT(reporter, !eq.hasMoreEvents());
    }

    { // Check an entire crossing event.
        EventQueue::Queue q;
        static constexpr Point b0 = {100, 100};
        static constexpr Point e0 = {200, 200};
        static constexpr Segment s0 = {b0, e0};
        static constexpr Point b1 = {200, 100};
        static constexpr Point e1 = {100, 200};
        static constexpr Segment s1 = {b1, e1};
        static constexpr Point crossingPoint = {150, 150};

        // Load crossing segments into the queue
        q.insert(Event{b0, Upper{s0}});
        q.insert(Event{b1, Upper{s1}});
        EventQueue eq{std::move(q)};

        REPORTER_ASSERT(reporter, eq.hasMoreEvents());

        TestEventHandler eh1{reporter, b0, {}, {{s0}}, {}, kHasNoDeletions};
        eq.handleNextEventPoint(&eh1);

        TestEventHandler eh2{reporter, b1, {}, {{s1}}, {{{s0, s1, crossingPoint}}}, kHasNoDeletions};
        eq.handleNextEventPoint(&eh2);

        TestEventHandler eh3{reporter, crossingPoint, {{s0, s1}}, {{s0, s1}}, {}};
        eq.handleNextEventPoint(&eh3);

        TestEventHandler eh4{reporter, e1, {}, {}, {}};
        eq.handleNextEventPoint(&eh4);

        TestEventHandler eh5{reporter, e0, {}, {}, {}};
        eq.handleNextEventPoint(&eh5);

        REPORTER_ASSERT(reporter, !eq.hasMoreEvents());
    }
}

