
#include "catch.hpp"

#include <mapnik/geometry.hpp>
#include <mapnik/geometry_is_valid.hpp>

TEST_CASE("geometry is_valid") {

// only Boost >= 1.56 has the is_valid function, but only after 1.58 is there support for returning what is invalid
#if BOOST_VERSION >= 105800


SECTION("empty geometry") {
    mapnik::geometry::geometry_empty empty;
    CHECK( mapnik::geometry::is_valid(empty) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(empty, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(empty, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("point") {
    mapnik::geometry::point<double> pt(0,0);
    CHECK( mapnik::geometry::is_valid(pt) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(pt, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(pt, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("point -- geometry object") {
    mapnik::geometry::point<double> pt(0,0);
    mapnik::geometry::geometry<double> geom(pt);
    CHECK( mapnik::geometry::is_valid(geom) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(geom, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(geom, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

#if BOOST_VERSION < 106000

SECTION("point unitialized") {
    mapnik::geometry::point<double> pt2;
    CHECK( mapnik::geometry::is_valid(pt2) );
    std::string message2;
    CHECK( mapnik::geometry::is_valid(pt2, message2) );
    CHECK( message2 == "Geometry is valid");
    boost::geometry::validity_failure_type failure2;
    CHECK( mapnik::geometry::is_valid(pt2, failure2) );
    CHECK( failure2 == boost::geometry::no_failure );
}
#endif

#if BOOST_VERSION >= 106000

SECTION("point NaN") {
    mapnik::geometry::point<double> pt(std::numeric_limits<double>::quiet_NaN(),std::numeric_limits<double>::quiet_NaN());
    CHECK( std::isnan(pt.x) );
    CHECK( std::isnan(pt.y) );
    CHECK( !mapnik::geometry::is_valid(pt) );
    std::string message;
    CHECK( !mapnik::geometry::is_valid(pt, message) );
    CHECK( message == "Geometry has point(s) with invalid coordinate(s)");
    boost::geometry::validity_failure_type failure;
    CHECK( !mapnik::geometry::is_valid(pt, failure) );
    CHECK( failure == boost::geometry::failure_invalid_coordinate );
}

SECTION("point Infinity") {
    mapnik::geometry::point<double> pt(std::numeric_limits<double>::infinity(),std::numeric_limits<double>::infinity());
    CHECK( std::isinf(pt.x) );
    CHECK( std::isinf(pt.y) );
    CHECK( !mapnik::geometry::is_valid(pt) );
    std::string message;
    CHECK( !mapnik::geometry::is_valid(pt, message) );
    CHECK( message == "Geometry has point(s) with invalid coordinate(s)");
    boost::geometry::validity_failure_type failure;
    CHECK( !mapnik::geometry::is_valid(pt, failure) );
    CHECK( failure == boost::geometry::failure_invalid_coordinate );
}

#else // BOOST_VERSION >= 1.60

// This is funky that boost geometry is_valid does not check for NAN when dealing with a point
// this test is here in case the logic ever changes
// Bug report on this: https://svn.boost.org/trac/boost/ticket/11711
SECTION("point NaN") {
    mapnik::geometry::point<double> pt(std::numeric_limits<double>::quiet_NaN(),std::numeric_limits<double>::quiet_NaN());
    CHECK( std::isnan(pt.x) );
    CHECK( std::isnan(pt.y) );
    CHECK( mapnik::geometry::is_valid(pt) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(pt, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(pt, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

// This is funky that boost geometry is_valid does not check for infinity when dealing with a point
// this test is here in case the logic ever changes
// Bug report on this: https://svn.boost.org/trac/boost/ticket/11711
SECTION("point Infinity") {
    mapnik::geometry::point<double> pt(std::numeric_limits<double>::infinity(),std::numeric_limits<double>::infinity());
    CHECK( std::isinf(pt.x) );
    CHECK( std::isinf(pt.y) );
    CHECK( mapnik::geometry::is_valid(pt) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(pt, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(pt, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

#endif // BOOST_VERSION >= 1.60

SECTION("multi point") {
    mapnik::geometry::multi_point<double> mpt;
    mpt.add_coord(0,0);
    mpt.add_coord(1,1);
    CHECK( mapnik::geometry::is_valid(mpt) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(mpt, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(mpt, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("multi point empty") {
    mapnik::geometry::multi_point<double> mpt;
    CHECK( mapnik::geometry::is_valid(mpt) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(mpt, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(mpt, failure) );
    CHECK( failure == boost::geometry::no_failure );
}


SECTION("line_string") {
    mapnik::geometry::line_string<double> line;
    line.add_coord(0,0);
    line.add_coord(1,1);
    CHECK( mapnik::geometry::is_valid(line) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(line, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(line, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

// This shouldn't fail -- test added in case logic ever changes
SECTION("line_string repeated points") {
    mapnik::geometry::line_string<double> line;
    line.add_coord(0,0);
    line.add_coord(1,1);
    line.add_coord(1,1);
    line.add_coord(2,2);
    CHECK( mapnik::geometry::is_valid(line) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(line, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(line, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("line_string empty") {
    mapnik::geometry::line_string<double> line;
    CHECK( !mapnik::geometry::is_valid(line) );
    std::string message;
    CHECK( !mapnik::geometry::is_valid(line, message) );
    CHECK( message == "Geometry has too few points");
    boost::geometry::validity_failure_type failure;
    CHECK( !mapnik::geometry::is_valid(line, failure) );
    CHECK( failure == boost::geometry::failure_few_points );
}

SECTION("multi_line_string") {
    mapnik::geometry::line_string<double> line1;
    line1.add_coord(0,0);
    line1.add_coord(1,1);
    mapnik::geometry::line_string<double> line2;
    line2.add_coord(0,1);
    line2.add_coord(1,2);
    mapnik::geometry::multi_line_string<double> lines;
    lines.emplace_back(line1);
    lines.emplace_back(line2);
    CHECK( mapnik::geometry::is_valid(lines) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(lines, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(lines, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("multi_line_string empty") {
    mapnik::geometry::multi_line_string<double> lines;
    CHECK( mapnik::geometry::is_valid(lines) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(lines, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(lines, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("polygon") {
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(1,0);
    ring.add_coord(1,1);
    ring.add_coord(0,1);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    CHECK( mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("polygon invalid winding order") {
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(0,1);
    ring.add_coord(1,1);
    ring.add_coord(1,0);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    CHECK( !mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( !mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry has wrong orientation" );
    boost::geometry::validity_failure_type failure;
    CHECK( !mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::failure_wrong_orientation );
}

// repeated points are not considered invalid in a polygon
SECTION("polygon 2 repeated points") {
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(1,0);
    ring.add_coord(1,1);
    ring.add_coord(1,1);
    ring.add_coord(0,1);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    CHECK( mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::no_failure );
}
// repeated points are not considered invalid in a polygon
SECTION("polygon 3 repeated points") {
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(1,0);
    ring.add_coord(1,1);
    ring.add_coord(1,1);
    ring.add_coord(1,1);
    ring.add_coord(0,1);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    CHECK( mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("polygon that is empty") {
    mapnik::geometry::polygon<double> poly;
    CHECK( !mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( !mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry has too few points");
    boost::geometry::validity_failure_type failure;
    CHECK( !mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::failure_few_points );
}

SECTION("polygon with spike") {
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(1,0);
    ring.add_coord(1,1);
    ring.add_coord(2,2);
    ring.add_coord(1,1);
    ring.add_coord(0,1);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    CHECK( !mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( !mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry has spikes. A spike point was found with apex at (2, 2)");
    boost::geometry::validity_failure_type failure;
    CHECK( !mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::failure_spikes );
}

SECTION("polygon with hole") {
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(3,0);
    ring.add_coord(3,3);
    ring.add_coord(0,3);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    mapnik::geometry::linear_ring<double> hole;
    hole.add_coord(1,1);
    hole.add_coord(1,2);
    hole.add_coord(2,2);
    hole.add_coord(2,1);
    hole.add_coord(1,1);
    poly.add_hole(std::move(hole));
    CHECK( mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("polygon with empty hole") {
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(3,0);
    ring.add_coord(3,3);
    ring.add_coord(0,3);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    mapnik::geometry::linear_ring<double> hole;
    poly.add_hole(std::move(hole));
    CHECK( !mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( !mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry has too few points");
    boost::geometry::validity_failure_type failure;
    CHECK( !mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::failure_few_points );
}


SECTION("polygon with hole with invalid winding order") {
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(3,0);
    ring.add_coord(3,3);
    ring.add_coord(0,3);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    mapnik::geometry::linear_ring<double> hole;
    hole.add_coord(1,1);
    hole.add_coord(2,1);
    hole.add_coord(2,2);
    hole.add_coord(1,2);
    hole.add_coord(1,1);
    poly.add_hole(std::move(hole));
    CHECK( !mapnik::geometry::is_valid(poly) );
    std::string message;
    CHECK( !mapnik::geometry::is_valid(poly, message) );
    CHECK( message == "Geometry has wrong orientation" );
    boost::geometry::validity_failure_type failure;
    CHECK( !mapnik::geometry::is_valid(poly, failure) );
    CHECK( failure == boost::geometry::failure_wrong_orientation );
}

SECTION("multi polygon") {
    mapnik::geometry::multi_polygon<double> mp;
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(1,0);
    ring.add_coord(1,1);
    ring.add_coord(0,1);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    mapnik::geometry::polygon<double> poly2;
    mapnik::geometry::linear_ring<double> ring2;
    ring2.add_coord(0,0);
    ring2.add_coord(-1,0);
    ring2.add_coord(-1,-1);
    ring2.add_coord(0,-1);
    ring2.add_coord(0,0);
    poly2.set_exterior_ring(std::move(ring2));
    mp.emplace_back(poly);
    mp.emplace_back(poly2);
    CHECK( mapnik::geometry::is_valid(mp) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(mp, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(mp, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("multi polygon with hole") {
    mapnik::geometry::multi_polygon<double> mp;
    mapnik::geometry::polygon<double> poly;
    mapnik::geometry::linear_ring<double> ring;
    ring.add_coord(0,0);
    ring.add_coord(3,0);
    ring.add_coord(3,3);
    ring.add_coord(0,3);
    ring.add_coord(0,0);
    poly.set_exterior_ring(std::move(ring));
    mapnik::geometry::linear_ring<double> hole;
    hole.add_coord(1,1);
    hole.add_coord(1,2);
    hole.add_coord(2,2);
    hole.add_coord(2,1);
    hole.add_coord(1,1);
    poly.add_hole(std::move(hole));
    mapnik::geometry::polygon<double> poly2;
    mapnik::geometry::linear_ring<double> ring2;
    ring2.add_coord(0,0);
    ring2.add_coord(-3,0);
    ring2.add_coord(-3,-3);
    ring2.add_coord(0,-3);
    ring2.add_coord(0,0);
    poly2.set_exterior_ring(std::move(ring2));
    mapnik::geometry::linear_ring<double> hole2;
    hole2.add_coord(-1,-1);
    hole2.add_coord(-1,-2);
    hole2.add_coord(-2,-2);
    hole2.add_coord(-2,-1);
    hole2.add_coord(-1,-1);
    poly2.add_hole(std::move(hole2));
    mp.emplace_back(poly);
    mp.emplace_back(poly2);
    CHECK( mapnik::geometry::is_valid(mp) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(mp, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(mp, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

SECTION("multi polygon empty") {
    mapnik::geometry::multi_polygon<double> mp;
    CHECK( mapnik::geometry::is_valid(mp) );
    std::string message;
    CHECK( mapnik::geometry::is_valid(mp, message) );
    CHECK( message == "Geometry is valid");
    boost::geometry::validity_failure_type failure;
    CHECK( mapnik::geometry::is_valid(mp, failure) );
    CHECK( failure == boost::geometry::no_failure );
}

#endif // BOOST_VERSION >= 1.58

}
