//
// Ported from JTS junit/linearref/AbstractIndexedLineTestCase.java r466
// and  junit/linearref/LengthIndexedLineTestCase.java r466

#include <tut/tut.hpp>
#include <utility.h>
// geos
#include <geos/platform.h>
#include <geos/io/WKTReader.h>
#include <geos/io/WKTWriter.h>
#include <geos/geom/PrecisionModel.h>
#include <geos/geom/GeometryFactory.h>
#include <geos/geom/Geometry.h> // required for use in unique_ptr
#include <geos/geom/LineString.h>
#include <geos/geom/Coordinate.h>
#include <geos/linearref/LengthIndexedLine.h>

// std
#include <cmath>
#include <sstream>
#include <string>
#include <memory>

using namespace geos::geom;
using namespace geos::linearref;
using namespace std;

/**
 * Tests the {@link LocationIndexedLine} class
 */
namespace tut {

typedef unique_ptr<Geometry> GeomPtr;
static const double TOLERANCE_DIST = 0.001;

struct test_lengthindexedline_data
{
    test_lengthindexedline_data()
        : reader(), writer()
    {
      writer.setTrim(true);
    }

    geos::io::WKTReader reader;
    geos::io::WKTWriter writer;

    void checkExpected(Geometry* result, string const& expected)
    {
        GeomPtr subLine(reader.read(expected));
        checkExpected(result, subLine.get());
    }

    void checkExpected(Geometry* result, const Geometry* expected)
    {
      bool isEqual = result->equalsExact(expected, 1.0e-5);
      ensure_equals("Expect: "+writer.write(expected)+" Obtained: "+writer.write(result), isEqual, true);
    }

    void runIndicesOfThenExtract(string const& inputStr, string const& subLineStr)
    {
        GeomPtr input(reader.read(inputStr));
        GeomPtr subLine(reader.read(subLineStr));
        GeomPtr result(indicesOfThenExtract(input.get(), subLine.get()));

        checkExpected(result.get(), subLine.get());
    }

    bool indexOfAfterCheck(Geometry* linearGeom, Coordinate testPt)
    {
        LengthIndexedLine indexedLine(linearGeom);

        // check locations are consecutive
        double loc1 = indexedLine.indexOf(testPt);
        double loc2 = indexedLine.indexOfAfter(testPt, loc1);
        if (loc2 <= loc1) return false;

        // check extracted points are the same as the input
        Coordinate pt1 = indexedLine.extractPoint(loc1);
        Coordinate pt2 = indexedLine.extractPoint(loc2);
        if (! pt1.equals2D(testPt)) return false;
        if (! pt2.equals2D(testPt)) return false;

        return true;
    }

    void runIndexOfAfterTest(string const& inputStr, string const& testPtWKT)
    {
        GeomPtr input(reader.read(inputStr));
        GeomPtr testPoint(reader.read(testPtWKT));
        const Coordinate* testPt = testPoint->getCoordinate();
        bool resultOK = indexOfAfterCheck(input.get(), *testPt);
        ensure(resultOK);
    }

    void runOffsetTest(string const& inputWKT, string const& testPtWKT,
                       double offsetDistance, string const& expectedPtWKT)
    {
        GeomPtr input(reader.read(inputWKT));
        GeomPtr testPoint(reader.read(testPtWKT));
        GeomPtr expectedPoint(reader.read(expectedPtWKT));
        const Coordinate* testPt = testPoint->getCoordinate();
        const Coordinate* expectedPt = expectedPoint->getCoordinate();
        Coordinate offsetPt = extractOffsetAt(input.get(), *testPt, offsetDistance);

        bool isOk = offsetPt.distance(*expectedPt) < TOLERANCE_DIST;
        if (! isOk)
            cout << "Expected = " << *expectedPoint << "  Actual = " << offsetPt << endl;
        ensure(isOk);
    }

    Coordinate extractOffsetAt(Geometry* linearGeom, Coordinate testPt, double offsetDistance)
    {
        LengthIndexedLine indexedLine(linearGeom);
        double index = indexedLine.indexOf(testPt);
        return indexedLine.extractPoint(index, offsetDistance);
    }

    void checkExtractLine(const char* wkt, double start, double end, const char* expected)
    {
        string wktstr(wkt);
        GeomPtr linearGeom(reader.read(wktstr));
        LengthIndexedLine indexedLine(linearGeom.get());
        GeomPtr result(indexedLine.extractLine(start, end));
        checkExpected(result.get(), expected);
    }


    Geometry* indicesOfThenExtract(Geometry* linearGeom, Geometry* subLine)
    {
        LengthIndexedLine indexedLine(linearGeom);
        double* loc = indexedLine.indicesOf(subLine);
        Geometry* result = indexedLine.extractLine(loc[0], loc[1]);
        delete [] loc;
        return result;
    }

}; // struct test_lengthindexedline_data

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

group test_lengthindexedline_group("geos::linearref::LocationIndexedLine");

// testML
template<>
template<>
void object::test<1>()
{
    runIndicesOfThenExtract("MULTILINESTRING ((0 0, 10 10), (20 20, 30 30))",
                            "MULTILINESTRING ((1 1, 10 10), (20 20, 25 25))");
}

// testPartOfSegmentNoVertex
template<>
template<>
void object::test<2>()
{
    runIndicesOfThenExtract("LINESTRING (0 0, 10 10, 20 20)",
                            "LINESTRING (1 1, 9 9)");
}

// testPartOfSegmentContainingVertex()
template<>
template<>
void object::test<3>()
{
    runIndicesOfThenExtract("LINESTRING (0 0, 10 10, 20 20)",
                            "LINESTRING (5 5, 10 10, 15 15)");
}

/**
 * Tests that duplicate coordinates are handled correctly.
 *
 * @throws Exception
 */
// testPartOfSegmentContainingDuplicateCoords
template<>
template<>
void object::test<4>()
{
    runIndicesOfThenExtract("LINESTRING (0 0, 10 10, 10 10, 20 20)",
                            "LINESTRING (5 5, 10 10, 10 10, 15 15)");
}

/**
 * Following tests check that correct portion of loop is identified.
 * This requires that the correct vertex for (0,0) is selected.
 */

// testLoopWithStartSubLine
template<>
template<>
void object::test<5>()
{
    runIndicesOfThenExtract("LINESTRING (0 0, 0 10, 10 10, 10 0, 0 0)",
                            "LINESTRING (0 0, 0 10, 10 10)");
}

// testLoopWithEndingSubLine()
template<>
template<>
void object::test<6>()
{
    runIndicesOfThenExtract("LINESTRING (0 0, 0 10, 10 10, 10 0, 0 0)",
                            "LINESTRING (10 10, 10 0, 0 0)");
}

// test a subline equal to the parent loop
// testLoopWithIdenticalSubLine()
template<>
template<>
void object::test<7>()
{
    runIndicesOfThenExtract("LINESTRING (0 0, 0 10, 10 10, 10 0, 0 0)",
                            "LINESTRING (0 0, 0 10, 10 10, 10 0, 0 0)");
}

// test a zero-length subline equal to the start point
// testZeroLenSubLineAtStart()
template<>
template<>
void object::test<8>()
{
    runIndicesOfThenExtract("LINESTRING (0 0, 0 10, 10 10, 10 0, 0 0)",
                            "LINESTRING (0 0, 0 0)");
}

// test a zero-length subline equal to a mid point
// testZeroLenSubLineAtMidVertex()
template<>
template<>
void object::test<9>()
{
    runIndicesOfThenExtract("LINESTRING (0 0, 0 10, 10 10, 10 0, 0 0)",
                            "LINESTRING (10 10, 10 10)");
}

// testIndexOfAfterSquare()
template<>
template<>
void object::test<10>()
{
    runIndexOfAfterTest("LINESTRING (0 0, 0 10, 10 10, 10 0, 0 0)",
                        "POINT (0 0)");
}

// testIndexOfAfterRibbon()
template<>
template<>
void object::test<11>()
{
    runIndexOfAfterTest("LINESTRING (0 0, 0 60, 50 60, 50 20, -20 20)",
                        "POINT (0 20)");
}

// testOffsetStartPoint()
template<>
template<>
void object::test<12>()
{
    runOffsetTest("LINESTRING (0 0, 10 10, 10 10, 20 20)", "POINT(0 0)", 1.0, "POINT (-0.7071067811865475 0.7071067811865475)");
    runOffsetTest("LINESTRING (0 0, 10 10, 10 10, 20 20)", "POINT(0 0)", -1.0, "POINT (0.7071067811865475 -0.7071067811865475)");
    runOffsetTest("LINESTRING (0 0, 10 10, 10 10, 20 20)", "POINT(10 10)", 5.0, "POINT (6.464466094067262 13.535533905932738)");
    runOffsetTest("LINESTRING (0 0, 10 10, 10 10, 20 20)", "POINT(10 10)", -5.0, "POINT (13.535533905932738 6.464466094067262)");
}

// testExtractLineBothIndicesAtEndpointXXX()
template<>
template<>
void object::test<13>()
{
  checkExtractLine(
    "MULTILINESTRING ((0 0, 10 0), (20 0, 25 0, 30 0))",
    -10, 10, "LINESTRING (10 0, 10 0)"
  );
}


// testExtractLineBeyondRange()
template<> template<>
void object::test<14>()
{
    checkExtractLine("LINESTRING (0 0, 10 10)", -100, 100, "LINESTRING (0 0, 10 10)");
}

// testExtractLineReverse()
template<>
template<>
void object::test<15>()
{
    checkExtractLine("LINESTRING (0 0, 10 0)", 9, 1, "LINESTRING (9 0, 1 0)");
}

// testExtractLineReverseMulti()
template<>
template<>
void object::test<16>()
{
    checkExtractLine("MULTILINESTRING ((0 0, 10 0), (20 0, 25 0, 30 0))",
                     19, 1, "MULTILINESTRING ((29 0, 25 0, 20 0), (10 0, 1 0))");
}

// testExtractLineNegative()
template<>
template<>
void object::test<17>()
{
    checkExtractLine("LINESTRING (0 0, 10 0)", -9, -1, "LINESTRING (1 0, 9 0)");
}

// testExtractLineNegativeReverse()
template<>
template<>
void object::test<18>()
{
    checkExtractLine("LINESTRING (0 0, 10 0)", -1, -9, "LINESTRING (9 0, 1 0)");
}

// testExtractLineIndexAtEndpoint()
template<>
template<>
void object::test<19>()
{
    checkExtractLine("MULTILINESTRING ((0 0, 10 0), (20 0, 25 0, 30 0))",
                     10, -1, "LINESTRING (20 0, 25 0, 29 0)");
}

// testExtractLineBothIndicesAtEndpoint()
template<>
template<>
void object::test<20>()
{
    checkExtractLine("MULTILINESTRING ((0 0, 10 0), (20 0, 25 0, 30 0))",
                     10, 10, "LINESTRING (10 0, 10 0)");
}

// testExtractLineBothIndicesAtEndpointNegative()
template<>
template<>
void object::test<21>()
{
    checkExtractLine("MULTILINESTRING ((0 0, 10 0), (20 0, 25 0, 30 0))",
                     -10, 10, "LINESTRING (10 0, 10 0)");
}

// testExtractPointBeyondRange()
template<>
template<>
void object::test<22>()
{
    GeomPtr linearGeom(reader.read("LINESTRING (0 0, 10 10)"));
    LengthIndexedLine indexedLine(linearGeom.get());
    Coordinate pt = indexedLine.extractPoint(100);
    ensure(pt == Coordinate(10, 10));

    Coordinate pt2 = indexedLine.extractPoint(0);
    ensure(pt2 == Coordinate(0, 0));
}

// testProjectPointWithDuplicateCoords()
template<>
template<>
void object::test<23>()
{
    GeomPtr linearGeom(reader.read("LINESTRING (0 0, 10 0, 10 0, 20 0)"));
    LengthIndexedLine indexedLine(linearGeom.get());
    double projIndex = indexedLine.project(Coordinate(10, 1));
    ensure(projIndex == 10.0);
}

/**
 * Tests that z values are interpolated
 *
 */
// testComputeZ()
template<>
template<>
void object::test<24>()
{
    GeomPtr linearGeom(reader.read("LINESTRING (0 0 0, 10 10 10)"));
    LengthIndexedLine indexedLine(linearGeom.get());
    double projIndex = indexedLine.project(Coordinate(5, 5));
    Coordinate projPt = indexedLine.extractPoint(projIndex);
    //    System.out.println(projPt);
    ensure(projPt.equals3D(Coordinate(5, 5, 5)));
}

/**
 * Tests that if the input does not have Z ordinates, neither does the output.
 *
 */
// testComputeZNaN()
template<>
template<>
void object::test<25>()
{

    GeomPtr linearGeom(reader.read("LINESTRING (0 0, 10 10 10)"));
    LengthIndexedLine indexedLine(linearGeom.get());
    double projIndex = indexedLine.project(Coordinate(5, 5));
    Coordinate projPt = indexedLine.extractPoint(projIndex);
    ensure(0 != ISNAN(projPt.z));
}

/**
 * From GEOS Ticket #323
 */
// testProjectExtractPoint()
template<>
template<>
void object::test<26>()
{
  GeomPtr linearGeom(reader.read("MULTILINESTRING ((0 2, 0 0), (-1 1, 1 1))"));
  LengthIndexedLine indexedLine(linearGeom.get());
  double index = indexedLine.project(Coordinate(1, 0));
  Coordinate pt = indexedLine.extractPoint(index);
  ensure_equals(pt, Coordinate(0, 0));
}

/**
 * Tests that leading and trailing zero-length sublines are trimmed in
 * the computed result, and that zero-length extracts return the lowest
 * extracted zero-length line
 */
// testExtractLineIndexAtEndpointWithZeroLenComponents()
template<> template<>
void object::test<27>()
{
  checkExtractLine(
    "MULTILINESTRING ((0 0, 10 0), (10 0, 10 0), (20 0, 25 0, 30 0))",
    10, -1, "LINESTRING (20 0, 25 0, 29 0)");

  checkExtractLine(
    "MULTILINESTRING ((0 0, 10 0), (10 0, 10 0), (20 0, 25 0, 30 0))",
    5, 10, "LINESTRING (5 0, 10 0)");

  checkExtractLine(
    "MULTILINESTRING ((0 0,10 0),(10 0,10 0),(10 0,10 0),(20 0,25 0,30 0))",
    10, 10, "LINESTRING (10 0, 10 0)");

  checkExtractLine(
    "MULTILINESTRING((0 0,10 0),(10 0,10 0),(10 0,10 0),(10 0,10 0),(20 0,25 0,30 0))",
    10, -10, "LINESTRING (10 0, 10 0)");
}





#if 0
template<>
template<>
void object::test<28>()
{

  GeomPtr linearGeom(reader.read(
"MULTILINESTRING ((0 -2, 0 2),(-2 0, 2 0))"
));
  LengthIndexedLine indexedLine(linearGeom.get());

  double projIndex = indexedLine.project(Coordinate(2, 1.9));
  ensure_equals(projIndex, 8);
  Coordinate projPt = indexedLine.extractPoint(projIndex);
  ensure_equals(projPt, Coordinate(2, 0));

  projIndex = indexedLine.project(Coordinate(2, 2.1));
  ensure_equals(projIndex, 4);
  projPt = indexedLine.extractPoint(projIndex);
  ensure_equals(projPt, Coordinate(0, 2));
}
#endif

template<>
template<>
void object::test<29>()
{
    GeomPtr linearGeom(reader.read("LINESTRING EMPTY"));
    LengthIndexedLine indexedLine(linearGeom.get());
    Coordinate pt = indexedLine.extractPoint(100);
    ensure(pt.isNull());
}

} // namespace tut

