// NextBotPath.h
// Encapsulate and manipulate a path through the world
// Author: Michael Booth, February 2006
//========= Copyright Valve Corporation, All rights reserved. ============//

#ifndef _NEXT_BOT_PATH_H_
#define _NEXT_BOT_PATH_H_

#include "NextBotInterface.h"

#include "tier0/vprof.h"

#define PATH_NO_LENGTH_LIMIT 0.0f				// non-default argument value for Path::Compute()
#define PATH_TRUNCATE_INCOMPLETE_PATH false		// non-default argument value for Path::Compute()

class INextBot;
class CNavArea;
class CNavLadder;


//---------------------------------------------------------------------------------------------------------------
/**
 * The interface for pathfinding costs.
 * TODO: Replace all template cost functors with this interface, so we can virtualize and derive from them.
 */
class IPathCost
{
public:
	virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const = 0;
};


//---------------------------------------------------------------------------------------------------------------
/**
 * The interface for selecting a goal area during "open goal" pathfinding
 */
class IPathOpenGoalSelector
{
public:
	// compare "newArea" to "currentGoal" and return the area that is the better goal area
	virtual CNavArea *operator() ( CNavArea *currentGoal, CNavArea *newArea ) const = 0;
};


//---------------------------------------------------------------------------------------------------------------
/**
 * A Path through the world.
 * Not only does this encapsulate a path to get from point A to point B,
 * but also the selecting the decision algorithm for how to build that path.
 */
class Path
{
public:
	Path( void );
	virtual ~Path() { }
	
	enum SegmentType
	{
		ON_GROUND,
		DROP_DOWN,
		CLIMB_UP,
		JUMP_OVER_GAP,
		LADDER_UP,
		LADDER_DOWN,
		
		NUM_SEGMENT_TYPES
	};

	// @todo Allow custom Segment classes for different kinds of paths	
	struct Segment
	{
		CNavArea *area;									// the area along the path
		NavTraverseType how;							// how to enter this area from the previous one
		Vector pos;										// our movement goal position at this point in the path
		const CNavLadder *ladder;						// if "how" refers to a ladder, this is it
		
		SegmentType type;								// how to traverse this segment of the path
		Vector forward;									// unit vector along segment
		float length;									// length of this segment
		float distanceFromStart;						// distance of this node from the start of the path
		float curvature;								// how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)

		Vector m_portalCenter;							// position of center of 'portal' between previous area and this area
		float m_portalHalfWidth;						// half width of 'portal'
	};

	virtual float GetLength( void ) const;						// return length of path from start to finish
	virtual const Vector &GetPosition( float distanceFromStart, const Segment *start = NULL ) const;	// return a position on the path at the given distance from the path start
	virtual const Vector &GetClosestPosition( const Vector &pos, const Segment *start = NULL, float alongLimit = 0.0f ) const;		// return the closest point on the path to the given position

	virtual const Vector &GetStartPosition( void ) const;	// return the position where this path starts
	virtual const Vector &GetEndPosition( void ) const;		// return the position where this path ends
	virtual CBaseCombatCharacter *GetSubject( void ) const;	// return the actor this path leads to, or NULL if there is no subject

	virtual const Path::Segment *GetCurrentGoal( void ) const;	// return current goal along the path we are trying to reach

	virtual float GetAge( void ) const;					// return "age" of this path (time since it was built)

	enum SeekType
	{
		SEEK_ENTIRE_PATH,			// search the entire path length
		SEEK_AHEAD,					// search from current cursor position forward toward end of path
		SEEK_BEHIND					// search from current cursor position backward toward path start
	};
	virtual void MoveCursorToClosestPosition( const Vector &pos, SeekType type = SEEK_ENTIRE_PATH, float alongLimit = 0.0f ) const;		// Set cursor position to closest point on path to given position
	
	enum MoveCursorType
	{
		PATH_ABSOLUTE_DISTANCE,
		PATH_RELATIVE_DISTANCE
	};
	virtual void MoveCursorToStart( void );				// set seek cursor to start of path
	virtual void MoveCursorToEnd( void );				// set seek cursor to end of path
	virtual void MoveCursor( float value, MoveCursorType type = PATH_ABSOLUTE_DISTANCE );	// change seek cursor position
	virtual float GetCursorPosition( void ) const;		// return position of seek cursor (distance along path)

	struct Data
	{
		Vector pos;										// the position along the path
		Vector forward;									// unit vector along path direction
		float curvature;								// how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
		const Segment *segmentPrior;					// the segment just before this position
	};
	virtual const Data &GetCursorData( void ) const;	// return path state at the current cursor position

	virtual bool IsValid( void ) const;
	virtual void Invalidate( void );					// make path invalid (clear it)

	virtual void Draw( const Path::Segment *start = NULL ) const;	// draw the path for debugging
	virtual void DrawInterpolated( float from, float to );	// draw the path for debugging - MODIFIES cursor position

	virtual const Segment *FirstSegment( void ) const;	// return first segment of path
	virtual const Segment *NextSegment( const Segment *currentSegment ) const;	// return next segment of path, given current one
	virtual const Segment *PriorSegment( const Segment *currentSegment ) const;	// return previous segment of path, given current one
	virtual const Segment *LastSegment( void ) const;	// return last segment of path

	enum ResultType
	{
		COMPLETE_PATH,
		PARTIAL_PATH,
		NO_PATH
	};
	virtual void OnPathChanged( INextBot *bot, ResultType result ) { }		// invoked when the path is (re)computed (path is valid at the time of this call)

	virtual void Copy( INextBot *bot, const Path &path );	// Replace this path with the given path's data


	//-----------------------------------------------------------------------------------------------------------------
	/**
	 * Compute shortest path from bot to given actor via A* algorithm.
	 * If returns true, path was found to the subject.
	 * If returns false, path may either be invalid (use IsValid() to check), or valid but 
	 * doesn't reach all the way to the subject.
	 */
	template< typename CostFunctor >
	bool Compute( INextBot *bot, CBaseCombatCharacter *subject, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
	{
		VPROF_BUDGET( "Path::Compute(subject)", "NextBot" );

		Invalidate();

		m_subject = subject;
		
		const Vector &start = bot->GetPosition();
		
		CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
		if ( !startArea )
		{
			OnPathChanged( bot, NO_PATH );
			return false;
		}

		CNavArea *subjectArea = subject->GetLastKnownArea();
		if ( !subjectArea )
		{
			OnPathChanged( bot, NO_PATH );
			return false;
		}

		Vector subjectPos = subject->GetAbsOrigin();
		
		// if we are already in the subject area, build trivial path
		if ( startArea == subjectArea )
		{
			BuildTrivialPath( bot, subjectPos );
			return true;
		}

		//
		// Compute shortest path to subject
		//
		CNavArea *closestArea = NULL;
		bool pathResult = NavAreaBuildPath( startArea, subjectArea, &subjectPos, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );

		// Failed?
		if ( closestArea == NULL )
			return false;

		//
		// Build actual path by following parent links back from goal area
		//

		// get count
		int count = 0;
		CNavArea *area;
		for( area = closestArea; area; area = area->GetParent() )
		{
			++count;

			if ( area == startArea )
			{
				// startArea can be re-evaluated during the pathfind and given a parent...
				break;
			}
			if ( count >= MAX_PATH_SEGMENTS-1 ) // save room for endpoint
				break;
		}
		
		if ( count == 1 )
		{
			BuildTrivialPath( bot, subjectPos );
			return pathResult;
		}

		// assemble path
		m_segmentCount = count;
		for( area = closestArea; count && area; area = area->GetParent() )
		{
			--count;
			m_path[ count ].area = area;
			m_path[ count ].how = area->GetParentHow();
			m_path[ count ].type = ON_GROUND;
		}

		if ( pathResult || includeGoalIfPathFails )
		{
			// append actual subject position
			m_path[ m_segmentCount ].area = closestArea;
			m_path[ m_segmentCount ].pos = subjectPos;
			m_path[ m_segmentCount ].ladder = NULL;
			m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
			m_path[ m_segmentCount ].type = ON_GROUND;
			++m_segmentCount;
		}
				
		// compute path positions
		if ( ComputePathDetails( bot, start ) == false )
		{
			Invalidate();
			OnPathChanged( bot, NO_PATH );
			return false;
		}

		// remove redundant nodes and clean up path
		Optimize( bot );
		
		PostProcess();

		OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );

		return pathResult;
	}


	//-----------------------------------------------------------------------------------------------------------------
	/**
	 * Compute shortest path from bot to 'goal' via A* algorithm.
	 * If returns true, path was found to the goal position.
	 * If returns false, path may either be invalid (use IsValid() to check), or valid but 
	 * doesn't reach all the way to the goal.
	 */
	template< typename CostFunctor >
	bool Compute( INextBot *bot, const Vector &goal, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
	{
		VPROF_BUDGET( "Path::Compute(goal)", "NextBotSpiky" );

		Invalidate();
		
		const Vector &start = bot->GetPosition();
		
		CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
		if ( !startArea )
		{
			OnPathChanged( bot, NO_PATH );
			return false;
		}

		// check line-of-sight to the goal position when finding it's nav area
		const float maxDistanceToArea = 200.0f;
		CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal, true, maxDistanceToArea, true );

		// if we are already in the goal area, build trivial path
		if ( startArea == goalArea )
		{
			BuildTrivialPath( bot, goal );
			return true;
		}

		// make sure path end position is on the ground
		Vector pathEndPosition = goal;
		if ( goalArea )
		{
			pathEndPosition.z = goalArea->GetZ( pathEndPosition );
		}
		else
		{
			TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
		}

		//
		// Compute shortest path to goal
		//
		CNavArea *closestArea = NULL;
		bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );

		// Failed?
		if ( closestArea == NULL )
			return false;

		//
		// Build actual path by following parent links back from goal area
		//

		// get count
		int count = 0;
		CNavArea *area;
		for( area = closestArea; area; area = area->GetParent() )
		{
			++count;

			if ( area == startArea )
			{
				// startArea can be re-evaluated during the pathfind and given a parent...
				break;
			}
			if ( count >= MAX_PATH_SEGMENTS-1 ) // save room for endpoint
				break;
		}
		
		if ( count == 1 )
		{
			BuildTrivialPath( bot, goal );
			return pathResult;
		}

		// assemble path
		m_segmentCount = count;
		for( area = closestArea; count && area; area = area->GetParent() )
		{
			--count;
			m_path[ count ].area = area;
			m_path[ count ].how = area->GetParentHow();
			m_path[ count ].type = ON_GROUND;
		}

		if ( pathResult || includeGoalIfPathFails )
		{
			// append actual goal position
			m_path[ m_segmentCount ].area = closestArea;
			m_path[ m_segmentCount ].pos = pathEndPosition;
			m_path[ m_segmentCount ].ladder = NULL;
			m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
			m_path[ m_segmentCount ].type = ON_GROUND;
			++m_segmentCount;
		}
				
		// compute path positions
		if ( ComputePathDetails( bot, start ) == false )
		{
			Invalidate();
			OnPathChanged( bot, NO_PATH );
			return false;
		}

		// remove redundant nodes and clean up path
		Optimize( bot );
		
		PostProcess();

		OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );

		return pathResult;
	}


	//-----------------------------------------------------------------------------------------------------------------
	/**
	 * Build a path from bot's current location to an undetermined goal area
	 * that minimizes the given cost along the final path and meets the
	 * goal criteria.
	 */
	virtual bool ComputeWithOpenGoal( INextBot *bot, const IPathCost &costFunc, const IPathOpenGoalSelector &goalSelector, float maxSearchRadius = 0.0f )
	{
		VPROF_BUDGET( "ComputeWithOpenGoal", "NextBot" );

		int teamID = bot->GetEntity()->GetTeamNumber();

		CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();

		if ( startArea == NULL )
			return NULL;

		startArea->SetParent( NULL );

		// start search
		CNavArea::ClearSearchLists();

		float initCost = costFunc( startArea, NULL, NULL, NULL, -1.0f );
		if ( initCost < 0.0f )
			return NULL;

		startArea->SetTotalCost( initCost );
		startArea->AddToOpenList();

		// find our goal as we search
		CNavArea *goalArea = NULL;

		//
		// Dijkstra's algorithm (since we don't know our goal).
		//
		while( !CNavArea::IsOpenListEmpty() )
		{
			// get next area to check
			CNavArea *area = CNavArea::PopOpenList();

			area->AddToClosedList();

			// don't consider blocked areas
			if ( area->IsBlocked( teamID ) )
				continue;

			// build adjacent area array
			CollectAdjacentAreas( area );

			// search adjacent areas
			for( int i=0; i<m_adjAreaIndex; ++i )
			{
				CNavArea *newArea = m_adjAreaVector[ i ].area;

				// only visit each area once
				if ( newArea->IsClosed() )
					continue;

				// don't consider blocked areas
				if ( newArea->IsBlocked( teamID ) )
					continue;

				// don't use this area if it is out of range
				if ( maxSearchRadius > 0.0f && ( newArea->GetCenter() - bot->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( maxSearchRadius ) )
					continue;

				// determine cost of traversing this area
				float newCost = costFunc( newArea, area, m_adjAreaVector[ i ].ladder, NULL, -1.0f );

				// don't use adjacent area if cost functor says it is a dead-end
				if ( newCost < 0.0f )
					continue;

				if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
				{
					// we have already visited this area, and it has a better path
					continue;
				}
				else
				{
					// whether this area has been visited or not, we now have a better path to it
					newArea->SetParent( area, m_adjAreaVector[ i ].how );
					newArea->SetTotalCost( newCost );

					// use 'cost so far' to hold cumulative cost
					newArea->SetCostSoFar( newCost );

					// tricky bit here - relying on OpenList being sorted by cost
					if ( newArea->IsOpen() )
					{
						// area already on open list, update the list order to keep costs sorted
						newArea->UpdateOnOpenList();
					}
					else
					{
						newArea->AddToOpenList();
					}

					// keep track of best goal so far
					goalArea = goalSelector( goalArea, newArea );
				}
			}
		}

		if ( goalArea )
		{
			// compile the path details into a usable path
			AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
			return true;
		}	

		// all adjacent areas are likely too far away
		return false;
	}


	//-----------------------------------------------------------------------------------------------------------------
	/**
	 * Given the last area in a path with valid parent pointers, 
	 * construct the actual path.
	 */
	void AssemblePrecomputedPath( INextBot *bot, const Vector &goal, CNavArea *endArea )
	{
		VPROF_BUDGET( "AssemblePrecomputedPath", "NextBot" );

		const Vector &start = bot->GetPosition();

		// get count
		int count = 0;
		CNavArea *area;
		for( area = endArea; area; area = area->GetParent() )
		{
			++count;
		}

		// save room for endpoint
		if ( count > MAX_PATH_SEGMENTS-1 )
		{
			count = MAX_PATH_SEGMENTS-1;
		}
		else if ( count == 0 )
		{
			return;
		}

		if ( count == 1 )
		{
			BuildTrivialPath( bot, goal );
			return;
		}

		// assemble path
		m_segmentCount = count;
		for( area = endArea; count && area; area = area->GetParent() )
		{
			--count;
			m_path[ count ].area = area;
			m_path[ count ].how = area->GetParentHow();
			m_path[ count ].type = ON_GROUND;
		}

		// append actual goal position
		m_path[ m_segmentCount ].area = endArea;
		m_path[ m_segmentCount ].pos = goal;
		m_path[ m_segmentCount ].ladder = NULL;
		m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
		m_path[ m_segmentCount ].type = ON_GROUND;
		++m_segmentCount;

		// compute path positions
		if ( ComputePathDetails( bot, start ) == false )
		{
			Invalidate();
			OnPathChanged( bot, NO_PATH );
			return;
		}

		// remove redundant nodes and clean up path
		Optimize( bot );

		PostProcess();

		OnPathChanged( bot, COMPLETE_PATH );
	}

	/**
	 * Utility function for when start and goal are in the same area
	 */
	bool BuildTrivialPath( INextBot *bot, const Vector &goal );	

	/**
	 * Determine exactly where the path goes between the given two areas
	 * on the path. Return this point in 'crossPos'.
	 */
	virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const;


private:
	enum { MAX_PATH_SEGMENTS = 256 };
	Segment m_path[ MAX_PATH_SEGMENTS ];
	int m_segmentCount;

	bool ComputePathDetails( INextBot *bot, const Vector &start );		// determine actual path positions 

	void Optimize( INextBot *bot );
	void PostProcess( void );
	int FindNextOccludedNode( INextBot *bot, int anchor );	// used by Optimize()

	void InsertSegment( Segment newSegment, int i );		// insert new segment at index i
	
	mutable Vector m_pathPos;								// used by GetPosition()
	mutable Vector m_closePos;								// used by GetClosestPosition()

	mutable float m_cursorPos;					// current cursor position (distance along path)
	mutable Data m_cursorData;					// used by GetCursorData()
	mutable bool m_isCursorDataDirty;

	IntervalTimer m_ageTimer;					// how old is this path?
	CHandle< CBaseCombatCharacter > m_subject;	// the subject this path leads to

	/**
	 * Build a vector of adjacent areas reachable from the given area
	 */
	void CollectAdjacentAreas( CNavArea *area )
	{
		m_adjAreaIndex = 0;			

		const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );		
		FOR_EACH_VEC( adjNorth, it )
		{
			if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
				break;

			m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
			m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
			m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
			++m_adjAreaIndex;
		}

		const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );		
		FOR_EACH_VEC( adjSouth, it )
		{
			if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
				break;

			m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
			m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
			m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
			++m_adjAreaIndex;
		}

		const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );		
		FOR_EACH_VEC( adjWest, it )
		{
			if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
				break;

			m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
			m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
			m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
			++m_adjAreaIndex;
		}

		const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );	
		FOR_EACH_VEC( adjEast, it )
		{
			if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
				break;

			m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
			m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
			m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
			++m_adjAreaIndex;
		}

		const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
		FOR_EACH_VEC( adjUpLadder, it )
		{
			CNavLadder *ladder = adjUpLadder[ it ].ladder;

			if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
			{
				m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
				m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
				m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
				++m_adjAreaIndex;
			}

			if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
			{
				m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
				m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
				m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
				++m_adjAreaIndex;
			}

			if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
			{
				m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
				m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
				m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
				++m_adjAreaIndex;
			}
		}

		const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
		FOR_EACH_VEC( adjDownLadder, it )
		{
			CNavLadder *ladder = adjDownLadder[ it ].ladder;

			if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
				break;

			if ( ladder->m_bottomArea )
			{
				m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
				m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
				m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
				++m_adjAreaIndex;
			}
		}
	}

	enum { MAX_ADJ_AREAS = 64 };

	struct AdjInfo
	{
		CNavArea *area;
		CNavLadder *ladder;
		NavTraverseType how;		
	};

	AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
	int m_adjAreaIndex;

};


inline float Path::GetLength( void ) const
{
	if (m_segmentCount <= 0)
	{
		return 0.0f;
	}
	
	return m_path[ m_segmentCount-1 ].distanceFromStart;
}

inline bool Path::IsValid( void ) const
{
	return (m_segmentCount > 0);
}

inline void Path::Invalidate( void )
{
	m_segmentCount = 0;

	m_cursorPos = 0.0f;
	
	m_cursorData.pos = vec3_origin;
	m_cursorData.forward = Vector( 1.0f, 0, 0 );
	m_cursorData.curvature = 0.0f;
	m_cursorData.segmentPrior = NULL;
	
	m_isCursorDataDirty = true;

	m_subject = NULL;
}

inline const Path::Segment *Path::FirstSegment( void ) const
{
	return (IsValid()) ? &m_path[0] : NULL;
}

inline const Path::Segment *Path::NextSegment( const Segment *currentSegment ) const
{
	if (currentSegment == NULL || !IsValid())
		return NULL;
		
	int i = currentSegment - m_path;

	if (i < 0 || i >= m_segmentCount-1)
	{
		return NULL;
	}
	
	return &m_path[ i+1 ];
}

inline const Path::Segment *Path::PriorSegment( const Segment *currentSegment ) const
{
	if (currentSegment == NULL || !IsValid())
		return NULL;
		
	int i = currentSegment - m_path;

	if (i < 1 || i >= m_segmentCount)
	{
		return NULL;
	}
	
	return &m_path[ i-1 ];
}

inline const Path::Segment *Path::LastSegment( void ) const
{
	return ( IsValid() ) ? &m_path[ m_segmentCount-1 ] : NULL;
}

inline const Vector &Path::GetStartPosition( void ) const
{
	return ( IsValid() ) ? m_path[ 0 ].pos : vec3_origin;
}

inline const Vector &Path::GetEndPosition( void ) const
{
	return ( IsValid() ) ? m_path[ m_segmentCount-1 ].pos : vec3_origin;
}

inline CBaseCombatCharacter *Path::GetSubject( void ) const
{
	return m_subject;
}

inline void Path::MoveCursorToStart( void )
{
	m_cursorPos = 0.0f;
	m_isCursorDataDirty = true;
}

inline void Path::MoveCursorToEnd( void )
{
	m_cursorPos = GetLength();
	m_isCursorDataDirty = true;
}

inline void Path::MoveCursor( float value, MoveCursorType type )
{
	if ( type == PATH_ABSOLUTE_DISTANCE )
	{
		m_cursorPos = value;
	}
	else	// relative distance
	{
		m_cursorPos += value;
	}
	
	if ( m_cursorPos < 0.0f )
	{
		m_cursorPos = 0.0f;
	}
	else if ( m_cursorPos > GetLength() )
	{
		m_cursorPos = GetLength();
	}
	
	m_isCursorDataDirty = true;
}

inline float Path::GetCursorPosition( void ) const
{
	return m_cursorPos;
}

inline const Path::Segment *Path::GetCurrentGoal( void ) const
{
	return NULL;
}

inline float Path::GetAge( void ) const
{
	return m_ageTimer.GetElapsedTime();
}


#endif	// _NEXT_BOT_PATH_H_