//- ****************************************************************************
//- 
//- Copyright 2009 Sandia Corporation. Under the terms of Contract
//- DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government
//- retains certain rights in this software.
//- 
//- BSD Open Source License.
//- All rights reserved.
//- 
//- Redistribution and use in source and binary forms, with or without
//- modification, are permitted provided that the following conditions are met:
//- 
//-    * Redistributions of source code must retain the above copyright notice,
//-      this list of conditions and the following disclaimer.
//-    * Redistributions in binary form must reproduce the above copyright
//-      notice, this list of conditions and the following disclaimer in the
//-      documentation and/or other materials provided with the distribution.
//-    * Neither the name of Sandia National Laboratories nor the names of its
//-      contributors may be used to endorse or promote products derived from
//-      this software without specific prior written permission.
//- 
//- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
//- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//- POSSIBILITY OF SUCH DAMAGE.
//-
//- ****************************************************************************

#include <sstream>
#include <set>

// use standard library objects
using namespace std;

// **** _LOCAL INCLUDES_ *******************************************************

#include "GeoTessPositionNaturalNeighbor.h"
#include "CPPUtils.h"
#include "GeoTessUtils.h"
#include "GeoTessException.h"
#include "Profile.h"
#include "ProfileType.h"

// **** _BEGIN GEOTESS NAMESPACE_ **********************************************

namespace geotess {

// **** _EXPLICIT TEMPLATE INSTANTIATIONS_ *************************************

// **** _STATIC INITIALIZATIONS_************************************************

// **** _FUNCTION IMPLEMENTATIONS_ *********************************************

/**
 * Standard Constructor.
 */
GeoTessPositionNaturalNeighbor::GeoTessPositionNaturalNeighbor(GeoTessModel* model,
		const InterpolatorType& radialType) :
			GeoTessPosition(model, radialType, nnCoefficients)
{
	store.initialize(4, 12, 6, 8, 18);
}

/**
 * Destructor.
 */
GeoTessPositionNaturalNeighbor::~GeoTessPositionNaturalNeighbor()
{
}

/**
 * Update the vertices and their associated interpolation coefficients, that will be used to
 * interpolate new values. Uses the natural neighbor interpolation algorithm originally proposed
 * by Sibson, R. (1980), A Vector Identity For Dirichlet Tessellation, Proc. Cambridge
 * Philosophical Society, 87, 151-155. The algorithm is described in detail in Hipp, J. R., C.
 * J. Young, S. G. Moore, E. R. Shepherd, C. A. Schultz and S. C. Myers (1999), Parametric Grid
 * Information in the DOE Knowledge Base: Data Preparation, Storage, and Access, SAND99-2277,
 * Sandia National Laboratories Report.
 */
void GeoTessPositionNaturalNeighbor::update2D(int tid)
{
	int DEBUG = 0;

	if (DEBUG > 0)
	{
		cout << endl << endl << endl
				 << "******************************************************************"
				 << endl << endl << "update2D tessid = " << tid << endl << endl;
	}

	int trngl = getTriangle(tid);
  vector<int>& vt = vertices[tid];
  vector<double>& hc = hCoefficients[tid];

	for (int corner = 0; corner < 3; ++corner)
	{
		int vertex = grid.getTriangleVertexIndex(trngl, corner);

		// if the interpolation point falls on a grid node:
		if (GeoTessUtils::parallel(unitVector,
				grid.getVertex(vertex)))
		{
			// the interpolation point coincides with one of the corners of
			// the triangle in which the interpolation point resides.
			// Set the list of interpolation vertices to include only
			// the identified vertex, and the interpolation coefficient = 1.

			vt.clear(); vt.push_back(vertex);
			hc.clear(); hc.push_back(1.0);
			return;
		}
	}

	// The interpolation point does not coincide with a grid vertex.
	// Carry on.

	// get the tessellation level, relative to the first tessellation
	// level of the current tessellation, that was discovered the last
	// time triangle walk algorithm was run.
	int t;
	int level = getTessLevel(tid);

	std::set<int> nnTriangles;
	std::set<int> testedTriangles;

	nnTriangles.insert(trngl);
	testedTriangles.insert(trngl);

	double centerTmp[3];
	for (int corner = 0; corner < 3; ++corner)
	{
		int vertex = grid.getTriangleVertexIndex(trngl, corner);

		// get indexes of triangles that contain this corner, and add them
		// to the set of all possible triangles.
		const vector<int>& vertexTriangles = grid.getVertexTriangles(tid, level, vertex);

		for (int i = 0; i < (int) vertexTriangles.size(); ++i)
		{
			t = vertexTriangles[i];
			if (testedTriangles.find(t) == testedTriangles.end())
			{
				testedTriangles.insert(t);
				// this is a triangle that has not been tested yet.
				// Get the circumcenter of this triangle.
				const double* center = grid.getCircumCenter(t, centerTmp);
				// if the distance from the interpolation point to the
				// circumcenter is less than
				// the radius of the circumcircle then add the triangle to
				// nnTriangles.
				if (GeoTessUtils::dot(center, unitVector) >
						GeoTessUtils::dot(center, grid.getTriangleVertex(t, 0)))
					nnTriangles.insert(t);
			}
		}
	}

	if (DEBUG > 0)
	{
		cout << "nnTriangles = [";
		std::set<int>::iterator it, itlast;
		itlast = nnTriangles.end();
		--itlast;
		for (it = nnTriangles.begin(); it != itlast; ++it)
			cout << *it << ", ";
		cout << *itlast << "]" << endl;
	}

	// make sure storage of edge vectors is ready to go

	store.resetIfRequired();

	// define an edge to be an int[3] that contains the indexes of the two
	// vertices that define the edge, and
	// the last index is the triangle index. The two vertices on the edge
	// are defined in counterclockwise order,
	// relative to the defining nnTriangle. In other words, the triangle is
	// on the left side of the edge.

	list<int*> edges;
	std::set<int>::iterator it;
	for (it = nnTriangles.begin(); it != nnTriangles.end(); ++it)
	{
		// visit each edge of every triangles identified above. If the
		// neighbor on the other side of the edge
		// is not one of the identified triangles (nnTriangles), then add
		// the edge to the list of edges.
		t = *it;
		const int* neighborTriangles = grid.getNeighbors(t);

		if (nnTriangles.find(neighborTriangles[0]) == nnTriangles.end())
		{
			int* edge = store.getArray();
			edge[0] = grid.getTriangleVertexIndex(t, 2);
			edge[1] = grid.getTriangleVertexIndex(t, 1);
			edge[2] = t; edge[3] = -1;
			edges.push_back(edge);
		}
		if (nnTriangles.find(neighborTriangles[1]) == nnTriangles.end())
		{
			int* edge = store.getArray();
			edge[0] = grid.getTriangleVertexIndex(t, 0);
			edge[1] = grid.getTriangleVertexIndex(t, 2);
			edge[2] = t; edge[3] = -1;
			edges.push_back(edge);
		}
		if (nnTriangles.find(neighborTriangles[2]) == nnTriangles.end())
		{
			int* edge = store.getArray();
			edge[0] = grid.getTriangleVertexIndex(t, 1);
			edge[1] = grid.getTriangleVertexIndex(t, 0);
			edge[2] = t; edge[3] = -1;
			edges.push_back(edge);
		}
	}

	// put them in order such that edge[i-1][1] == edge[i][0]
	orderEdges(edges, 0);

	if (DEBUG > 0)
	{
		string hdr = "Surrounding polygon";
		cout << printEdges(hdr, edges) << endl;
	}

	vt.clear();
	vt.reserve(edges.size());
	hc.clear();
	hc.reserve(edges.size());

	double ip1[3], ip2Tmp[3], ip3Tmp[3];
	const double* ip2;
	const double* ip3;

	int *cEdge, *nextEdge;
	int* preEdge = edges.back();
	bool lastEdge;
	double totalWeight = 0.0;

	list<int*> surroundingEdges;
	double const* const* gridVrtcs = grid.getVertices();

	list<int*>::iterator edgeListIt;
	for (edgeListIt = edges.begin(); edgeListIt != edges.end(); ++edgeListIt)
	{
		vt.push_back(preEdge[1]);
		hc.push_back(0.0);
		double& coef = hc.back();

		if (DEBUG > 0)
		{
			cout	<< "pos      = "
						<< GeoTessUtils::getLatLonString(unitVector) << endl;
			cout	<< "preEdge0 = "
						<< GeoTessUtils::getLatLonString(gridVrtcs[preEdge[0]]) << endl;
			cout	<< "preEdge1 = "
						<< GeoTessUtils::getLatLonString(gridVrtcs[preEdge[1]]) << endl;
		}

		// set ip1 to the virtual veronoi vertex of the triangle formed by
		// interpolationPoint and preEdge
		GeoTessUtils::circumCenter(unitVector, gridVrtcs[preEdge[1]],
																gridVrtcs[preEdge[0]], ip1);

		grid.getEdgeList(tid, level, preEdge[1], preEdge[0], store, surroundingEdges);

		if (DEBUG > 0)
		{
			string hdr = "Spokes:";
			cout << printEdges(hdr, surroundingEdges) << endl;
		}

		// the first surrounding edge corresponds to a reversed version of
		// preEdge.

		store.reuseArray(preEdge);
		cEdge = surroundingEdges.front();
		surroundingEdges.pop_front();

		// set ip2 to the circumCenter of the nnTriangle that is to the
		// right of cEdge

		ip2 = grid.getCircumCenter(cEdge[3], ip2Tmp);
		store.reuseArray(cEdge);

		while (true)
		{
			nextEdge = surroundingEdges.front();
			surroundingEdges.pop_front();

			// if the triangle to the right of current edge is not one of
			// the nnTriangles, then
			// this is the last edge.

			lastEdge = (nnTriangles.find(nextEdge[3]) == nnTriangles.end());

			if (lastEdge)
			{
				// set ip3 to the virtual veronoi vertex of the triangle
				// formed by interpolationPoint and cEdge
				ip3 = &ip3Tmp[0];
				GeoTessUtils::circumCenter(unitVector, gridVrtcs[nextEdge[1]],
																		gridVrtcs[nextEdge[0]], ip3Tmp);
			}
			else
				// set ip3 to the circumcenter of the triangle to the right
				// of the current edge.
				ip3 = grid.getCircumCenter(nextEdge[3], ip3Tmp);

      store.reuseArray(nextEdge);
			coef += GeoTessUtils::getTriangleArea(ip1, ip2, ip3);

			if (DEBUG > 0)
			{
				char buf[300];
				string s1 = GeoTessUtils::getLatLonString(ip3);
				sprintf(buf, "ip1 = %s  dist,az = %10.4f %10.4f", s1.c_str(),
								GeoTessUtils::angleDegrees(unitVector, ip1),
								GeoTessUtils::azimuthDegrees(unitVector, ip1, -999.));
				cout << buf << endl;
				string s2 = GeoTessUtils::getLatLonString(ip3);
				sprintf(buf, "ip2 = %s  dist,az = %10.4f %10.4f", s2.c_str(),
								GeoTessUtils::angleDegrees(unitVector, ip2),
								GeoTessUtils::azimuthDegrees(unitVector, ip2, -999.));
				cout << buf << endl;
				string s3 = GeoTessUtils::getLatLonString(ip3);
				sprintf(buf, "ip3 = %s  dist,az = %10.4f %10.4f", s3.c_str(),
								GeoTessUtils::angleDegrees(unitVector, ip3),
								GeoTessUtils::azimuthDegrees(unitVector, ip3, -999.));
				cout << buf << endl;
				sprintf(buf, "Area = %1.4e", GeoTessUtils::getTriangleArea(ip1, ip2, ip3));
				cout << buf << endl << endl;
			}

			if (lastEdge) break;

			if (grid.getEdges() == NULL)
			{
				ip2Tmp[0] = ip3Tmp[0]; ip2Tmp[1] = ip3Tmp[1]; ip2Tmp[2] = ip3Tmp[2];
			}
			else
				ip2 = ip3;
		}

		// sum coefficient to total weight, get next edge, and continue

		totalWeight += coef;

		preEdge = *edgeListIt;
	}

	// normalize the interpolation coefficients.

	for (int i = 0; i < (int) vt.size(); ++i) hc[i] /= totalWeight;

	if (DEBUG > 0)
	{
		char buf[100];
		for (int i = 0; i < (int) vt.size(); ++i)
		{
			sprintf(buf, "%6d %8.16f", vt[i], hc[i]);
			cout << buf << endl;
		}
	}
}

/**
 * Take a list of edges and reorder them in one of two ways: If index is zero, then edges are
 * put into order where second vertex of one edge is coincident with the first vertex of the
 * next edge. In this mode, the edges will form a polygon. If index is 2, then the edges are put
 * into order such that the triangle to right of one edge is the same as the triangle to the
 * left of the next edge. In this mode, the edges will look like spokes emanating from a common
 * vertex.
 *
 * @param edges
 * @param index
 * @throws GeoTessException
 */
void GeoTessPositionNaturalNeighbor::orderEdges(list<int*>& edges, int index)
{
	int maxCount = 100 * edges.size();
	int count = 0;

	// order edges

	list<int*>::iterator itin = edges.begin();
	int* current = *itin;
	++itin;
	list<int*>::iterator itparse = edges.begin();
	++itparse;
	while (itparse != edges.end())
	{
	  int* next = *itparse;
	  if (next[index] == current[index + 1])
	  {
	    if (*itin == *itparse)
	    {
	      ++itin;
	    }
	    else
	    {
	      edges.insert(itin, next);
	      edges.erase(itparse);
	    }
	    current = next;
	    itparse = itin;
	  }
	  else
	    ++itparse;

		// if count == maxCount then error occured ... throw

		++count;
		if (count == maxCount)
		{
			string hdr = "Supplied list of edges do not join a continuous list of triangles.";
			throw GeoTessException(printEdges(hdr, edges), __FILE__, __LINE__, 3201);
		}
	}
}

/**
 * Dump input edge list.
 */
string GeoTessPositionNaturalNeighbor::printEdges(const string& header, list<int*>& edges)
{
	char s[300];
	ostringstream os;
	os << endl << header << endl;
	list<int*>::iterator itin = edges.begin();
	for (itin = edges.begin(); itin != edges.end(); ++itin)
	{
		for (int i = 0; i < 4; ++i)
		{
			sprintf(s, " %8d", (*itin)[i]);
			os << s;
		}
		os << endl;
	}
	return os.str();
}

} // end namespace geotess
