// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <map>
#include <vector>

#include "nel/../../src/pacs/collision_mesh_build.h"
#include "nel/../../src/pacs/local_retriever.h"
#include "nel/../../src/pacs/exterior_mesh.h"

#include "mouline.h"
#include "build_surfaces.h"

using namespace std;
using namespace NLMISC;
using namespace NLPACS;

/*
// a reference on an edge
struct CEdgeKey
{
	uint32	V0;
	uint32	V1;

	CEdgeKey() {}
	CEdgeKey(uint32 v0, uint32 v1) : V0(v0), V1(v1) {}

	bool	operator() (const CEdgeKey &a, const CEdgeKey &b)
	{
		return a.V0 < b.V0 || (a.V0 == b.V0 && a.V1 < b.V1);
	}
};

// the info on an edge
struct CEdgeInfo
{
	sint32	Left, LeftEdge;
	sint32	Right, RightEdge;

	CEdgeInfo(sint32 left=-1, sint32 leftEdge=-1, sint32 right=-1, sint32 rightEdge=-1) : Left(left), LeftEdge(leftEdge), Right(right), RightEdge(rightEdge) {}
};

typedef	map<CEdgeKey, CEdgeInfo, CEdgeKey>	TLinkRelloc;
typedef TLinkRelloc::iterator				ItTLinkRelloc;


//
void	linkMesh(CCollisionMeshBuild &cmb, bool linkInterior)
{
	uint			i, j;
	TLinkRelloc		relloc;

	// check each edge of each face
	for (i=0; i<cmb.Faces.size(); ++i)
	{
		if (cmb.Faces[i].Surface == CCollisionFace::ExteriorSurface && linkInterior ||
			cmb.Faces[i].Surface >= CCollisionFace::InteriorSurfaceFirst && !linkInterior)
			continue;

		for (j=0; j<3; ++j)
		{
			cmb.Faces[i].Edge[j] = -1;

			uint	edge = (j+2)%3;
			uint32	va = cmb.Faces[i].V[j],
					vb = cmb.Faces[i].V[(j+1)%3];

			ItTLinkRelloc	it;
			if ((it = relloc.find(CEdgeKey(va, vb))) != relloc.end())
			{
				// in this case, the left triangle of the edge has already been found.
				// should throw an error
				nlerror("On face %d, edge %d: left side of edge (%d,%d) already linked to face %d",
						i, edge, va, vb, (*it).second.Left);
			}
			else if ((it = relloc.find(CEdgeKey(vb, va))) != relloc.end())
			{
				// in this case, we must check the right face has been set yet
				if ((*it).second.Right != -1)
				{
					nlerror("On face %d, edge %d: right side of edge (%d,%d) already linked to face %d",
							i, edge, vb, va, (*it).second.Right);
				}

				(*it).second.Right = i;
				(*it).second.RightEdge = edge;
			}
			else
			{
				// if the edge wasn't present yet, create it and set it up.
				relloc.insert(make_pair(CEdgeKey(va, vb), CEdgeInfo(i, edge, -1, -1)));
			}
		}
	}

	// for each checked edge, update the edge info inside the faces
	ItTLinkRelloc	it;
	for (it=relloc.begin(); it!=relloc.end(); ++it)
	{
		sint32	left, leftEdge;
		sint32	right, rightEdge;

		// get the link info on the edge
		left = (*it).second.Left;
		leftEdge = (*it).second.LeftEdge;
		right = (*it).second.Right;
		rightEdge = (*it).second.RightEdge;

		// update both faces
		if (left != -1)
			cmb.Faces[left].Edge[leftEdge] = right;
		if (right != -1)
			cmb.Faces[right].Edge[rightEdge] = left;
	}
}
*/



void	buildExteriorMesh(CCollisionMeshBuild &cmb, CExteriorMesh &em)
{
	uint							startFace = 0;
	vector<CExteriorMesh::CEdge>	edges;

	uint	i;
	for (i=0; i<cmb.Faces.size(); ++i)
	{
		cmb.Faces[i].EdgeFlags[0] = false;
		cmb.Faces[i].EdgeFlags[1] = false;
		cmb.Faces[i].EdgeFlags[2] = false;
	}
	
	while (true)
	{
		// find the first non interior face
		uint	i, edge;
		bool	found = false;
		for (i=startFace; i<cmb.Faces.size() && !found; ++i)
		{
			if (cmb.Faces[i].Surface != CCollisionFace::ExteriorSurface)
				continue;

			for (edge=0; edge<3 && !found; ++edge)
				if (cmb.Faces[i].Edge[edge] == -1 && !cmb.Faces[i].EdgeFlags[edge])
				{
//					nlassert(cmb.Faces[i].Material != 0xdeadbeef);
					found = true;
					break;
				}

			if (found)
				break;
		}

		//
		if (!found)
			break;

//		cmb.Faces[i].Material = 0xdeadbeef;

		startFace = i+1;

		sint32		current = i;
		sint32		next = cmb.Faces[current].Edge[edge];

		sint		oedge;
		sint		pivot = (edge+1)%3;
		sint		nextEdge = edge;
		bool		allowThis = true;

		uint		numLink = 0;
		uint		firstEdge = (uint)edges.size();

		vector<CExteriorMesh::CEdge>	loop;

		while (true)
		{
			if (cmb.Faces[current].EdgeFlags[nextEdge])
			{
				// if reaches the end of the border, then quits.
				break;
			}
			else if (next == -1)
			{
				// if the next edge belongs to the border, then go on the same element
				cmb.Faces[current].EdgeFlags[nextEdge] = true;
				/// \todo get the real edge link
				sint	link = (cmb.Faces[current].Visibility[nextEdge]) ? -1 : 0;	//(numLink++);

				//edges.push_back(CExteriorMesh::CEdge(cmb.Vertices[cmb.Faces[current].V[pivot]], link));
				loop.push_back(CExteriorMesh::CEdge(cmb.Vertices[cmb.Faces[current].V[pivot]], link));

				pivot = (pivot+1)%3;
				nextEdge = (nextEdge+1)%3;
				next = cmb.Faces[current].Edge[nextEdge];
			}
			else
			{
				// if the next element is inside the surface, then go to the next element
				for (oedge=0; oedge<3 && cmb.Faces[next].Edge[oedge]!=current; ++oedge)
					;
				nlassert(oedge != 3);
				current = next;
				pivot = (oedge+2)%3;
				nextEdge = (oedge+1)%3;
				next = cmb.Faces[current].Edge[nextEdge];
			}
		}

		// mark the end of a ext mesh block
		// this way, collisions won't be checked in the pacs engine
		if (loop.size() >= 3)
		{
			uint	n = (uint)loop.size();
			while (loop.front().Link >= 0 && loop.back().Link >= 0 && n > 0)
			{
				loop.push_back(loop.front());
				loop.erase(loop.begin());
				--n;
			}
		}
		loop.push_back(loop.front());
		loop.back().Link = -2;
		edges.insert(edges.end(), loop.begin(), loop.end());
		//edges.push_back(edges[firstEdge]);
		//edges.back().Link = -2;
	}

	bool	previousWasLink = false;
	sint	previousLink = -1;
	for (i=0; i<edges.size(); ++i)
	{
//		nldebug("ext-mesh: vertex=%d (%.2f,%.2f,%.2f) link=%d", i, edges[i].Start.x, edges[i].Start.y, edges[i].Start.z, edges[i].Link);
		if (edges[i].Link >= 0)
		{
			if (!previousWasLink)
				++previousLink;
			edges[i].Link = previousLink;
			previousWasLink = true;
		}
		else
		{
			previousWasLink = false;
		}
	}

	em.setEdges(edges);
}


//
void	linkExteriorToInterior(CLocalRetriever &lr)
{
	CExteriorMesh					em = lr.getExteriorMesh();
	vector<CExteriorMesh::CEdge>	edges = em.getEdges();
	vector<CExteriorMesh::CLink>	links;
	const vector<CChain>			&chains = lr.getChains();
	const vector<COrderedChain3f>	&ochains = lr.getFullOrderedChains();
	const vector<uint16>			&bchains = lr.getBorderChains();

	{
		uint	i;

		nlinfo("Border chains (to be linked) for this retriever:");

		for (i=0; i<bchains.size(); ++i)
		{
			static char	buf[512], w[256];
			const CChain	&chain = chains[bchains[i]];
			sprintf(buf, "Border chain %d: chain=%d ", i, bchains[i]);
			uint	och;
			for (och=0; och<chain.getSubChains().size(); ++och)
			{
				const COrderedChain3f	&ochain = ochains[chain.getSubChain(och)];
				sprintf(w, "subchain=%d", chain.getSubChain(och));
				strcat(buf, w);
				uint	v;
				for (v=0; v<ochain.getVertices().size(); ++v)
				{
					sprintf(w, " (%.2f,%.2f)", ochain[v].x, ochain[v].y);
					strcat(buf, w);
				}
			}

			nlinfo("%s", buf);
		}
	}

	uint	edge, ch;
	for (edge=0; edge+1<edges.size(); )
	{
		if (edges[edge].Link == -1 || edges[edge].Link == -2)
		{
			++edge;
			continue;
		}

		uint	startedge = edge;
		uint	stopedge;

		for (stopedge=edge; stopedge+1<edges.size() && edges[stopedge+1].Link == edges[startedge].Link; ++stopedge)
			;

		edge = stopedge+1;

		CVector	start = edges[startedge].Start, stop = edges[stopedge+1].Start;
		bool	found = false;

		for (ch=0; ch<bchains.size() && !found; ++ch)
		{
			// get the border chain.
			//const CChain	&chain = chains[bchains[ch]];

			const CVector	&cstart = lr.getStartVector(bchains[ch]),
							&cstop = lr.getStopVector(bchains[ch]);

			float d = (start-cstart).norm()+(stop-cstop).norm();
			if (d < 1.0e-1f)
			{
				found = true;
				break;
			}
		}

		// create a link
		CExteriorMesh::CLink	link;

		if (!found)
		{
			nlwarning("in linkInteriorToExterior():");
			nlwarning("couldn't find any link to the exterior edge %d-%d!!", startedge, stopedge);
		}
		else
		{
			// set it up to point on the chain and surface
			link.BorderChainId = ch;
			link.ChainId = bchains[ch];
			link.SurfaceId = (uint16)chains[link.ChainId].getLeft();
		}

		// enlarge the links
		if (edges[startedge].Link >= (sint)links.size())
			links.resize(edges[startedge].Link+1);

		// if the link already exists, warning
		if (links[edges[startedge].Link].BorderChainId != 0xFFFF ||
			links[edges[startedge].Link].ChainId != 0xFFFF ||
			links[edges[startedge].Link].SurfaceId != 0xFFFF)
		{
			nlwarning("in linkInteriorToExterior():");
			nlwarning("link %d already set!!", edges[startedge].Link);
		}

		// setup the link
		links[edges[startedge].Link] = link;
	}

//	em.setEdges(edges);
	em.setLinks(links);
	lr.setExteriorMesh(em);
}





//
void	computeRetriever(CCollisionMeshBuild &cmb, CLocalRetriever &lr, CVector &translation, bool useCmbTrivialTranslation)
{
	// set the retriever
	lr.setType(CLocalRetriever::Interior);

	// if should use the own cmb bbox, then compute it
	if (useCmbTrivialTranslation)
	{
		translation = cmb.computeTrivialTranslation();
		// snap the translation vector to a meter wide grid
		translation.x = (float)ceil(translation.x);
		translation.y = (float)ceil(translation.y);
		translation.z = 0.0f;
	}

	uint	i, j;

	for (i=0; i<cmb.Faces.size(); ++i)
	{
		CVector		normal = ((cmb.Vertices[cmb.Faces[i].V[1]]-cmb.Vertices[cmb.Faces[i].V[0]])^(cmb.Vertices[cmb.Faces[i].V[2]]-cmb.Vertices[cmb.Faces[i].V[0]])).normed();

		if (normal.z < 0.0f)
		{
			nlwarning("Face %d in cmb (%s) has negative normal! -- face is flipped", i, cmb.Faces[i].Surface == CCollisionFace::InteriorSurfaceFirst ? "interior" : "exterior");
/*
			std::swap(cmb.Faces[i].V[1], cmb.Faces[i].V[2]);
			std::swap(cmb.Faces[i].Visibility[1], cmb.Faces[i].Visibility[2]);
*/
		}
	}

	// first link faces
/*
	linkMesh(cmb, false);
	linkMesh(cmb, true);
*/
	vector<string>	errors;
	
	cmb.link(false, errors);
	cmb.link(true, errors);

	if (!errors.empty())
	{
		nlwarning("Edge issues reported !!");
		uint	i;
		for (i=0; i<errors.size(); ++i)
			nlwarning("%s", errors[i].c_str());
		nlerror("Can't continue.");
	}
	
	// translate the meshbuild to the local axis
	cmb.translate(translation);

	// find the exterior mesh border
	CExteriorMesh	extMesh;
	buildExteriorMesh(cmb, extMesh);
	lr.setExteriorMesh(extMesh);

	// build the surfaces in the local retriever
	buildSurfaces(cmb, lr);

	// create the snapping faces and vertices
	// after the build surfaces because the InternalSurfaceId is filled within buildSurfaces()...
	buildSnapping(cmb, lr);

	//
	lr.computeLoopsAndTips();

	lr.findBorderChains();
	lr.updateChainIds();
	lr.computeTopologies();

	lr.unify();

	lr.computeCollisionChainQuad();
/*
	//
	for (i=0; i<lr.getSurfaces().size(); ++i)
		lr.dumpSurface(i);
*/
	//
	linkExteriorToInterior(lr);

	// compute the bbox of the retriever
	CAABBox	bbox;
	bool	first = true;

	for (i=0; i<extMesh.getEdges().size(); ++i)
		if (!first)
			bbox.extend(extMesh.getEdge(i).Start);
		else
			bbox.setCenter(extMesh.getEdge(i).Start), first=false;

	for (i=0; i<lr.getOrderedChains().size(); ++i)
		for (j=0; j<lr.getOrderedChain(i).getVertices().size(); ++j)
			if (!first)
				bbox.extend(lr.getOrderedChain(i)[j].unpack3f());
			else
				bbox.setCenter(lr.getOrderedChain(i)[j].unpack3f()), first=false;

	CVector	bboxhs = bbox.getHalfSize();
	bboxhs.z = 10000.0f;
	bbox.setHalfSize(bboxhs);

	lr.setBBox(bbox);
}