/**********************************************************************
 *
 * 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: geomgraph/Edge.java r428 (JTS-1.12+)
 *
 **********************************************************************/

#ifdef _MSC_VER
#pragma warning(disable:4355)
#endif

#include <cassert>
#include <string>
#include <sstream>

#include <geos/geomgraph/Edge.h>
#include <geos/geomgraph/Position.h>
#include <geos/geomgraph/Label.h>
#include <geos/geomgraph/index/MonotoneChainEdge.h>
#include <geos/algorithm/LineIntersector.h>
#include <geos/geom/IntersectionMatrix.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/CoordinateArraySequence.h> // FIXME: shouldn't use
#include <geos/geom/Coordinate.h>

//#define GEOS_DEBUG_INTERSECT 0
#ifndef GEOS_DEBUG
#define GEOS_DEBUG 0
#endif

#if GEOS_DEBUG || GEOS_DEBUG_INTERSECT
#include <iostream>
#endif

using namespace std;
using namespace geos::geom;

namespace geos {
namespace geomgraph { // geos.geomgraph

using namespace geos::geomgraph::index;
using namespace geos::algorithm;

/**
 * Updates an IM from the label for an edge.
 * Handles edges from both L and A geometrys.
 */
void
Edge::updateIM(const Label& lbl, IntersectionMatrix& im)
{
	im.setAtLeastIfValid(lbl.getLocation(0,Position::ON),
	                      lbl.getLocation(1,Position::ON),
	                      1);
	if (lbl.isArea()) {

		im.setAtLeastIfValid(lbl.getLocation(0,Position::LEFT),
		                      lbl.getLocation(1,Position::LEFT),
		                      2);

		im.setAtLeastIfValid(lbl.getLocation(0,Position::RIGHT),
		                      lbl.getLocation(1,Position::RIGHT),
		                      2);
	}
}

/*public*/
Edge::~Edge()
{
	//cerr<<"["<<this<<"] ~Edge()"<<endl;
	delete mce;
	delete pts;
	delete env;
}

/*public*/
Edge::Edge(CoordinateSequence* newPts, const Label& newLabel)
	:
	GraphComponent(newLabel),
	mce(nullptr),
	env(nullptr),
	isIsolatedVar(true),
	depth(),
	depthDelta(0),
	pts(newPts),
	eiList(this)
{
	testInvariant();
}

/*public*/
Edge::Edge(CoordinateSequence* newPts)
	:
	GraphComponent(),
	mce(nullptr),
	env(nullptr),
	isIsolatedVar(true),
	depth(),
	depthDelta(0),
	pts(newPts),
	eiList(this)
{
	testInvariant();
}

/*public*/
MonotoneChainEdge*
Edge::getMonotoneChainEdge()
{
	testInvariant();
	if (mce==nullptr) mce=new MonotoneChainEdge(this);
	return mce;
}


/*public*/
bool
Edge::isCollapsed() const
{
	testInvariant();
	if (!label.isArea()) return false;
	if (getNumPoints()!= 3) return false;
	if (pts->getAt(0)==pts->getAt(2) ) return true;
	return false;
}

Edge*
Edge::getCollapsedEdge()
{
	testInvariant();
	CoordinateSequence *newPts = new CoordinateArraySequence(2);
	newPts->setAt(pts->getAt(0),0);
	newPts->setAt(pts->getAt(1),1);
	return new Edge(newPts, Label::toLineLabel(label));
}

/*public*/
void
Edge::addIntersections(LineIntersector *li, int segmentIndex, int geomIndex)
{
#if GEOS_DEBUG
	cerr<<"["<<this<<"] Edge::addIntersections("<<li->toString()<<", "<<segmentIndex<<", "<<geomIndex<<") called"<<endl;
#endif
	for (int i=0; i<li->getIntersectionNum();i++) {
		addIntersection(li,segmentIndex,geomIndex,i);
	}

	testInvariant();
}

/*public*/
void
Edge::addIntersection(LineIntersector *li,
	int segmentIndex, int geomIndex, int intIndex)
{
#if GEOS_DEBUG
	std::cerr << "["<<this<<"] Edge::addIntersection("<<li->toString()<<", "<<segmentIndex<<", "<<geomIndex<<", "<<intIndex<<") called"<< std::endl;
#endif
	const Coordinate& intPt=li->getIntersection(intIndex);
	unsigned int normalizedSegmentIndex=segmentIndex;
	double dist=li->getEdgeDistance(geomIndex,intIndex);

	// normalize the intersection point location
	unsigned int nextSegIndex=normalizedSegmentIndex+1;
	unsigned int npts=getNumPoints();
	if (nextSegIndex<npts)
	{
		const Coordinate& nextPt=pts->getAt(nextSegIndex);
        // Normalize segment index if intPt falls on vertex
        // The check for point equality is 2D only - Z values are ignored
		if (intPt.equals2D(nextPt)) {
			normalizedSegmentIndex=nextSegIndex;
			dist=0.0;
		}
	}

	/*
	 * Add the intersection point to edge intersection list.
	 */
#if GEOS_DEBUG
	cerr<<"Edge::addIntersection adding to edge intersection list point "<<intPt.toString()<<endl;
#endif
	eiList.add(intPt,normalizedSegmentIndex,dist);

	testInvariant();
}

/*public*/
bool
Edge::equals(const Edge& e) const
{
	testInvariant();

	unsigned int npts1=getNumPoints();
	unsigned int npts2=e.getNumPoints();

	if (npts1 != npts2 ) return false;

	bool isEqualForward=true;
	bool isEqualReverse=true;

	for (unsigned int i=0, iRev=npts1-1; i<npts1; ++i, --iRev)
	{
		const Coordinate &e1pi=pts->getAt(i);
		const Coordinate &e2pi=e.pts->getAt(i);
		const Coordinate &e2piRev=e.pts->getAt(iRev);

		if ( !e1pi.equals2D(e2pi) ) isEqualForward=false;
		if ( !e1pi.equals2D(e2piRev) ) isEqualReverse=false;
		if ( !isEqualForward && !isEqualReverse ) return false;
	}
	return true;
}

/*public*/
bool
Edge::isPointwiseEqual(const Edge *e) const
{
	testInvariant();

#if GEOS_DEBUG > 2
	cerr<<"Edge::isPointwiseEqual call"<<endl;
#endif
	unsigned int npts=getNumPoints();
	unsigned int enpts=e->getNumPoints();
	if (npts!=enpts) return false;
#if GEOS_DEBUG
	cerr<<"Edge::isPointwiseEqual scanning "<<enpts<<"x"<<npts<<" points"<<endl;
#endif
	for (unsigned int i=0; i<npts; ++i)
	{
		if (!pts->getAt(i).equals2D(e->pts->getAt(i))) {
			return false;
		}
	}
	return true;
}

string
Edge::print() const
{
	testInvariant();

	std::stringstream ss;
	ss << *this;
	return ss.str();
}

// Dunno how to implemente this in terms of operator<<
string
Edge::printReverse() const
{
	testInvariant();

	stringstream os;

	os << "EDGE (rev)";
	if ( name != "" ) os << " name:" << name;

	os << " label:" << label
	   << " depthDelta:" << depthDelta
	   << ":" << std::endl
	   << "  LINESTRING(";
	unsigned int npts=getNumPoints();
	for (unsigned int i=npts; i>0; --i)
	{
		if (i<npts) os << ", ";
		os << pts->getAt(i-1).toString();
	}
	os << ")";

	return os.str();
}

Envelope*
Edge::getEnvelope()
{
	// compute envelope lazily
	if (env==nullptr)
	{
		env=new Envelope();
		unsigned int npts=getNumPoints();
		for (unsigned int i = 0; i<npts; ++i)
		{
			env->expandToInclude(pts->getAt(i));
		}
	}
	testInvariant();
	return env;
}

std::ostream&
operator<< (std::ostream&os, const Edge& e)
{
	os << "edge";
	if ( e.name != "" ) os << " " << e.name;

	os
	   << "  LINESTRING"
	   << *(e.pts)
	   << "  " << e.label
	   << "  " << e.depthDelta
	   ;

	return os;
}

} // namespace geos.geomgraph
} // namespace geos

