//
// Test Suite for geos::operation::overlay::snap::LineStringSnapper class.

// tut
#include <tut/tut.hpp>
// geos
#include <geos/operation/overlay/snap/LineStringSnapper.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/CoordinateList.h>
#include <geos/geom/CoordinateArraySequence.h>
// std
#include <string>
#include <vector>

namespace tut {
//
// Test Group
//

// Common data used by tests
struct test_linestringsnapper_data {

    test_linestringsnapper_data() {}
};

typedef test_group<test_linestringsnapper_data> group;
typedef group::object object;

group test_linestringsnapper_group("geos::operation::overlay::snap::LineStringSnapper");

//
// Test Cases
//

// Test vertices snapping
template<>
template<>
void object::test<1>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // source coordinates
    Coordinate src_a(0, 0);
    Coordinate src_b(10, 10);

    // snap coordinates
    Coordinate snp_a(0.1, 0);
    Coordinate snp_b(10, 10.1);

    Coordinate::Vect srcCoords;
    srcCoords.push_back(src_a);
    srcCoords.push_back(src_b);

    Coordinate::ConstVect snpCoords;
    snpCoords.push_back(&snp_a);
    snpCoords.push_back(&snp_b);

    LineStringSnapper snapper(srcCoords, 0.4);

    CoordsVectAptr ret(snapper.snapTo(snpCoords));

    // both points should have been snapped
    ensure_equals(ret->size(), 2u);
    ensure_equals(ret->operator[](0), snp_a);
    ensure_equals(ret->operator[](1), snp_b);

}

// Test segment snapping
template<>
template<>
void object::test<2>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // source coordinates
    Coordinate src_a(0, 0);
    Coordinate src_b(10, 10);

    // snap coordinates
    Coordinate snp_a(0.4, 0);
    Coordinate snp_b(10, 10.4);

    Coordinate::Vect srcCoords;
    srcCoords.push_back(src_a);
    srcCoords.push_back(src_b);

    Coordinate::ConstVect snpCoords;
    snpCoords.push_back(&snp_a);
    snpCoords.push_back(&snp_b);

    LineStringSnapper snapper(srcCoords, 0.3);

    CoordsVectAptr ret(snapper.snapTo(snpCoords));

    // snap point a should be inserted
    ensure_equals(ret->size(), 3u);
    ensure_equals(ret->operator[](0), src_a);
    ensure_equals(ret->operator[](1), snp_a);
    ensure_equals(ret->operator[](2), src_b);

}

// Test vertices snapping in a closed ring
template<>
template<>
void object::test<3>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // source coordinates

    Coordinate src_a(0, 0);
    Coordinate src_b(10, 10);
    Coordinate src_c(0, 10);

    Coordinate::Vect srcCoords;
    srcCoords.push_back(src_a);
    srcCoords.push_back(src_b);
    srcCoords.push_back(src_c);
    srcCoords.push_back(src_a);

    // snap coordinates

    Coordinate snp_a(0.1, 0);
    Coordinate snp_b(10, 10.1);

    Coordinate::ConstVect snpCoords;
    snpCoords.push_back(&snp_a);
    snpCoords.push_back(&snp_b);

    LineStringSnapper snapper(srcCoords, 0.4);

    CoordsVectAptr ret(snapper.snapTo(snpCoords));

    // Points A and B should be snapped
    ensure_equals(ret->size(), 4u);
    ensure_equals(ret->operator[](0), snp_a);
    ensure_equals(ret->operator[](1), snp_b);
    ensure_equals(ret->operator[](2), src_c);
    ensure_equals(ret->operator[](3), snp_a);

}

// Test vertices snapping in a short sequence
template<>
template<>
void object::test<4>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // source coordinates

    Coordinate src_a(0, 0);
    Coordinate::Vect srcCoords;
    srcCoords.push_back(src_a);

    // snap coordinates

    Coordinate snp_a(0.1, 0);
    Coordinate::ConstVect snpCoords;
    snpCoords.push_back(&snp_a);

    LineStringSnapper snapper(srcCoords, 0.4);

    CoordsVectAptr ret(snapper.snapTo(snpCoords));

    ensure_equals(ret->size(), 1u);
    ensure_equals(ret->operator[](0), snp_a);

}

// Test vertices snapping in an empty sequence
template<>
template<>
void object::test<5>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // source coordinates

    Coordinate::Vect srcCoords;

    // snap coordinates

    Coordinate::ConstVect snpCoords;

    LineStringSnapper snapper(srcCoords, 0.4);

    CoordsVectAptr ret(snapper.snapTo(snpCoords));

    ensure_equals(ret->size(), 0u);

}

// Test snapping an empty sequence
template<>
template<>
void object::test<6>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // source coordinates

    Coordinate::Vect srcCoords;

    // snap coordinates

    Coordinate snp_a(0.1, 0);
    Coordinate::ConstVect snpCoords;
    snpCoords.push_back(&snp_a);

    LineStringSnapper snapper(srcCoords, 0.4);

    CoordsVectAptr ret(snapper.snapTo(snpCoords));

    ensure_equals(ret->size(), 0u);

}

// Test allow snapping to source vertices
template<>
template<>
void object::test<7>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // Source: (0 0, 10 0, 0 1)
    Coordinate src_a(0, 0);
    Coordinate src_b(10, 0);
    Coordinate src_c(0, 1);
    Coordinate::Vect srcCoords;
    srcCoords.push_back(src_a);
    srcCoords.push_back(src_b);
    srcCoords.push_back(src_c);

    // Snap: (0 0)
    Coordinate snp_a(0, 0);
    Coordinate::ConstVect snpCoords;
    snpCoords.push_back(&snp_a);

    // Snap with tolerance of 1
    // (both first and second point could be snapped)
    LineStringSnapper snapper(srcCoords, 1);

    // Allow source-snapping, expect: (0 0, 5 0, 0 0, 10 0)
    snapper.setAllowSnappingToSourceVertices(true);
    CoordsVectAptr ret(snapper.snapTo(snpCoords));

    ensure_equals(ret->size(), 4u);
    ensure_equals(ret->operator[](0), src_a);
    ensure_equals(ret->operator[](1), src_b);
    ensure_equals(ret->operator[](2), snp_a);
    ensure_equals(ret->operator[](3), src_c);

    // Do not allow source-snapping, expect: (0 0, 5 0, 10 0)
    snapper.setAllowSnappingToSourceVertices(false);
    ret = snapper.snapTo(snpCoords);
    ensure_equals(ret->size(), 3u);
    ensure_equals(ret->operator[](0), src_a);
    ensure_equals(ret->operator[](1), src_b);
    ensure_equals(ret->operator[](2), src_c);
}

// Test two candidate vertices snaps
template<>
template<>
void object::test<8>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // Source: (0 0, 1 0, 1 1)
    Coordinate src_a(0, 0);
    Coordinate src_b(1, 0);
    Coordinate src_c(1, 1);
    Coordinate::Vect srcCoords;
    srcCoords.push_back(src_a);
    srcCoords.push_back(src_b);
    srcCoords.push_back(src_c);

    // Snap: (0.5, 0)
    Coordinate snp_a(0.5, 0);
    Coordinate::ConstVect snpCoords;
    snpCoords.push_back(&snp_a);

    // Snap with tolerance of 1
    // (both first and second point could be snapped)
    LineStringSnapper snapper(srcCoords, 1);

    // Expect: (0.5 0, 1 0, 1 1)
    CoordsVectAptr ret(snapper.snapTo(snpCoords));
    ensure_equals(ret->size(), 3u);
    ensure_equals(ret->operator[](0), snp_a);
    ensure_equals(ret->operator[](1), src_b);
    ensure_equals(ret->operator[](2), src_c);
}

// Snap of last segment in closed linestring
// See https://trac.osgeo.org/geos/ticket/758
template<>
template<>
void object::test<9>
()
{
    using geos::geom::Coordinate;
    using geos::operation::overlay::snap::LineStringSnapper;

    typedef std::unique_ptr<Coordinate::Vect> CoordsVectAptr;


    // Source: (1 1, 5 9, 9 1, 1 1)
    Coordinate src_a(1, 1);
    Coordinate src_b(5, 9);
    Coordinate src_c(9, 1);
    Coordinate::Vect srcCoords;
    srcCoords.push_back(src_a);
    srcCoords.push_back(src_b);
    srcCoords.push_back(src_c);
    srcCoords.push_back(src_a);

    // Snap: (0 0, 10 0, 1 0.5)
    Coordinate snp_a(0, 0);
    Coordinate snp_b(10, 0);
    Coordinate snp_c(1, 0.5);
    Coordinate::ConstVect snpCoords;
    snpCoords.push_back(&snp_a);
    snpCoords.push_back(&snp_b);
    snpCoords.push_back(&snp_c);

    // Snap with tolerance of 2
    // (both first and second point could be snapped)
    LineStringSnapper snapper(srcCoords, 2);

    // Expect: (0 0, 5 9, 10 0, 1 0.5, 0 0)
    CoordsVectAptr ret(snapper.snapTo(snpCoords));
    ensure_equals(ret->size(), 5u);
    ensure_equals(ret->operator[](0), snp_a); //  0 0
    ensure_equals(ret->operator[](1), src_b); //  5 9
    ensure_equals(ret->operator[](2), snp_b); // 10 0
    ensure_equals(ret->operator[](3), snp_c); //  1 0.5
    ensure_equals(ret->operator[](4), snp_a); //  0 0
}

} // namespace tut
