#include <maps/wikimap/mapspro/services/mrc/tasks-planner/lib/disjoint_polygons.h>

#include <library/cpp/testing/gtest/gtest.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/common.h>
#include <maps/libs/geolib/include/multipolygon.h>
#include <maps/libs/geolib/include/test_tools/comparison.h>
#include <maps/libs/geolib/include/test_tools/io_operations.h>

#include <exception>

namespace maps {
namespace mrc {
namespace tasks_planner {
namespace tests {

using geolib3::Point2;
using geolib3::Polygon2;
using geolib3::PointsVector;

using geolib3::io::operator <<;

using ::testing::get;

MATCHER_P(ApproxEq, geom, "") {
    constexpr double EPS = 2 * 1e-5;

    bool res = geolib3::test_tools::approximateEqual(arg, geom, EPS);
    if(!res) {
        std::cerr << arg << "\n" << geom << "\n";
    }
    return res;
}

MATCHER(CanBuildMultipolygon, "") {
    geolib3::MultiPolygon2 multiPolygon(arg);
    return true;
}

TEST(test_fix_polygon_intersections, test_on_empty_polygons)
{
    std::vector<geolib3::Polygon2> polygons;
    EXPECT_THAT(makeMultiPolygon(polygons), ApproxEq(geolib3::MultiPolygon2()));

    polygons.push_back(geolib3::Polygon2());
    EXPECT_THROW(makeMultiPolygon(polygons), maps::Exception);
}

TEST(test_fix_polygon_intersections, test_on_single_polygon)
{
    std::vector<geolib3::Polygon2> polygons{
        Polygon2{PointsVector{{0, 0}, {0, 1}, {1, 1}, {1, 0}}}
    };

    EXPECT_THAT(makeMultiPolygon(polygons), ApproxEq(geolib3::MultiPolygon2(polygons)));
}

TEST(test_fix_polygon_intersections, test_on_two_non_intersected_polygons)
{
    std::vector<geolib3::Polygon2> polygons{
        Polygon2{PointsVector{{0, 0}, {0, 1}, {1, 1}, {1, 0}}},
        Polygon2{PointsVector{{2, 0}, {2, 1}, {3, 1}, {3, 0}}}
    };

    EXPECT_THAT(makeMultiPolygon(polygons), ApproxEq(geolib3::MultiPolygon2(polygons)));
}

TEST(test_fix_polygon_intersections, test_on_two_touches_polygons)
{
    std::vector<geolib3::Polygon2> polygons{
        Polygon2{PointsVector{{0, 0}, {0, 1}, {1, 1}, {1, 0}}},
        Polygon2{PointsVector{{1, 1}, {1, 2}, {2, 2}, {2, 1}}}
    };
    EXPECT_THAT(makeMultiPolygon(polygons), ApproxEq(geolib3::MultiPolygon2(polygons)));
}


TEST(test_fix_polygon_intersections, test_on_two_intersected_polygons)
{

    std::vector<geolib3::Polygon2> polygons{
        Polygon2{PointsVector{{0, 0}, {0, 2}, {2, 2}, {2, 0}}},
        Polygon2{PointsVector{{1, 1}, {1, 3}, {3, 3}, {3, 1}}}
    };

    std::vector<geolib3::Polygon2> expectedPolygons{
        Polygon2{PointsVector{{0, 0}, {0, 2}, {1, 2}, {1, 1}, {2, 1}, {2, 0}}},
        Polygon2{PointsVector{{1, 1}, {1, 3}, {3, 3}, {3, 1}}}
    };

    EXPECT_THAT(makeMultiPolygon(std::move(polygons)),
        ApproxEq(geolib3::MultiPolygon2(expectedPolygons, false /* enforceValidation */))
    );
}

TEST(test_fix_polygon_intersections, test_on_three_intersected_polygons)
{

    std::vector<geolib3::Polygon2> polygons{
        Polygon2{PointsVector{{0, 0}, {0, 2}, {2, 2}, {2, 0}}},
        Polygon2{PointsVector{{1, 1}, {1, 3}, {3, 3}, {3, 1}}},
        Polygon2{PointsVector{{-1, 1}, {-1, 2}, {2, 2}, {2, 1}}}
    };

    std::vector<geolib3::Polygon2> expectedPolygons{
        Polygon2{PointsVector{{0, 0}, {0, 1}, {2, 1}, {2, 0}}},
        Polygon2{PointsVector{{1, 2}, {1, 3}, {3, 3}, {3, 1}, {2, 1}, {2, 2}}},
        Polygon2{PointsVector{{-1, 1}, {-1, 2}, {2, 2}, {2, 1}}}
    };

    EXPECT_THAT(makeMultiPolygon(std::move(polygons)),
        ApproxEq(geolib3::MultiPolygon2(expectedPolygons, false /* enforceValidation */))
    );
}


TEST(test_fix_polygon_intersections, test_fail_on_crossing_polygons)
{

    std::vector<geolib3::Polygon2> polygons{
        Polygon2{PointsVector{{0, 1}, {0, 2}, {3, 2}, {3, 1}}},
        Polygon2{PointsVector{{1, 0}, {1, 3}, {2, 3}, {2, 0}}}
    };

    EXPECT_THROW(makeMultiPolygon(std::move(polygons)), std::exception);
}

} // namespace tests
} // namespace tasks_planner
} // namespace mrc
} // namespace maps
