// 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/SweepLine.h"

#include "modules/bentleyottmann/include/EventQueueInterface.h"
#include "modules/bentleyottmann/include/Point.h"
#include "tests/Test.h"

#include <iterator>

using namespace bentleyottmann;

namespace bentleyottmann {
struct SweepLineTestingPeer {
    SweepLineTestingPeer(SweepLine* sl) : fSL{sl} {}
    void verifySweepLine(int32_t y) const {
        fSL->verify(y);
    }
    void insertSegment(int i, const Segment& s) {
        auto& v = fSL->fSweepLine;
        v.insert(v.begin() + i, s);
    }
    size_t size() const {
        return fSL->fSweepLine.size();
    }

    const std::vector<Segment>& sweepLine() const {
        return fSL->fSweepLine;
    }

    SweepLine* const fSL;
};
}  // namespace bentleyottmann

using TP = SweepLineTestingPeer;

DEF_TEST(BO_SweepLineSearch, reporter) {
    {
        SweepLine sweepLine;
        TP tp{&sweepLine};

        Point p0 = {-100, -100},
              p1 = { 100,  100},
              p2 = { 100, -100},
              p3 = {-100,  100};
        Segment s0 = {p0, p1},
                s1 = {p2, p3};
        tp.insertSegment(1, s0);
        tp.insertSegment(2, s1);

        auto& sl = tp.sweepLine();

        const Point crossing = {0, 0};

        const auto l = std::lower_bound(sl.begin(), sl.end(), crossing,
                                        rounded_point_less_than_segment_in_x_lower);

        const auto r = std::lower_bound(l, sl.end(), crossing,
                                        rounded_point_less_than_segment_in_x_upper);

        // Remember that the index is off by 1 because of the left sentinel.
        REPORTER_ASSERT(reporter, std::distance(sl.begin(), l) == 1);
        REPORTER_ASSERT(reporter, std::distance(sl.begin(), r) == 3);
    }
    {
        SweepLine sweepLine;
        TP tp{&sweepLine};

        // No longer cross at {0, 0}, but still round to {0, 0}.
        Point p0 = {-100, -100},
              p1 = {  99,  100},
              p2 = { 100, -100},
              p3 = {-101,  100};
        Segment s0 = {p0, p1},
                s1 = {p2, p3};
        tp.insertSegment(1, s0);
        tp.insertSegment(2, s1);

        auto& sl = tp.sweepLine();

        const Point crossing = {0, 0};

        const auto l = std::lower_bound(sl.begin(), sl.end(), crossing,
                                        rounded_point_less_than_segment_in_x_lower);

        const auto r = std::lower_bound(l, sl.end(), crossing,
                                        rounded_point_less_than_segment_in_x_upper);

        // Remember that the index is off by 1 because of the left sentinel.
        REPORTER_ASSERT(reporter, std::distance(sl.begin(), l) == 1);
        REPORTER_ASSERT(reporter, std::distance(sl.begin(), r) == 3);
    }
}

DEF_TEST(BO_SweepLineInsert, reporter) {
    {
        SweepLine sweepLine;
        TP tp{&sweepLine};
        tp.verifySweepLine(0);
    }
    { // Handle insert
        SweepLine sweepLine;
        TP tp{&sweepLine};

        class FailIfEventQueueAdd : public EventQueueInterface {
        public:
            void addCrossing(Point crossingPoint, const Segment& s0, const Segment& s1) override {
                SK_ABORT("There should be no crossings.");
            }
        } eventQueue;
        InsertionSegmentSet insertions;
        Segment s = {{0, -100}, {0, 100}};

        insertions.insert(s);

        tp.verifySweepLine(-99);

        sweepLine.handleInsertionsAndCheckForNewCrossings(
                Point{0, -100}, insertions, &eventQueue);
        REPORTER_ASSERT(reporter, tp.size() == 3);

        tp.verifySweepLine(-99);
    }

    { // Handle 3 segments where removing middle segment introduces crossing
        SweepLine sweepLine;
        TP tp{&sweepLine};

        Point p0 = {-100, -100},
              p1 = { 100,  100},
              p2 = { 100, -100},
              p3 = {-100,  100},
              p4 = {   0, -100},
              p5 = {   0,  -50};
        Segment s0 = {p0, p1},
                s1 = {p2, p3},
                s2 = {p4, p5};

        class CollectCrossings : public EventQueueInterface {
        public:
            void addCrossing(Point crossingPoint, const Segment& s0, const Segment& s1) override {
                fCrossing.push_back({s0, s1, crossingPoint});
            }
            std::vector<Crossing> fCrossing;
        } eventQueue;

        { // Simulate handling Upper s0
            InsertionSegmentSet insertions;
            insertions.insert(s0);
            tp.verifySweepLine(-99);
            sweepLine.handleInsertionsAndCheckForNewCrossings(
                    p0, insertions, &eventQueue);
            REPORTER_ASSERT(reporter, tp.size() == 3);
            REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 0);
            tp.verifySweepLine(-99);
        }
        { // Simulate handling Upper s2
            InsertionSegmentSet insertions;
            insertions.insert(s2);
            tp.verifySweepLine(-99);
            sweepLine.handleInsertionsAndCheckForNewCrossings(
                    p4, insertions, &eventQueue);
            REPORTER_ASSERT(reporter, tp.size() == 4);
            REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 0);
            tp.verifySweepLine(-99);
        }
        { // Simulate handling Upper s1
            InsertionSegmentSet insertions;
            insertions.insert(s1);
            tp.verifySweepLine(-99);
            sweepLine.handleInsertionsAndCheckForNewCrossings(
                    p2, insertions, &eventQueue);
            REPORTER_ASSERT(reporter, tp.size() == 5);
            REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 0);
            tp.verifySweepLine(-99);
        }
        { // Simulate handling Lower s2 which introduces a crossing
            DeletionSegmentSet deletions;  // empty set because this will be a lower event
            InsertionSegmentSet insertions;
            tp.verifySweepLine(-51);
            sweepLine.handleDeletions(p5, deletions);
            REPORTER_ASSERT(reporter, tp.size() == 4);
            sweepLine.handleInsertionsAndCheckForNewCrossings(
                    p5, insertions, &eventQueue);
            REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 1);
            tp.verifySweepLine(-51);
        }
        { // Handle crossing
            DeletionSegmentSet deletions{s0, s1};  // empty set because this will be a lower event
            InsertionSegmentSet insertions{s0, s1};
            tp.verifySweepLine(-1);  // Check above the crossing
            sweepLine.handleDeletions({0,0}, deletions);
            sweepLine.handleInsertionsAndCheckForNewCrossings(
                    {0,0}, insertions, &eventQueue);
            REPORTER_ASSERT(reporter, tp.size() == 4);
            REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 1);
            tp.verifySweepLine(1);  // Make sure things are correct after deletion
        }
        { // Handle deletion s1
            DeletionSegmentSet deletions{};  // empty set because this will be a lower event
            InsertionSegmentSet insertions{};
            tp.verifySweepLine(99);  // Check above the crossing
            sweepLine.handleDeletions(p3, deletions);
            sweepLine.handleInsertionsAndCheckForNewCrossings(
                    p3, insertions, &eventQueue);
            REPORTER_ASSERT(reporter, tp.size() == 3);
            REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 1);
            tp.verifySweepLine(99);  // Make sure sentinels are correct.
        }
        { // Handle deletion s0
            DeletionSegmentSet deletions{};  // empty set because this will be a lower event
            InsertionSegmentSet insertions{};
            tp.verifySweepLine(99);  // Check above the crossing
            sweepLine.handleDeletions(p1, deletions);
            sweepLine.handleInsertionsAndCheckForNewCrossings(
                    p1, insertions, &eventQueue);
            REPORTER_ASSERT(reporter, tp.size() == 2);
            REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 1);
            tp.verifySweepLine(99);  // Make sure sentinels are correct.
        }
    }
}
