/**
* Path Manager
* @version: 01-29-2005
* @author Joseph 'Pcjoe' Florencio
*/

#include "fxsys.h"

// Constructors
CPathManager::CPathManager()
{	setID(0);
	setFXEvent(NULL);
}

CPathManager::CPathManager(int id)
{	CPathManager();
	setID(id);
}

CPathManager::CPathManager(CFXEvent *pEvent, TiXmlElement *pElement)
{	CPathManager();
	setFXEvent(pEvent);
	readXML(pElement);
}

// Destructor
CPathManager::~CPathManager()
{	std::list<CPathSegment*>::iterator del;
	ForEachIn(del, m_PathSegments)
	{	delete (*del);
		(*del) = NULL;
	}
	m_PathSegments.clear();
}

// Get Length
float CPathManager::getLength( void )
{	float flLength=0;
	std::list<CPathSegment*>::iterator iterCurrent;
	std::list<CPathSegment*>::iterator iterPrev=NULL;
	ForEachIn(iterCurrent, m_PathSegments)
	{	CPathSegment *pTest = (*iterCurrent);
		flLength += (*iterCurrent)->getLength();

		// Account for space between segments
		if(iterPrev != NULL)
		{	flLength += ((*iterPrev)->getEndPoint()-(*iterCurrent)->getStartPoint()).Length();
		}
		iterPrev = iterCurrent;
	}
	return flLength;
}

// Add to list
void CPathManager::addToList( CPathSegment *pSeg )
{	pSeg->setFXEvent(getFXEvent());
	m_PathSegments.push_back(pSeg);
}

// Get Current (latest) Segment
CPathSegment *CPathManager::getCurrentSegment( void )
{	if(m_PathSegments.empty())
	{	return NULL;
	}
	return *(--m_PathSegments.end());
}

// Add Linear Path
CLinearPath *CPathManager::addLinearPath( CFXDataPoint dp1, CFXDataPoint dp2 )
{	CLinearPath *pLinear = addLinearPath();
	if(pLinear)
	{	// Add points
		pLinear->addPoint( dp1 );
		pLinear->addPoint( dp2 );
	}
	return pLinear;
}
CLinearPath *CPathManager::addLinearPath( void )
{	CLinearPath *pLinear = NULL;
	CPathSegment *pSegment = getCurrentSegment();
	// Use existing linear path
	if(pSegment && pSegment->linearPath())
	{	pLinear = (CLinearPath*)pSegment;
	}
	else
	{	// Create and add to list
		pLinear = new CLinearPath;
		// Add to list
		addToList(pLinear);
	}
	return pLinear;
}

// Add Linear Point (only if current segment is linear)
void CPathManager::addLinearPoint( CFXDataPoint dp1 )
{	CPathSegment *pSegment = getCurrentSegment();
	// Create new path
	if(!pSegment || !pSegment->linearPath())
	{	// Create
		CLinearPath *pLinear = new CLinearPath;
		pLinear->addPoint( dp1 );
		addToList(pLinear);
		return;
	}
	// Add to current 
	((CLinearPath*)pSegment)->addPoint( dp1 );
}

// Add Bezier Path
CBezierPath *CPathManager::addBezierPath( CFXDataPoint a, CFXDataPoint b, CFXDataPoint c, CFXDataPoint d )
{	CBezierPath *pBezier = addBezierPath();
	if(pBezier)
	{	// Add points
		pBezier->addPoints( a,b );
		pBezier->addPoints( d,c );
	}
	return pBezier;
}

// Add Bezier Path
CBezierPath *CPathManager::addBezierPath( void )
{	CBezierPath *pBezier = NULL;
	CPathSegment *pSegment = getCurrentSegment();
	// Use existing bezier path
	if(pSegment && pSegment->bezierPath())
	{	pBezier = (CBezierPath*)pSegment;
	}
	else
	{	// Create and add to list
		pBezier = new CBezierPath;
		// Add to list
		addToList(pBezier);
	}
	return pBezier;
}

// Add Bezier Points (only if current segment is bezier)
void CPathManager::addBezierPoints( CFXDataPoint a, CFXDataPoint b )
{	CPathSegment *pSegment = getCurrentSegment();
	// We're not currently on bezier, and a bezier curve REQUIRES 4 points, so we cant create here
	if(!pSegment || !pSegment->bezierPath())
	{	return;
	}
	// Add to current 
	CBezierPath *pBezier = ((CBezierPath*)pSegment);
	pBezier->addPoints(a,b);
	// Recalculate length
	pBezier->calculateLength();
}

// Move points by specified vector
void CPathManager::movePoints( vec3_t move )
{	std::list<CPathSegment*>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_PathSegments)
	{	(*iterCurrent)->movePoints( move );
	}
}

// Rotate points by a specified angle
void CPathManager::rotatePoints( vec3_t angle )
{	std::list<CPathSegment*>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_PathSegments)
	{	(*iterCurrent)->rotatePoints( angle );
	}
}

// Get point at distance on path
vec3_t CPathManager::getPointAtDistance( float flDist )
{	// Nothing exist here, return events pos
	if(m_PathSegments.empty())
	{	return getFXEvent()->makePosFinal(vec3_t(0,0,0));
	}

	// Cap at max length of path
	if(flDist > getLength())
	{	flDist = getLength();
	}

	// 0 distance, use first path
	if(flDist <= 0)
	{	if(m_PathSegments.begin() != m_PathSegments.end())
		{	(*m_PathSegments.begin())->getPoint(0);
		}
	}

	// Find path segment our point resides on
	float flCurDist=0, flStartDist=0;
	CPathSegment *pPath=NULL;
	std::list<CPathSegment*>::iterator iterCurrent,iterPrev=NULL;
	ForEachIn(iterCurrent, m_PathSegments)
	{	flStartDist = flCurDist;

		// Account for space between segments
		if(iterPrev != NULL)
		{	float flBetween = ((*iterPrev)->getEndPoint()-(*iterCurrent)->getStartPoint()).Length();
			flCurDist += flBetween;
			// Overstepped between segments, return distance between
			if(flCurDist>flDist)
			{	flDist -= flStartDist;
				float flPer = flDist/flBetween;
				return ((*iterPrev)->getEndPoint() + ((*iterCurrent)->getStartPoint()-(*iterPrev)->getEndPoint())*flPer);
			}
		}
		flStartDist = flCurDist;
		iterPrev = iterCurrent;

		// Add up distance until we overstep our distance we're looking for
		flCurDist += (*iterCurrent)->getLength();

		// Found path our point resides on, break out and save relitive path distance
		if(flCurDist >= flDist)
		{	flDist -= flStartDist;
			pPath = (*iterCurrent);
			break;
		}
	}

	// Valid path saved
	if(pPath)
	{	return pPath->getPoint(flDist);
	}

	return getFXEvent()->makePosFinal(vec3_t(0,0,0));
}

// Get closest point to ray
extern vec3_t g_CamOrigin;
CFXDataPoint *CPathManager::getClosestPointToRay( vec3_t vecRay )
{	float flDot=-1;
	CFXDataPoint* pPoint=NULL;

	std::list<CPathSegment*>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_PathSegments)
	{	// Bezier path
		if((*iterCurrent)->bezierPath())
		{	CBezierPath *pBezierPath = (CBezierPath*)(*iterCurrent);
			// Iterate through bezier points
			std::list<CBezierPoints>::iterator iterBezierPoints;
			ForEachIn(iterBezierPoints,pBezierPath->m_BezierPoints)
			{	CFXDataPoint *p1 = 	iterBezierPoints->getCurveTo();
				CFXDataPoint *p2 = iterBezierPoints->getOrigin();
				CFXDataPoint *pClosest=NULL;
				float flClosest=0;	
				// Dot product check both
				float flDot1 = DotProduct(vecRay,(p1->getPosition()-g_CamOrigin).Normalize());
				float flDot2 = DotProduct(vecRay,(p2->getPosition()-g_CamOrigin).Normalize());
				if(flDot1>flDot2)
				{	pClosest=p1;
					flClosest=flDot1;
				}
				else
				{	pClosest=p2;
					flClosest=flDot2;
				}
				// set new closest dot
				if(flClosest>flDot)
				{	flDot = flClosest;
					pPoint = pClosest;
				}
			}
		}
		// Linear Path
		else if((*iterCurrent)->linearPath())
		{	CLinearPath *pLinearPath = (CLinearPath*)(*iterCurrent);
			std::list<CFXDataPoint>::iterator iterLinearPoints;
			ForEachIn(iterLinearPoints,pLinearPath->m_Points)
			{	float flLinearDot = DotProduct(vecRay,(iterLinearPoints->getPosition()-g_CamOrigin).Normalize());
				// Set new closest
				if(flLinearDot>flDot)
				{	flDot=flLinearDot;
					pPoint = (&(*iterLinearPoints));
				}
			}
		}
	}
	return pPoint;
}

// Calculate Length ( bezier )
void CPathManager::calculateLength( void )
{	std::list<CPathSegment*>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_PathSegments)
	{	if((*iterCurrent)->bezierPath())
		{	((CBezierPath*)(*iterCurrent))->calculateLength();
		}
	}
}

#ifdef CLIENT_DLL
#include "../hud.h"
#include "../cl_util.h"
#include "../studio_util.h"
// added by Pcjoe
#include "../vgui_TeamFortressViewport.h"
#include "../vgui_fxtoolpanel.h"
#include "../tf_defs.h"
#endif
// Render Path
void CPathManager::RenderPath( void )
{	bool bSelected = false;
	if(gViewPort->m_pFXToolPanel->getPathManager()->getPathList()->getSelectedLabel() == getID())
		bSelected = true;
	// Iterate through path segments
	std::list<CPathSegment*>::iterator iterPathCur;
	std::list<CPathSegment*>::iterator iterPathPrev=NULL;
	ForEachIn(iterPathCur, m_PathSegments)
	{	// render
		(*iterPathCur)->RenderSegment(bSelected);

		// Account for space between segments
		if(iterPathPrev != NULL)
		{	// black for between
			gEngfuncs.pTriAPI->RenderMode( kRenderNormal );
			gEngfuncs.pTriAPI->Color4ub(0,0,0,255);
			gEngfuncs.pTriAPI->CullFace( TRI_NONE );
			gEngfuncs.pTriAPI->Begin( TRI_LINES );

			gEngfuncs.pTriAPI->Vertex3fv( (*iterPathCur)->getStartPoint() );
			gEngfuncs.pTriAPI->Vertex3fv( (*iterPathPrev)->getEndPoint() );

			gEngfuncs.pTriAPI->End();
		}
		iterPathPrev = iterPathCur;
	}
}

// Write XML
TiXmlElement *CPathManager::writeXML( void )
{	// Create path element
	TiXmlElement *pPath = new TiXmlElement("path");
	pPath->SetAttribute("name",getName().c_str());
	pPath->SetAttribute("id",getID());

	std::list<CPathSegment*>::iterator iterPathCur;
	ForEachIn(iterPathCur, m_PathSegments)
	{	pPath->LinkEndChild((*iterPathCur)->writeXML());
	}
	return pPath;
}

// read XML
void CPathManager::readXML( TiXmlElement *pElement )
{	if(!pElement)
	{	return;
	}

	if(pElement->Attribute("name"))
		setName(pElement->Attribute("name"));
	if(pElement->Attribute("id"))
		setID(atoi(pElement->Attribute("id")));

	// Iterate through children
	TiXmlElement *pPaths=NULL;
	IterateXML(pPaths,pElement)
	{	if(pPaths->Attribute("type"))
		{	if(string(pPaths->Attribute("type")) == "linear")
			{	CLinearPath *pLinear = addLinearPath();
				if(pLinear)
					pLinear->readXML(pPaths);
			}
			else if(string(pPaths->Attribute("type")) == "bezier")
			{	CBezierPath *pBezier = addBezierPath();
				if(pBezier)
					pBezier->readXML(pPaths);
			}
		}
	}
}