#include <maps/wikimap/mapspro/services/editor/src/polyline_sticker/polyline_sticker.h>
#include <maps/wikimap/mapspro/services/editor/src/utils.h>

#include <library/cpp/testing/unittest/registar.h>

namespace maps::wiki::tests {

Y_UNIT_TEST_SUITE(polyline_sticker_tests)
{

class TestGeom final : public polyline_sticker::IPolyline
{
public:
    TestGeom(polyline_sticker::ID id,
        std::vector<geolib3::Point2> vertexes,
        bool closed = true)
        : id_(id)
        , vertexes_(vertexes)
        , closed_(closed)
    {
    }
    size_t numLineStrings() const override { return 1; }
    polyline_sticker::ID id() const override { return id_; };
    size_t numLineStringVertexes(size_t) const override { return vertexes_.size(); };
    geolib3::Point2 lineStringVertex(size_t, size_t v) const override  { return vertexes_[v]; };
    bool isClosed() const override { return closed_; };

private:
    polyline_sticker::ID id_;
    std::vector<geolib3::Point2> vertexes_;
    bool closed_;
};

Y_UNIT_TEST(test_no_change)
{
    TestGeom p1(1, {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}});
    TestGeom p2(2, {{2, 2}, {2, 3}, {3, 3}, {3, 2}, {2, 2}});
    polyline_sticker::PolylineSticker sticker(0.1, 0.1);
    sticker.addPolyline(&p1, false);
    sticker.addPolyline(&p2, false);
    UNIT_ASSERT(!sticker.processMesh());
}

Y_UNIT_TEST(big_and_small_touch)
{
    TestGeom p1(1, {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}});
    TestGeom p2(2, {{1, 0}, {1, 0.5}, {2, 0.5}, {2, 0}, {1, 0}});
    polyline_sticker::PolylineSticker sticker(0.1, 0.1);
    sticker.addPolyline(&p1, false);
    sticker.addPolyline(&p2, false);
    UNIT_ASSERT(sticker.processMesh());
    auto resultP1 = sticker.resultPolyline(p1.id());
    UNIT_ASSERT(resultP1);
    UNIT_ASSERT_EQUAL(resultP1->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(resultP1->numLineStringVertexes(0), 6);
}

Y_UNIT_TEST(big_and_small_treshold_apart)
{
    TestGeom p1(1, {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}});
    TestGeom p2(2, {{1.01, 0}, {1.01, 0.5}, {2, 0.5}, {2, 0}, {1.01, 0}});
    polyline_sticker::PolylineSticker sticker(0.1, 0.1);
    sticker.addPolyline(&p1, false);
    sticker.addPolyline(&p2, false);
    UNIT_ASSERT(sticker.processMesh());
    auto resultP1 = sticker.resultPolyline(p1.id());
    UNIT_ASSERT(resultP1);
    UNIT_ASSERT_EQUAL(resultP1->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(resultP1->numLineStringVertexes(0), 6);
}

Y_UNIT_TEST(check_no_deadloop_double_big_and_small_treshold_apart)
{
    TestGeom p1(1, {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}});
    TestGeom p11(11, {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}});
    TestGeom p2(2, {{1.01, 0}, {1.01, 0.5}, {2, 0.5}, {2, 0}, {1.01, 0}});
    polyline_sticker::PolylineSticker sticker(0.1, 0.1);
    sticker.addPolyline(&p1, false);
    sticker.addPolyline(&p2, false);
    sticker.addPolyline(&p11, false);
    UNIT_ASSERT(sticker.processMesh());
    auto resultP1 = sticker.resultPolyline(p1.id());
    UNIT_ASSERT(resultP1);
    UNIT_ASSERT_EQUAL(resultP1->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(resultP1->numLineStringVertexes(0), 6);
    auto resultP11 = sticker.resultPolyline(p1.id());
    UNIT_ASSERT(resultP11);
    UNIT_ASSERT_EQUAL(resultP11->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(resultP11->numLineStringVertexes(0), 6);
}

Y_UNIT_TEST(check_remove_duplicate_vertexes)
{
    TestGeom p1(1, {{0, 0}, {0, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 0}, {0, 0}});
    polyline_sticker::PolylineSticker sticker(0.1, 0.1);
    sticker.addPolyline(&p1, false);
    UNIT_ASSERT(sticker.processMesh());
    auto resultP1 = sticker.resultPolyline(p1.id());
    UNIT_ASSERT(resultP1);
    UNIT_ASSERT_EQUAL(resultP1->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(resultP1->numLineStringVertexes(0), 5);
}

Y_UNIT_TEST(check_closed_and_open)
{
    TestGeom closed(1, {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}, true);
    TestGeom open(2, {{1, 0.5}, {2, 0.5}}, false);
    polyline_sticker::PolylineSticker sticker(0.1, 0.1);
    sticker.addPolyline(&closed, false);
    sticker.addPolyline(&open, false);
    UNIT_ASSERT(sticker.processMesh());
    auto result1 = sticker.resultPolyline(closed.id());
    UNIT_ASSERT(result1);
    UNIT_ASSERT_EQUAL(result1->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(result1->numLineStringVertexes(0), 6);
    auto result2 = sticker.resultPolyline(open.id());
    UNIT_ASSERT(result2);
    UNIT_ASSERT_EQUAL(result2->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(result2->numLineStringVertexes(0), 2);
}

Y_UNIT_TEST(check_open_and_open)
{
    //   _ _
    // _|_|
    //

    TestGeom open1(1, {{0, 0}, {1, 0}, {1, 0.5}}, false);
    TestGeom open2(2, {{0.5, 0}, {0.5, 0.5}, {1.5, 0.5}}, false);

    polyline_sticker::PolylineSticker sticker(0.1, 0.1);
    sticker.addPolyline(&open1, false);
    sticker.addPolyline(&open2, false);
    UNIT_ASSERT(sticker.processMesh());
    auto result1 = sticker.resultPolyline(open1.id());
    UNIT_ASSERT(result1);
    UNIT_ASSERT_EQUAL(result1->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(result1->numLineStringVertexes(0), 4);
    auto result2 = sticker.resultPolyline(open2.id());
    UNIT_ASSERT(result2);
    UNIT_ASSERT_EQUAL(result2->numLineStrings(), 1);
    UNIT_ASSERT_EQUAL(result2->numLineStringVertexes(0), 4);
}

namespace {
GeometryPtr createSolidPolygon(const polyline_sticker::IPolyline& polygon)
{
    std::unique_ptr<std::vector<geos::geom::Coordinate>>
        coords(new std::vector<geos::geom::Coordinate>);
    for (size_t i = 0; i < polygon.numLineStringVertexes(0); ++i) {
        coords->emplace_back(
            polygon.lineStringVertex(0, i).x(),
            polygon.lineStringVertex(0, i).y());
    }
    return maps::wiki::createPolygon(
        coords.release(),
        nullptr,
        SpatialRefSystem::Mercator,
        AntiMeridianAdjustPolicy::Ignore);
}

std::string validateAndDumpPoly(const polyline_sticker::IPolyline& polygon)
{
    Geom poly(createSolidPolygon(polygon));
    UNIT_ASSERT(poly->isValid());
    std::ostringstream os;
    os.precision(12);
    poly.geoJson(os, SpatialRefSystem::Geodetic);
    return os.str();
}

} // namespace

Y_UNIT_TEST(check_from_prod)
{
    // Test covers issue with case where huge existing poly1 contains almost same poly2
    // and small poly3 sharing border with them
    // Then poly4 added inside poly3 touching same border
    // Before fix poly2 were damaging poly1 and poly4 not aligned at all
    // Now all polys get inserted vertexes of each other
    // poly4 -> poly3
    // poly4, poly3 -> poly2
    // poly4, poly3 -> poly1

    constexpr double TOLERANCE = 0.3;
    TestGeom poly1(3932244170, {{4155797.67192661, 7445949.6569652}, {4156157.96402641, 7446119.85040548}, {4156256.29364583, 7445911.70831225}, {4156194.67463413, 7445882.61565196}, {4156217.22484602, 7445837.58449034}, {4156054.13989957, 7445766.0948801}, {4156057.30638249, 7445758.5212455}, {4156163.6164962, 7445504.24214118}, {4156189.33129857, 7445496.96591021}, {4156361.20859236, 7445533.15047829}, {4156395.82895399, 7445375.43420669}, {4156388.03658964, 7445346.52646326}, {4156101.9454983, 7445424.20397966}, {4156106.28695844, 7445440.13288389}, {4156070.33076291, 7445449.96555732}, {4156061.20256467, 7445449.17894298}, {4156048.55399885, 7445446.1654242}, {4156051.40644948, 7445430.10356997}, {4156023.13129882, 7445424.99059144}, {4156020.45963104, 7445439.54292388}, {4155938.52848581, 7445424.99059144}, {4155912.03444701, 7445420.07426914}, {4155855.70678466, 7445411.81485476}, {4155786.13210292, 7445578.57808501}, {4155823.67538042, 7445594.33012123}, {4155807.50544514, 7445642.2951798}, {4155782.12460125, 7445696.96644401}, {4155852.58983892, 7445729.61206004}, {4155892.96953705, 7445748.7186595}, {4155797.67192661, 7445949.6569652}}, true);
    TestGeom poly2(3932247500, {{4155786.13210292, 7445578.57808501}, {4155855.70678466, 7445411.81485476}, {4155912.03444701, 7445420.07426914}, {4156020.45963104, 7445439.54292388}, {4156023.13129882, 7445424.99059144}, {4156051.40644948, 7445430.10356997}, {4156048.51214272, 7445446.22913993}, {4156061.20256467, 7445449.17894298}, {4156070.33076291, 7445449.96555732}, {4156106.28695844, 7445440.13288389}, {4156101.9454983, 7445424.20397966}, {4156388.03658964, 7445346.52646326}, {4156395.82895399, 7445375.43420669}, {4156361.20859236, 7445533.15047829}, {4156189.33129857, 7445496.96591021}, {4156163.6164962, 7445504.24214118}, {4156054.18943675, 7445766.19104767}, {4156217.27249076, 7445837.57957375}, {4156194.67463413, 7445882.61565196}, {4156256.45695152, 7445911.5254134}, {4156157.93920217, 7446119.79690983}, {4155797.70932996, 7445949.6785985}, {4155892.63279559, 7445748.73930893}, {4155782.12460125, 7445696.96644401}, {4155807.50544515, 7445642.295181}, {4155823.75809081, 7445594.31065216}, {4155786.13210292, 7445578.57808501}}, true);
    TestGeom poly3(4480469117, {{4156226.21992698, 7445897.37662007}, {4156202.69821209, 7445886.37011862}, {4156191.77350024, 7445909.50348598}, {4156215.24124662, 7445920.58610849}, {4156226.21992698, 7445897.37662007}}, true);
    TestGeom poly4(4481375090, {{4156209.39411043, 7445889.49587035}, {4156205.12523075, 7445900.99159443}, {4156214.03909708, 7445904.30171293}, {4156217.73964831, 7445893.40892194}, {4156209.39411043, 7445889.49587035}}, true);

    auto checkPolygonRings = [](const polyline_sticker::IPolyline& polygon) {
        for (size_t r = 0; r < polygon.numLineStrings(); ++r) {
            const auto firstVertex = polygon.lineStringVertex(r, 0);
            const auto lastVertex = polygon.lineStringVertex(r, polygon.numLineStringVertexes(r) - 1);
            UNIT_ASSERT_EQUAL(firstVertex, lastVertex);
        }
    };
    checkPolygonRings(poly1);
    checkPolygonRings(poly2);
    checkPolygonRings(poly3);
    checkPolygonRings(poly4);

    polyline_sticker::PolylineSticker sticker(TOLERANCE, TOLERANCE);
    sticker.addPolyline(&poly1, true);
    sticker.addPolyline(&poly2, true);
    sticker.addPolyline(&poly3, true);
    sticker.addPolyline(&poly4, false);

    UNIT_ASSERT(sticker.processMesh());
    const auto result1 = *sticker.resultPolyline(poly1.id());
    UNIT_ASSERT(result1.isClosed());
    checkPolygonRings(result1);
    validateAndDumpPoly(poly1);
    validateAndDumpPoly(*sticker.resultPolyline(poly1.id()));

    validateAndDumpPoly(poly2);
    validateAndDumpPoly(*sticker.resultPolyline(poly2.id()));

    validateAndDumpPoly(poly3);
    validateAndDumpPoly(*sticker.resultPolyline(poly3.id()));

    validateAndDumpPoly(poly4);
    validateAndDumpPoly(*sticker.resultPolyline(poly4.id()));

    UNIT_ASSERT_EQUAL(sticker.resultPolyline(poly1.id())->numLineStringVertexes(0),
        poly1.numLineStringVertexes(0) + 4);
    UNIT_ASSERT_EQUAL(sticker.resultPolyline(poly2.id())->numLineStringVertexes(0),
        poly2.numLineStringVertexes(0) + 4);
    UNIT_ASSERT_EQUAL(sticker.resultPolyline(poly3.id())->numLineStringVertexes(0),
        poly3.numLineStringVertexes(0) + 2);
    UNIT_ASSERT_EQUAL(sticker.resultPolyline(poly4.id())->numLineStringVertexes(0),
    poly4.numLineStringVertexes(0));
}

} // Y_UNIT_TEST_SUITE

} // namespace maps::wiki::tests
