// 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 "surface_splitter.h"

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

// -----------------------
//

// Constructor
CSurfaceSplitter::CSurfaceSplitter()
{
}


//
void	CSurfaceSplitter::build(CLocalRetriever &lr)
{
	nlinfo("--- Build SurfaceSplitter...");
	nlinfo("------------------------------------------");
	uint	i;

	initEdgeGrid();

	_NumChains = 0;
	_NumSurfaces = 0;
	_NumTips = 0;

	for (i=0; i<lr.getChains().size(); ++i)
		buildChain(lr, i);

	for (i=0; i<lr.getSurfaces().size(); ++i)
		buildSurface(lr, i);

	nlinfo("converted %d chains & %d surfaces", lr.getChains().size(), lr.getSurfaces().size());
	_NumSurfaces = (uint)lr.getSurfaces().size();
	_NumChains = (uint)lr.getChains().size();

	//splitChains();

	dump();

	nlinfo("------------------------------------------");
}

//
void	CSurfaceSplitter::buildChain(CLocalRetriever &lr, uint chain)
{
	const NLPACS::CChain	&pacsChain = lr.getChain(chain);

	CSurfaceId				left = pacsChain.getLeft() >= 0 ? CSurfaceId(0, (uint16)pacsChain.getLeft()) : CSurfaceId();
	CSurfaceId				right = pacsChain.getRight() >= 0 ? CSurfaceId(0, (uint16)pacsChain.getRight()) : CSurfaceId();
	vector<CVector2s64>		vertices;

	uint	i;
	// walk through all subchains
	for (i=0; i<pacsChain.getSubChains().size(); ++i)
	{
		uint	ochain = pacsChain.getSubChain(i);
		const NLPACS::COrderedChain	&pacsOChain = lr.getOrderedChain(ochain);

		sint	j;
		// walk through subchain, forward or backward
		if (pacsOChain.isForward())
		{
			for (j=0; j<(sint)pacsOChain.getVertices().size()-1; ++j)
				vertices.push_back(CVector2s64(pacsOChain[j]));
		}
		else
		{
			for (j=(sint)pacsOChain.getVertices().size()-1; j>0; --j)
				vertices.push_back(CVector2s64(pacsOChain[j]));
		}

		// add final chain point
		if (i == pacsChain.getSubChains().size()-1)
			vertices.push_back(CVector2s64(pacsOChain[j]));
	}

	addChain(left,right, vertices);
}

//
void	CSurfaceSplitter::buildSurface(CLocalRetriever &lr, uint surface)
{
	const NLPACS::CRetrievableSurface	&pacsSurface = lr.getSurface(surface);
	CSurface							newSurface;

	newSurface.Id = CSurfaceId(0, surface);

	uint	i;
	// walk through all loops
	for (i=0; i<pacsSurface.getLoops().size(); ++i)
	{
		const NLPACS::CRetrievableSurface::TLoop	&pacsLoop = pacsSurface.getLoop(i);
		newSurface.Loops.push_back(CSurfaceSplitter::CLoop());
		newSurface.Loops.back().Surface = newSurface.Id;

		uint	j;
		// walk through loop
		for (j=0; j<pacsLoop.size(); ++j)
		{
			uint	chainIndex = pacsLoop[j];
			uint	chain = pacsSurface.getChain(chainIndex).Chain;

			newSurface.Loops.back().Chains.push_back(CChainId(chain));
		}
	}

	_Surfaces.insert(make_pair(newSurface.Id, newSurface));
}



//
void	CSurfaceSplitter::initEdgeGrid()
{
	_Edges.create(256, 2.0f);
}


//
void	CSurfaceSplitter::splitChains()
{
	nlinfo("Split chains...");

	uint	numInters = 0;
	uint	i;

	for (i=0; i<_NumChains; ++i)
	{
		TChainMap::iterator	it = _Chains.find(CChainId(i));
		if (it != _Chains.end())
			splitChain(it, numInters);
	}

	nlinfo("%d intersections found", numInters);
}

//
void	CSurfaceSplitter::splitChain(TChainMap::iterator it, uint &numInters)
{
	CChain	&chain = (*it).second;

	if (chain.DontSplit)
		return;

	uint	edge;
	// for each edge of the chain, test for other edges collision
	for (edge=0; edge<chain.Vertices.size()-1; ++edge)
	{
		CVector	p0 = chain.Vertices[edge].asVector();
		CVector	p1 = chain.Vertices[edge+1].asVector();
		CVector	pmin, pmax;
		pmin.minof(p0, p1);
		pmax.maxof(p0, p1);

		_Edges.select(pmin, pmax);

		CFixed64	closerDist(10.0);
		bool		collisionFound = false;
		CEdgeId		collidingEdge;
		CVector2s64	collision;

		TEdgeGrid::CIterator	it;
		for (it=_Edges.begin(); it!=_Edges.end(); ++it)
		{
			CEdgeId		iedge = *it;

			//
			if (chain.Id == iedge.Chain && (edge == iedge.Edge || edge+1 == iedge.Edge || edge-1 == iedge.Edge))
				continue;

			CChain		*ichain = getChain(iedge.Chain);

			if (ichain == NULL)
			{
				nlwarning("Couldn't find referenced chain %d", iedge.Chain.Id);
				continue;
			}

			if (ichain->DontSplit)
				continue;

			CVector2s64	inters;
			CFixed64	ndist;

			if (intersect(chain.Vertices[edge], chain.Vertices[edge+1], ichain->Vertices[iedge.Edge], ichain->Vertices[iedge.Edge+1], inters, ndist))
			{
				if (inters != chain.Vertices[edge+1] || edge != chain.Vertices.size()-2)
				{
					++numInters;
					nlinfo("Intersection: %d:%d[%.3f,%.3f-%.3f,%.3f]-%d:%d[%.3f,%.3f-%.3f,%.3f] : [%.3f,%.3f]", chain.Id.Id, edge, p0.x, p0.y, p1.x, p1.y, iedge.Chain.Id, iedge.Edge, ichain->Vertices[iedge.Edge].asVector().x, ichain->Vertices[iedge.Edge].asVector().y, ichain->Vertices[iedge.Edge+1].asVector().x, ichain->Vertices[iedge.Edge+1].asVector().y, inters.asVector().x, inters.asVector().y);

					if (closerDist > ndist)
					{
						collisionFound = true;
						collidingEdge = iedge;
						collision = inters;
					}
				}
				else
				{
					nlinfo("Intersection: %d:%d[%.3f,%.3f-%.3f,%.3f]-%d:%d[%.3f,%.3f-%.3f,%.3f] : [%.3f,%.3f] -- SKIPPED", chain.Id.Id, edge, p0.x, p0.y, p1.x, p1.y, iedge.Chain.Id, iedge.Edge, ichain->Vertices[iedge.Edge].asVector().x, ichain->Vertices[iedge.Edge].asVector().y, ichain->Vertices[iedge.Edge+1].asVector().x, ichain->Vertices[iedge.Edge+1].asVector().y, inters.asVector().x, inters.asVector().y);
				}
			}
		}

		// split chain
		if (collisionFound)
		{
			if (chain.Id == collidingEdge.Chain)
			{
				// self colliding chain

				// check order
				//nlassert(edge >= 0); // always true for unsigned
				nlassert(edge < collidingEdge.Edge);
				nlassert(collidingEdge.Edge < chain.Vertices.size()-1);

				// must split the chain in 3 parts
				uint				e;
				vector<CVector2s64>	begin;
				vector<CVector2s64>	middle;
				vector<CVector2s64>	end;
				vector<CChainId>	v;
				CChainId			beginId;
				CChainId			middleId;
				CChainId			endId;

				for (e=0; e<=edge; ++e)
					begin.push_back(chain.Vertices[e]);

				begin.push_back(collision);

				if (collision != chain.Vertices[e])
					middle.push_back(collision);

				for (; e<=collidingEdge.Edge; ++e)
					middle.push_back(chain.Vertices[e]);

				middle.push_back(collision);

				if (collision != chain.Vertices[e])
					end.push_back(collision);

				for (; e<chain.Vertices.size(); ++e)
					end.push_back(chain.Vertices[e]);

				beginId = addChain(chain.Left, chain.Right, begin, true);
				middleId = addChain(chain.Left, chain.Right, middle);
				endId = addChain(chain.Left, chain.Right, end);

				v.push_back(beginId);
				v.push_back(middleId);
				v.push_back(endId);

				replaceChain(chain.Id, v);
			}
			else
			{
				//nlassert(edge >= 0); // always true for unsigned
				nlassert(edge < chain.Vertices.size()-1);

				// split the chain
				uint				e;
				vector<CVector2s64>	begin;
				vector<CVector2s64>	end;
				vector<CChainId>	v;
				CChainId			beginId;
				CChainId			endId;

				// split the first chain
				for (e=0; e<=edge; ++e)
					begin.push_back(chain.Vertices[e]);

				begin.push_back(collision);

				if (collision != chain.Vertices[e])
					end.push_back(collision);

				for (; e<chain.Vertices.size(); ++e)
					end.push_back(chain.Vertices[e]);

				beginId = addChain(chain.Left, chain.Right, begin, true);
				endId = addChain(chain.Left, chain.Right, end);

				v.push_back(beginId);
				v.push_back(endId);

				replaceChain(chain.Id, v);

				// reset for second chain
				begin.clear();
				end.clear();
				v.clear();

				// split the second chain
				CChain	*collide = getChain(collidingEdge.Chain);

				for (e=0; e<=collidingEdge.Edge; ++e)
					begin.push_back(collide->Vertices[e]);

				begin.push_back(collision);

				if (collision != collide->Vertices[e])
					end.push_back(collision);

				for (; e<collide->Vertices.size(); ++e)
					end.push_back(collide->Vertices[e]);

				beginId = addChain(collide->Left, collide->Right, begin);
				endId = addChain(collide->Left, collide->Right, end);

				v.push_back(beginId);
				v.push_back(endId);

				replaceChain(collide->Id, v);
			}

			return;
		}
	}
}

//
bool	CSurfaceSplitter::intersect(const CVector2s64 &v0, const CVector2s64 &v1,
									const CVector2s64 &c0, const CVector2s64 &c1,
									CVector2s64 &intersect,
									CFixed64 &ndist)
{
	if (v0 == c0 || v0 == c1)
		return false;

	if (v1 == c0 || v1 == c1)
	{
		intersect = v1;
		ndist = CFixed64(1.0);
		return true;
	}

	CVector2s64		nnc(c0.y-c1.y, c1.x-c0.x),
					nnv(v0.y-v1.y, v1.x-v0.x);

	CFixed64		dv = (v1-v0)*nnc;

	// vectors are colinears ?
	if ((sint64)dv == 0)
		return false;

	CFixed64		nv = (c0-v0)*nnc;

	if ((sint64)dv < 0)
		dv=-dv, nv=-nv;
	// intersection outside main edge ? (or at first point ?)
	if ((sint64)nv<=0 || nv>dv)
		return false;

	CFixed64		dc = (c1-c0)*(c1-c0);
	// second edge null ?
	if ((sint64)dc == 0)
		return false;

	CFixed64		lv = nv/dv;

	if ((sint64)lv == 0)
		return false;

	CFixed64		nc = (v0-c0 + (v1-v0)*lv)*(c1-c0);
	// intersection outside colliding edge ?
	if ((sint64)nc<0 || nc>dc)
		return false;

	// treat each singular case
	if (nv == dv)
		intersect = v1;
	else if ((sint64)nc == 0)
		intersect = c0;
	else if (nc == dc)
		intersect = c1;
	else
		// compute intersecting point
		intersect = v0 + (v1-v0)*lv;

	ndist = lv;

	return true;
}



//
CSurfaceSplitter::CChainId	CSurfaceSplitter::addChain(const CSurfaceId &left, const CSurfaceId &right, const vector<CVector2s64> &points, bool dontSplit)
{
	pair<TChainMap::iterator, bool>	res = _Chains.insert(make_pair(CChainId(_NumChains++), CChain()));

	CChain	&chain = (*(res.first)).second;

	chain.Id = (*(res.first)).first;
	chain.Left = left;
	chain.Right = right;

	uint	i;
	for (i=0; i<points.size()-1; ++i)
	{
		if (points[i] == points[i+1])
		{
			nlwarning("--- !! points are together !! ---");
			nlstop;
		}
	}

	chain.Vertices = points;
	chain.DontSplit = dontSplit;

	uint	edge;
	for (edge=0; edge<chain.Vertices.size()-1; ++edge)
	{
		CVector	p0 = chain.Vertices[edge].asVector();
		CVector	p1 = chain.Vertices[edge+1].asVector();

		p0.z = -100.0f;
		p1.z = +100.0f;

		CVector	pmin, pmax;
		pmin.minof(p0, p1);
		pmax.maxof(p0, p1);

		chain.Iterators.push_back(_Edges.insert(pmin, pmax, CEdgeId(chain.Id, edge)));
	}

	return chain.Id;
}

//
void	CSurfaceSplitter::removeChain(CChainId chainId)
{
	// get chain it
	TChainMap::iterator	it = _Chains.find(chainId);

	if (it == _Chains.end())
		return;

	CChain	&chain = (*it).second;

	CSurface	*surf;
	
	if ((surf = getSurface(chain.Left)))
		surf->removeChain(chainId);

	if ((surf = getSurface(chain.Right)))
		surf->removeChain(chainId);
}

//
void	CSurfaceSplitter::replaceChain(CChainId chainId, const vector<CChainId> &chains)
{
	// get chain it
	TChainMap::iterator	it = _Chains.find(chainId);

	if ((it == _Chains.end()))
		return;

	CChain	&chain = (*it).second;

	CSurface	*surf;

	nlinfo("-- Replace --");
	dumpChain(chainId);
	nlinfo("-- By --");
	uint	c;
	for (c=0; c<chains.size(); ++c)
		dumpChain(chains[c]);
	nlinfo("-- End Replace --");
	
	if ((surf = getSurface(chain.Left)))
	{
		uint	loop;
		for (loop=0; loop<surf->Loops.size(); ++loop)
		{
			CLoop	&ploop = surf->Loops[loop];
			vector<CChainId>::iterator	it;
			for (it=ploop.Chains.begin(); it!=ploop.Chains.end(); ++it)
			{
				if (*it == chainId)
				{
					it = ploop.Chains.erase(it);
					sint	i;
					for (i=(sint)chains.size()-1; i>=0; --i)
					{
						it = ploop.Chains.insert(it, chains[i]);
					}
				}
			}
		}
	}

	if ((surf = getSurface(chain.Right)))
	{
		uint	loop;
		for (loop=0; loop<surf->Loops.size(); ++loop)
		{
			CLoop	&ploop = surf->Loops[loop];
			vector<CChainId>::iterator	it;
			for (it=ploop.Chains.begin(); it!=ploop.Chains.end(); ++it)
			{
				if (*it == chainId)
				{
					it = ploop.Chains.erase(it);
					uint	i;
					for (i=0; i<chains.size(); ++i)
					{
						it = ploop.Chains.insert(it, chains[i]);
					}
				}
			}
		}
	}

	uint	i;
	for (i=0; i<chain.Iterators.size(); ++i)
		_Edges.erase(chain.Iterators[i]);

	_Chains.erase(it);
}