/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2005-2006 Refractions Research Inc.
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU Lesser General Public Licence as published
 * by the Free Software Foundation.
 * See the COPYING file for more information.
 *
 **********************************************************************
 *
 * Last port: linearref/LinearLocation.java r463
 *
 **********************************************************************/

#include <geos/geom/LineString.h>
#include <geos/linearref/LengthIndexedLine.h>
#include <geos/linearref/LinearLocation.h>
#include <geos/linearref/LengthLocationMap.h>
#include <geos/util/IllegalArgumentException.h>

using namespace std;

using namespace geos::geom;

namespace geos {
namespace linearref { // geos::linearref

/* public static */
LinearLocation
LinearLocation::getEndLocation(const Geometry* linear)
{
	// assert: linear is LineString or MultiLineString
	LinearLocation loc;
	loc.setToEnd(linear);
	return loc;
}

/* public static */
Coordinate
LinearLocation::pointAlongSegmentByFraction(const Coordinate& p0, const Coordinate& p1, double frac)
{
	if (frac <= 0.0) return p0;
	if (frac >= 1.0) return p1;

	double x = (p1.x - p0.x) * frac + p0.x;
	double y = (p1.y - p0.y) * frac + p0.y;
	// interpolate Z value. If either input Z is NaN, result z will be NaN as well.
	double z = (p1.z - p0.z) * frac + p0.z;
	return Coordinate(x, y, z);
}

/* public */
LinearLocation::LinearLocation(unsigned int segmentIndex,
		double segmentFraction)
	:
	componentIndex(0),
	segmentIndex(segmentIndex),
	segmentFraction(segmentFraction)
{}


/* public */
LinearLocation::LinearLocation(unsigned int componentIndex,
		unsigned int segmentIndex, double segmentFraction)
	:
	componentIndex(componentIndex),
	segmentIndex(segmentIndex),
	segmentFraction(segmentFraction)
{
	normalize();
}

/* private */
void
LinearLocation::normalize()
{
	if (segmentFraction < 0.0)
	{
		segmentFraction = 0.0;
	}
	if (segmentFraction > 1.0)
	{
		segmentFraction = 1.0;
	}

	if (segmentFraction == 1.0)
	{
		segmentFraction = 0.0;
		segmentIndex += 1;
	}
}

/* public */
void
LinearLocation::clamp(const Geometry* linear)
{
	if (componentIndex >= linear->getNumGeometries())
	{
		setToEnd(linear);
		return;
	}
	if (segmentIndex >= linear->getNumPoints())
	{
		const LineString* line = dynamic_cast<const LineString*> (linear->getGeometryN(componentIndex));
		segmentIndex = static_cast<unsigned int>(line->getNumPoints() - 1);
		segmentFraction = 1.0;
	}
}

/* public */
void
LinearLocation::snapToVertex(const Geometry* linearGeom, double minDistance)
{
	if (segmentFraction <= 0.0 || segmentFraction >= 1.0)
		return;
	double segLen = getSegmentLength(linearGeom);
	double lenToStart = segmentFraction * segLen;
	double lenToEnd = segLen - lenToStart;
	if (lenToStart <= lenToEnd && lenToStart < minDistance)
	{
		segmentFraction = 0.0;
	}
	else if (lenToEnd <= lenToStart && lenToEnd < minDistance)
	{
		segmentFraction = 1.0;
	}
}

/* public */
double
LinearLocation::getSegmentLength(const Geometry* linearGeom) const
{
	const LineString* lineComp = dynamic_cast<const LineString*> (linearGeom->getGeometryN(componentIndex));

	// ensure segment index is valid
	unsigned int segIndex = segmentIndex;
	if (segmentIndex >= lineComp->getNumPoints() - 1)
		segIndex = static_cast<unsigned int>(lineComp->getNumPoints() - 2);

	Coordinate p0 = lineComp->getCoordinateN(segIndex);
	Coordinate p1 = lineComp->getCoordinateN(segIndex + 1);
	return p0.distance(p1);
}

/* public */
void
LinearLocation::setToEnd(const Geometry* linear)
{
	componentIndex = static_cast<unsigned int>(linear->getNumGeometries() - 1);
	const LineString* lastLine = dynamic_cast<const LineString*>(linear->getGeometryN(componentIndex));
	segmentIndex = static_cast<unsigned int>(lastLine->getNumPoints() - 1);
	segmentFraction = 1.0;
}

/* public */
unsigned int
LinearLocation::getComponentIndex() const
{
	return componentIndex;
}

/* public */
unsigned int
LinearLocation::getSegmentIndex() const
{
	return segmentIndex;
}

/* public */
double
LinearLocation::getSegmentFraction() const
{
	return segmentFraction;
}

/* public */
bool
LinearLocation::isVertex() const
{
	return segmentFraction <= 0.0 || segmentFraction >= 1.0;
}

/* public */
Coordinate
LinearLocation::getCoordinate(const Geometry* linearGeom) const
{
	const LineString* lineComp = dynamic_cast<const LineString *> (linearGeom->getGeometryN(componentIndex));
	if ( ! lineComp ) {
		throw util::IllegalArgumentException("LinearLocation::getCoordinate only works with LineString geometries");
	}
	if (linearGeom->isEmpty()) {
		return Coordinate::getNull();
	}
	Coordinate p0 = lineComp->getCoordinateN(segmentIndex);
	if (segmentIndex >= lineComp->getNumPoints() - 1)
		return p0;
	Coordinate p1 = lineComp->getCoordinateN(segmentIndex + 1);
	return pointAlongSegmentByFraction(p0, p1, segmentFraction);
}

/* public */
std::unique_ptr<LineSegment>
LinearLocation::getSegment(const Geometry* linearGeom) const
{
	const LineString* lineComp = dynamic_cast<const LineString *> (linearGeom->getGeometryN(componentIndex));
	Coordinate p0 = lineComp->getCoordinateN(segmentIndex);
	// check for endpoint - return last segment of the line if so
	if (segmentIndex >= lineComp->getNumPoints() - 1)
	{
		Coordinate prev = lineComp->getCoordinateN(static_cast<int>(lineComp->getNumPoints() - 2));
		return std::unique_ptr<LineSegment>(new LineSegment(prev, p0));
	}
	Coordinate p1 = lineComp->getCoordinateN(segmentIndex + 1);
	return std::unique_ptr<LineSegment>(new LineSegment(p0, p1));
}

/* public */
bool
LinearLocation::isValid(const Geometry* linearGeom) const
{
	if (componentIndex >= linearGeom->getNumGeometries())
		return false;

	const LineString* lineComp = dynamic_cast<const LineString*> (linearGeom->getGeometryN(componentIndex));
	if (segmentIndex > lineComp->getNumPoints())
		return false;
	if (segmentIndex == lineComp->getNumPoints() && segmentFraction != 0.0)
		return false;

	if (segmentFraction < 0.0 || segmentFraction > 1.0)
		return false;
	return true;
}

/* public */
int
LinearLocation::compareTo(const LinearLocation& other) const
{
	// compare component indices
	if (componentIndex < other.componentIndex) return -1;
	if (componentIndex > other.componentIndex) return 1;
	// compare segments
	if (segmentIndex < other.segmentIndex) return -1;
	if (segmentIndex > other.segmentIndex) return 1;
	// same segment, so compare segment fraction
	if (segmentFraction < other.segmentFraction) return -1;
	if (segmentFraction > other.segmentFraction) return 1;
	// same location
	return 0;
}

/* public */
int
LinearLocation::compareLocationValues(unsigned int componentIndex1,
		unsigned int segmentIndex1, double segmentFraction1) const
{
	// compare component indices
	if (componentIndex < componentIndex1) return -1;
	if (componentIndex > componentIndex1) return 1;
	// compare segments
	if (segmentIndex < segmentIndex1) return -1;
	if (segmentIndex > segmentIndex1) return 1;
	// same segment, so compare segment fraction
	if (segmentFraction < segmentFraction1) return -1;
	if (segmentFraction > segmentFraction1) return 1;
	// same location
	return 0;
}


/* public static */
int
LinearLocation::compareLocationValues(
	unsigned int componentIndex0, unsigned int segmentIndex0,
	double segmentFraction0,
	unsigned int componentIndex1, unsigned int segmentIndex1,
	double segmentFraction1)
{
	// compare component indices
	if (componentIndex0 < componentIndex1) return -1;
	if (componentIndex0 > componentIndex1) return 1;
	// compare segments
	if (segmentIndex0 < segmentIndex1) return -1;
	if (segmentIndex0 > segmentIndex1) return 1;
	// same segment, so compare segment fraction
	if (segmentFraction0 < segmentFraction1) return -1;
	if (segmentFraction0 > segmentFraction1) return 1;
	// same location
	return 0;
}


/* public */
bool
LinearLocation::isOnSameSegment(const LinearLocation& loc) const
{
	if (componentIndex != loc.componentIndex) return false;
	if (segmentIndex == loc.segmentIndex) return true;
	if (loc.segmentIndex - segmentIndex == 1
			&& loc.segmentFraction == 0.0)
		return true;
	if (segmentIndex - loc.segmentIndex == 1
			&& segmentFraction == 0.0)
		return true;
	return false;
}

/* public */
bool
LinearLocation::isEndpoint(const Geometry& linearGeom) const
{
  const LineString& lineComp = dynamic_cast<const LineString&>(
      *(linearGeom.getGeometryN(componentIndex)) );
  // check for endpoint
  auto nseg = lineComp.getNumPoints() - 1;
  return segmentIndex >= nseg
        || (segmentIndex == nseg && segmentFraction >= 1.0);

}


ostream& operator<<(ostream &out, const LinearLocation &obj)
{
	return out << "LinearLoc[" << obj.componentIndex << ", " <<
	       obj.segmentIndex << ", " << obj.segmentFraction << "]";
}

} // namespace geos.linearref
} // namespace geos

