/*
*
*   This program is free software; you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation; either version 2 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
*   General Public License for more details.
*
*   You should have received a copy of the GNU General Public License
*   along with this program; if not, write to the Free Software Foundation,
*   Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*   In addition, as a special exception, the author gives permission to
*   link the code of this program with the Half-Life Game Engine ("HL
*   Engine") and Modified Game Libraries ("MODs") developed by Valve,
*   L.L.C ("Valve").  You must obey the GNU General Public License in all
*   respects for all of the code used other than the HL Engine and MODs
*   from Valve.  If you modify this file, you may extend this exception
*   to your version of the file, but you are not obligated to do so.  If
*   you do not wish to do so, delete this exception statement from your
*   version.
*
*/

#ifndef NAV_AREA_H
#define NAV_AREA_H
#ifdef _WIN32
#pragma once
#endif

#include <list>

class CNavArea;

void DestroyHidingSpots();
void StripNavigationAreas();
bool SaveNavigationMap(const char *filename);
NavErrorType LoadNavigationMap();
void DestroyNavigationMap();

enum NavEditCmdType
{
	EDIT_NONE,
	EDIT_DELETE,			// delete current area
	EDIT_SPLIT,			// split current area
	EDIT_MERGE,			// merge adjacent areas
	EDIT_JOIN,			// define connection between areas
	EDIT_BREAK,			// break connection between areas
	EDIT_MARK,			// mark an area for further operations
	EDIT_ATTRIB_CROUCH,		// toggle crouch attribute on current area
	EDIT_ATTRIB_JUMP,		// toggle jump attribute on current area
	EDIT_ATTRIB_PRECISE,		// toggle precise attribute on current area
	EDIT_ATTRIB_NO_JUMP,		// toggle inhibiting discontinuity jumping in current area
	EDIT_BEGIN_AREA,		// begin creating a new nav area
	EDIT_END_AREA,			// end creation of the new nav area
	EDIT_CONNECT,			// connect marked area to selected area
	EDIT_DISCONNECT,		// disconnect marked area from selected area
	EDIT_SPLICE,			// create new area in between marked and selected areas
	EDIT_TOGGLE_PLACE_MODE,		// switch between normal and place editing
	EDIT_TOGGLE_PLACE_PAINTING,	// switch between "painting" places onto areas
	EDIT_PLACE_FLOODFILL,		// floodfill areas out from current area
	EDIT_PLACE_PICK,		// "pick up" the place at the current area
	EDIT_MARK_UNNAMED,		// mark an unnamed area for further operations
	EDIT_WARP_TO_MARK,		// warp a spectating local player to the selected mark
	EDIT_SELECT_CORNER,		// select a corner on the current area
	EDIT_RAISE_CORNER,		// raise a corner on the current area
	EDIT_LOWER_CORNER,		// lower a corner on the current area
};

enum RouteType
{
	FASTEST_ROUTE,
	SAFEST_ROUTE,
};

union NavConnect
{
	unsigned int id;
	CNavArea *area;

	bool operator==(const NavConnect &other) const { return (area == other.area) ? true : false; }
};

typedef std::STD_LIST<NavConnect> NavConnectList;

enum LadderDirectionType
{
	LADDER_UP = 0,
	LADDER_DOWN,
	NUM_LADDER_DIRECTIONS
};

class CNavLadder
{
public:
	CNavLadder()
	{
		m_topForwardArea = NULL;
		m_topRightArea = NULL;
		m_topLeftArea = NULL;
		m_topBehindArea = NULL;
		m_bottomArea = NULL;
		m_entity = NULL;
	}

	Vector m_top;
	Vector m_bottom;
	float m_length;
	NavDirType m_dir;
	Vector2D m_dirVector;
	CBaseEntity *m_entity;

	CNavArea *m_topForwardArea;
	CNavArea *m_topLeftArea;
	CNavArea *m_topRightArea;
	CNavArea *m_topBehindArea;
	CNavArea *m_bottomArea;

	bool m_isDangling;

	void OnDestroyNotify(CNavArea *dead)
	{
		if (dead == m_topForwardArea)
			m_topForwardArea = NULL;

		if (dead == m_topLeftArea)
			m_topLeftArea = NULL;

		if (dead == m_topRightArea)
			m_topRightArea = NULL;

		if (dead == m_topBehindArea)
			m_topBehindArea = NULL;

		if (dead == m_bottomArea)
			m_bottomArea = NULL;
	}
};

typedef std::STD_LIST<CNavLadder *> NavLadderList;
extern NavLadderList TheNavLadderList;

class HidingSpot
{
public:
	HidingSpot();
	HidingSpot(const Vector *pos, unsigned char flags);

	enum
	{
		IN_COVER = 0x01,
		GOOD_SNIPER_SPOT = 0x02,
		IDEAL_SNIPER_SPOT = 0x04
	};

	bool HasGoodCover() const { return (m_flags & IN_COVER) ? true : false; }
	bool IsGoodSniperSpot() const { return (m_flags & GOOD_SNIPER_SPOT) ? true : false; }
	bool IsIdealSniperSpot() const { return (m_flags & IDEAL_SNIPER_SPOT) ? true : false; }

	void SetFlags(unsigned char flags) { m_flags |= flags; }
	unsigned char GetFlags() const { return m_flags; }

	void Save(int fd, unsigned int version) const;
	void Load(SteamFile *file, unsigned int version);

	const Vector *GetPosition() const { return &m_pos; }
	unsigned int GetID() const { return m_id; }

	void Mark() { m_marker = IMPL(m_masterMarker); }
	bool IsMarked() const { return (m_marker == IMPL(m_masterMarker)) ? true : false; }

	static void ChangeMasterMarker() { ++IMPL(m_masterMarker); }

#ifndef HOOK_GAMEDLL
private:
#endif
	friend void DestroyHidingSpots();

	Vector m_pos;
	unsigned int m_id;
	unsigned int m_marker;
	unsigned char m_flags;

	static unsigned int IMPL(m_nextID);
	static unsigned int IMPL(m_masterMarker);
};

typedef std::STD_LIST<HidingSpot *> HidingSpotList;
extern HidingSpotList TheHidingSpotList;

struct SpotOrder
{
	float t;
	union
	{
		HidingSpot *spot;
		unsigned int id;
	};
};

typedef std::STD_LIST<SpotOrder> SpotOrderList;

struct SpotEncounter
{
	NavConnect from;
	NavDirType fromDir;
	NavConnect to;
	NavDirType toDir;
	Ray path;				// the path segment
	SpotOrderList spotList;			// list of spots to look at, in order of occurrence
};

typedef std::STD_LIST<SpotEncounter> SpotEncounterList;
typedef std::STD_LIST<CNavArea *> NavAreaList;

// A CNavArea is a rectangular region defining a walkable area in the map
class CNavArea
{
public:
	CNavArea(CNavNode *nwNode, CNavNode *neNode, CNavNode *seNode, CNavNode *swNode);
	CNavArea();
	CNavArea(const Vector *corner, const Vector *otherCorner);
	CNavArea(const Vector *nwCorner, const Vector *neCorner, const Vector *seCorner, const Vector *swCorner);

	~CNavArea();

	void ConnectTo(CNavArea *area, NavDirType dir);				// connect this area to given area in given direction
	void Disconnect(CNavArea *area);					// disconnect this area from given area

	void Save(FILE *fp) const;
	void Save(int fd, unsigned int version);

	void Load(SteamFile *file, unsigned int version);
	NavErrorType PostLoad();

	unsigned int GetID() const { return m_id; }
	void SetAttributes(unsigned char bits) { m_attributeFlags = bits; }
	unsigned char GetAttributes() const { return m_attributeFlags; }
	void SetPlace(Place place) { m_place = place; }						// set place descriptor
	Place GetPlace() const { return m_place; }						// get place descriptor

	bool IsOverlapping(const Vector *pos) const;						// return true if 'pos' is within 2D extents of area
	bool IsOverlapping(const CNavArea *area) const;						// return true if 'area' overlaps our 2D extents
	bool IsOverlappingX(const CNavArea *area) const;					// return true if 'area' overlaps our X extent
	bool IsOverlappingY(const CNavArea *area) const;					// return true if 'area' overlaps our Y extent
	int GetPlayerCount(int teamID = 0, CBasePlayer *ignore = NULL) const;			// return number of players with given teamID in this area (teamID == 0 means any/all)
	float GetZ(const Vector *pos) const;							// return Z of area at (x,y) of 'pos'
	float GetZ(float x, float y) const;							// return Z of area at (x,y) of 'pos'
	bool Contains(const Vector *pos) const;							// return true if given point is on or above this area, but no others
	bool IsCoplanar(const CNavArea *area) const;						// return true if this area and given area are approximately co-planar
	void GetClosestPointOnArea(const Vector *pos, Vector *close) const;			// return closest point to 'pos' on this area - returned point in 'close'
	float GetDistanceSquaredToPoint(const Vector *pos) const;				// return shortest distance between point and this area
	bool IsDegenerate() const;								// return true if this area is badly formed
	bool IsEdge(NavDirType dir) const;							// return true if there are no bi-directional links on the given side
	int GetAdjacentCount(NavDirType dir) const { return m_connect[dir].size(); }		// return number of connected areas in given direction
	CNavArea *GetAdjacentArea(NavDirType dir, int i) const;					// return the i'th adjacent area in the given direction
	CNavArea *GetRandomAdjacentArea(NavDirType dir) const;
	const NavConnectList *GetAdjacentList(NavDirType dir) const { return &m_connect[dir]; }
	bool IsConnected(const CNavArea *area, NavDirType dir) const;				// return true if given area is connected in given direction
	float ComputeHeightChange(const CNavArea *area);					// compute change in height from this area to given area

	const NavLadderList *GetLadderList(LadderDirectionType dir) const { return &m_ladder[dir]; }

	void ComputePortal(const CNavArea *to, NavDirType dir, Vector *center, float *halfWidth) const;				// compute portal to adjacent area
	void ComputeClosestPointInPortal(const CNavArea *to, NavDirType dir, const Vector *fromPos, Vector *closePos) const;	// compute closest point within the "portal" between to adjacent areas
	NavDirType ComputeDirection(Vector *point) const;									// return direction from this area to the given point

	// for hunting algorithm
	void SetClearedTimestamp(int teamID) { m_clearedTimestamp[teamID] = gpGlobals->time; }	// set this area's "clear" timestamp to now
	float GetClearedTimestamp(int teamID) { return m_clearedTimestamp[teamID]; }		// get time this area was marked "clear"

	// hiding spots
	const HidingSpotList *GetHidingSpotList() const { return &m_hidingSpotList; }

	void ComputeHidingSpots();							// analyze local area neighborhood to find "hiding spots" in this area - for map learning
	void ComputeSniperSpots();							// analyze local area neighborhood to find "sniper spots" in this area - for map learning

	SpotEncounter *GetSpotEncounter(const CNavArea *from, const CNavArea *to);	// given the areas we are moving between, return the spots we will encounter
	void ComputeSpotEncounters();							// compute spot encounter data - for map learning

	// danger
	void IncreaseDanger(int teamID, float amount);					// increase the danger of this area for the given team
	float GetDanger(int teamID);							// return the danger of this area (decays over time)

	float GetSizeX() const { return m_extent.hi.x - m_extent.lo.x; }
	float GetSizeY() const { return m_extent.hi.y - m_extent.lo.y; }

	const Extent *GetExtent() const { return &m_extent; }
	const Vector *GetCenter() const { return &m_center; }
	const Vector *GetCorner(NavCornerType corner) const;

	// approach areas
	struct ApproachInfo
	{
		NavConnect here;			// the approach area
		NavConnect prev;			// the area just before the approach area on the path
		NavTraverseType prevToHereHow;
		NavConnect next;			// the area just after the approach area on the path
		NavTraverseType hereToNextHow;
	};

	const ApproachInfo *GetApproachInfo(int i) const { return &m_approach[i]; }
	int GetApproachInfoCount() const { return m_approachCount; }
	void ComputeApproachAreas();						// determine the set of "approach areas" - for map learning

	// A* pathfinding algorithm
	static void MakeNewMarker()
	{
		if (++IMPL(m_masterMarker) == 0)
			IMPL(m_masterMarker) = 1;
	}
	void Mark() { m_marker = IMPL(m_masterMarker); }
	BOOL IsMarked() const { return (m_marker == IMPL(m_masterMarker)) ? true : false; }
	void SetParent(CNavArea *parent, NavTraverseType how = NUM_TRAVERSE_TYPES) { m_parent = parent; m_parentHow = how; }
	CNavArea *GetParent() const { return m_parent; }
	NavTraverseType GetParentHow() const { return m_parentHow; }

	bool IsOpen() const;		// true if on "open list"
	void AddToOpenList();		// add to open list in decreasing value order
	void UpdateOnOpenList();	// a smaller value has been found, update this area on the open list
	void RemoveFromOpenList();
	static bool IsOpenListEmpty();
	static CNavArea *PopOpenList();	// remove and return the first element of the open list

	bool IsClosed() const;		// true if on "closed list"
	void AddToClosedList();		// add to the closed list
	void RemoveFromClosedList();

	static void ClearSearchLists();	// clears the open and closed lists for a new search

	void SetTotalCost(float value) { m_totalCost = value; }
	float GetTotalCost() const { return m_totalCost; }

	void SetCostSoFar(float value) { m_costSoFar = value; }
	float GetCostSoFar() const { return m_costSoFar; }

	// editing
	void Draw(byte red, byte green, byte blue, int duration = 50);							// draw area for debugging & editing
	void DrawConnectedAreas();
	void DrawMarkedCorner(NavCornerType corner, byte red, byte green, byte blue, int duration = 50);
	bool SplitEdit(bool splitAlongX, float splitEdge, CNavArea **outAlpha = NULL, CNavArea **outBeta = NULL);	// split this area into two areas at the given edge
	bool MergeEdit(CNavArea *adj);											// merge this area and given adjacent area
	bool SpliceEdit(CNavArea *other);										// create a new area between this area and given area
	void RaiseCorner(NavCornerType corner, int amount);								// raise/lower a corner (or all corners if corner == NUM_CORNERS)

	// ladders
	void AddLadderUp(CNavLadder *ladder) { m_ladder[LADDER_UP].push_back(ladder); }
	void AddLadderDown(CNavLadder *ladder) { m_ladder[LADDER_DOWN].push_back(ladder); }

#ifndef HOOK_GAMEDLL
private:
#endif
	friend void ConnectGeneratedAreas();
	friend void MergeGeneratedAreas();
	friend void MarkJumpAreas();
	friend bool SaveNavigationMap(const char *filename);
	friend NavErrorType LoadNavigationMap();
	friend void DestroyNavigationMap();
	friend void DestroyHidingSpots();
	friend void StripNavigationAreas();
	friend class CNavAreaGrid;
	friend class CCSBotManager;

	void Initialize();				// to keep constructors consistent
	static bool IMPL(m_isReset);			// if true, don't bother cleaning up in destructor since everything is going away

	static unsigned int IMPL(m_nextID);		// used to allocate unique IDs
	unsigned int m_id;				// unique area ID
	Extent m_extent;				// extents of area in world coords (NOTE: lo.z is not necessarily the minimum Z, but corresponds to Z at point (lo.x, lo.y), etc
	Vector m_center;				// centroid of area
	unsigned char m_attributeFlags;			// set of attribute bit flags (see NavAttributeType)
	Place m_place;					// place descriptor

	// height of the implicit corners
	float m_neZ;
	float m_swZ;

	enum { MAX_AREA_TEAMS = 2 };

	// for hunting
	float m_clearedTimestamp[MAX_AREA_TEAMS];	// time this area was last "cleared" of enemies

	// danger
	float m_danger[MAX_AREA_TEAMS];			// danger of this area, allowing bots to avoid areas where they died in the past - zero is no danger
	float m_dangerTimestamp[MAX_AREA_TEAMS];	// time when danger value was set - used for decaying
	void DecayDanger();

	// hiding spots
	HidingSpotList m_hidingSpotList;
	bool IsHidingSpotCollision(const Vector *pos) const;	// returns true if an existing hiding spot is too close to given position

	// encounter spots
	SpotEncounterList m_spotEncounterList;			// list of possible ways to move thru this area, and the spots to look at as we do
	void AddSpotEncounters(const CNavArea *from, NavDirType fromDir, const CNavArea *to, NavDirType toDir);

	// approach areas
	enum { MAX_APPROACH_AREAS = 16 };
	ApproachInfo m_approach[MAX_APPROACH_AREAS];
	unsigned char m_approachCount;

	void Strip();						// remove "analyzed" data from nav area

	// A* pathfinding algorithm
	static unsigned int IMPL(m_masterMarker);
	unsigned int m_marker;					// used to flag the area as visited
	CNavArea *m_parent;					// the area just prior to this on in the search path
	NavTraverseType m_parentHow;				// how we get from parent to us
	float m_totalCost;					// the distance so far plus an estimate of the distance left
	float m_costSoFar;					// distance travelled so far

	static CNavArea *IMPL(m_openList);
	CNavArea *m_nextOpen, *m_prevOpen;			// only valid if m_openMarker == m_masterMarker
	unsigned int m_openMarker;				// if this equals the current marker value, we are on the open list

	// connections to adjacent areas
	NavConnectList m_connect[ NUM_DIRECTIONS ];		// a list of adjacent areas for each direction
	NavLadderList m_ladder[ NUM_LADDER_DIRECTIONS ];	// list of ladders leading up and down from this area

	CNavNode *m_node[ NUM_CORNERS ];			// nav nodes at each corner of the area

	void FinishMerge(CNavArea *adjArea);					// recompute internal data once nodes have been adjusted during merge
	void MergeAdjacentConnections(CNavArea *adjArea);			// for merging with "adjArea" - pick up all of "adjArea"s connections
	void AssignNodes(CNavArea *area);					// assign internal nodes to the given area
	void FinishSplitEdit(CNavArea *newArea, NavDirType ignoreEdge);		// given the portion of the original area, update its internal data

	NavAreaList m_overlapList;						// list of areas that overlap this area
	void OnDestroyNotify(CNavArea *dead);					// invoked when given area is going away

	CNavArea *m_prevHash, *m_nextHash;					// for hash table in CNavAreaGrid
};

extern NavAreaList TheNavAreaList;

inline bool CNavArea::IsDegenerate() const
{
	return (m_extent.lo.x >= m_extent.hi.x || m_extent.lo.y >= m_extent.hi.y);
}

inline CNavArea *CNavArea::GetAdjacentArea(NavDirType dir, int i) const
{
	NavConnectList::const_iterator iter;
	for (iter = m_connect[dir].begin(); iter != m_connect[dir].end(); ++iter)
	{
		if (i == 0)
			return (*iter).area;
		--i;
	}

	return NULL;
}

inline bool CNavArea::IsOpen() const
{
	return (m_openMarker == IMPL(m_masterMarker)) ? true : false;
}

inline bool CNavArea::IsOpenListEmpty()
{
	return (IMPL(m_openList) != NULL) ? false : true;
}

inline CNavArea *CNavArea::PopOpenList()
{
	if (IMPL(m_openList))
	{
		CNavArea *area = IMPL(m_openList);

		// disconnect from list
		area->RemoveFromOpenList();
		return area;
	}

	return NULL;
}

inline bool CNavArea::IsClosed() const
{
	if (IsMarked() && !IsOpen())
		return true;

	return false;
}

inline void CNavArea::AddToClosedList()
{
	Mark();
}

inline void CNavArea::RemoveFromClosedList()
{
	// since "closed" is defined as visited (marked) and not on open list, do nothing
}

// The CNavAreaGrid is used to efficiently access navigation areas by world position
// Each cell of the grid contains a list of areas that overlap it
// Given a world position, the corresponding grid cell is ( x/cellsize, y/cellsize )
class CNavAreaGrid
{
public:
	CNavAreaGrid();
	~CNavAreaGrid();

	void Reset();										// clear the grid to empty
	void Initialize(float minX, float maxX, float minY, float maxY);			// clear and reset the grid to the given extents
	void AddNavArea(CNavArea *area);							// add an area to the grid
	void RemoveNavArea(CNavArea *area);							// remove an area from the grid
	unsigned int GetNavAreaCount() const { return m_areaCount; }				// return total number of nav areas
	CNavArea *GetNavArea(const Vector *pos, float beneathLimt = 120.0f) const;		// given a position, return the nav area that IsOverlapping and is *immediately* beneath it
	CNavArea *GetNavAreaByID(unsigned int id) const;
	CNavArea *GetNearestNavArea(const Vector *pos, bool anyZ = false) const;

	Place GetPlace(const Vector *pos) const;						// return radio chatter place for given coordinate

private:
	const float m_cellSize;
	NavAreaList *m_grid;
	int m_gridSizeX;
	int m_gridSizeY;
	float m_minX;
	float m_minY;
	unsigned int m_areaCount;				// total number of nav areas

	enum { HASH_TABLE_SIZE = 256 };
	CNavArea *m_hashTable[HASH_TABLE_SIZE];			// hash table to optimize lookup by ID
	inline int ComputeHashKey(unsigned int id) const	// returns a hash key for the given nav area ID
	{
		return id & 0xFF;
	}

	inline int WorldToGridX(float wx) const
	{
		int x = (wx - m_minX) / m_cellSize;
		if (x < 0)
			x = 0;

		else if (x >= m_gridSizeX)
			x = m_gridSizeX - 1;

		return x;
	}
	inline int WorldToGridY(float wy) const
	{
		int y = (wy - m_minY) / m_cellSize;
		if (y < 0)
			y = 0;
		else if (y >= m_gridSizeY)
			y = m_gridSizeY - 1;

		return y;
	}
};

extern CNavAreaGrid TheNavAreaGrid;

class ShortestPathCost
{
public:
	float operator()(CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
	{
		if (fromArea == NULL)
		{
			// first area in path, no cost
			return 0.0f;
		}
		else
		{
			// compute distance travelled along path so far
			float dist;

			if (ladder)
				dist = ladder->m_length;
			else
				dist = (*area->GetCenter() - *fromArea->GetCenter()).Length();

			float cost = dist + fromArea->GetCostSoFar();

			// if this is a "crouch" area, add penalty
			if (area->GetAttributes() & NAV_CROUCH)
			{
				const float crouchPenalty = 20.0f;
				cost += crouchPenalty * dist;
			}

			// if this is a "jump" area, add penalty
			if (area->GetAttributes() & NAV_JUMP)
			{
				const float jumpPenalty = 5.0f;
				cost += jumpPenalty * dist;
			}

			return cost;
		}
	}
};

// Find path from startArea to goalArea via an A* search, using supplied cost heuristic.
// If cost functor returns -1 for an area, that area is considered a dead end.
// This doesn't actually build a path, but the path is defined by following parent
// pointers back from goalArea to startArea.
// If 'closestArea' is non-NULL, the closest area to the goal is returned (useful if the path fails).
// If 'goalArea' is NULL, will compute a path as close as possible to 'goalPos'.
// If 'goalPos' is NULL, will use the center of 'goalArea' as the goal position.
// Returns true if a path exists.
template <typename CostFunctor>
bool NavAreaBuildPath(CNavArea *startArea, CNavArea *goalArea, const Vector *goalPos, CostFunctor &costFunc, CNavArea **closestArea = NULL)
{
	if (closestArea != NULL)
		*closestArea = NULL;

	if (startArea == NULL)
		return false;

	// If goalArea is NULL, this function will return the closest area to the goal.
	// However, if there is also no goal, we can't do anything.
	if (goalArea == NULL && goalPos == NULL)
	{
		return false;
	}

	startArea->SetParent(NULL);

	// if we are already in the goal area, build trivial path
	if (startArea == goalArea)
	{
		goalArea->SetParent(NULL);

		if (closestArea != NULL)
			*closestArea = goalArea;

		return true;
	}

	// determine actual goal position
	Vector actualGoalPos = (goalPos != NULL) ? (*goalPos) : (*goalArea->GetCenter());

	// start search
	CNavArea::ClearSearchLists();

	// compute estimate of path length
	// TODO: Cost might work as "manhattan distance"
	startArea->SetTotalCost((*startArea->GetCenter() - actualGoalPos).Length());

	float_precision initCost = costFunc(startArea, NULL, NULL);
	if (initCost < 0.0f)
		return false;

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

	// keep track of the area we visit that is closest to the goal
	if (closestArea != NULL)
		*closestArea = startArea;

	float closestAreaDist = startArea->GetTotalCost();

	// do A* search
	while (!CNavArea::IsOpenListEmpty())
	{
		// get next area to check
		CNavArea *area = CNavArea::PopOpenList();

		// check if we have found the goal area
		if (area == goalArea)
		{
			if (closestArea != NULL)
				*closestArea = goalArea;

			return true;
		}

		// search adjacent areas
		bool searchFloor = true;
		int dir = NORTH;
		const NavConnectList *floorList = area->GetAdjacentList(NORTH);
		NavConnectList::const_iterator floorIter = floorList->begin();

		bool ladderUp = true;
		const NavLadderList *ladderList = NULL;
		NavLadderList::const_iterator ladderIter;
		enum
		{
			AHEAD = 0,
			LEFT,
			RIGHT,
			BEHIND,
			NUM_TOP_DIRECTIONS
		};

		int ladderTopDir;
		while (true)
		{
			CNavArea *newArea;
			NavTraverseType how;
			const CNavLadder *ladder = NULL;

			// Get next adjacent area - either on floor or via ladder
			if (searchFloor)
			{
				// if exhausted adjacent connections in current direction, begin checking next direction
				if (floorIter == floorList->end())
				{
					++dir;

					if (dir == NUM_DIRECTIONS)
					{
						// checked all directions on floor - check ladders next
						searchFloor = false;

						ladderList = area->GetLadderList(LADDER_UP);
						ladderIter = ladderList->begin();
						ladderTopDir = AHEAD;
					}
					else
					{
						// start next direction
						floorList = area->GetAdjacentList((NavDirType)dir);
						floorIter = floorList->begin();
					}
					continue;
				}

				newArea = (*floorIter).area;
				how = (NavTraverseType)dir;
				++floorIter;
			}
			// search ladders
			else
			{
				if (ladderIter == ladderList->end())
				{
					if (!ladderUp)
					{
						// checked both ladder directions - done
						break;
					}
					else
					{
						// check down ladders
						ladderUp = false;
						ladderList = area->GetLadderList(LADDER_DOWN);
						ladderIter = ladderList->begin();
					}
					continue;
				}

				if (ladderUp)
				{
					ladder = (*ladderIter);

					// cannot use this ladder if the ladder bottom is hanging above our head
					if (ladder->m_isDangling)
					{
						++ladderIter;
						continue;
					}

					// do not use BEHIND connection, as its very hard to get to when going up a ladder
					if (ladderTopDir == AHEAD)
						newArea = ladder->m_topForwardArea;
					else if (ladderTopDir == LEFT)
						newArea = ladder->m_topLeftArea;
					else if (ladderTopDir == RIGHT)
						newArea = ladder->m_topRightArea;
					else
					{
						++ladderIter;
						continue;
					}

					how = GO_LADDER_UP;
					++ladderTopDir;
				}
				else
				{
					newArea = (*ladderIter)->m_bottomArea;
					how = GO_LADDER_DOWN;
					ladder = (*ladderIter);
					++ladderIter;
				}

				if (newArea == NULL)
					continue;
			}

			// don't backtrack
			if (newArea == area)
				continue;

			float_precision newCostSoFar = costFunc(newArea, area, ladder);

			// check if cost functor says this area is a dead-end
			if (newCostSoFar < 0.0f)
				continue;

			if ((newArea->IsOpen() || newArea->IsClosed()) && newArea->GetCostSoFar() <= newCostSoFar)
			{
				// this is a worse path - skip it
				continue;
			}
			else
			{
				// compute estimate of distance left to go
				float_precision newCostRemaining = (*newArea->GetCenter() - actualGoalPos).Length();

				// track closest area to goal in case path fails
				if (closestArea != NULL && newCostRemaining < closestAreaDist)
				{
					*closestArea = newArea;
					closestAreaDist = newCostRemaining;
				}

				newArea->SetParent(area, how);
				newArea->SetCostSoFar(newCostSoFar);
				newArea->SetTotalCost(newCostSoFar + newCostRemaining);

				if (newArea->IsClosed())
					newArea->RemoveFromClosedList();

				if (newArea->IsOpen())
				{
					// area already on open list, update the list order to keep costs sorted
					newArea->UpdateOnOpenList();
				}
				else
					newArea->AddToOpenList();
			}
		}

		// we have searched this area
		area->AddToClosedList();
	}

	return false;
}

// Compute distance between two areas. Return -1 if can't reach 'endArea' from 'startArea'.
template <typename CostFunctor>
float_precision NavAreaTravelDistance(CNavArea *startArea, CNavArea *endArea, CostFunctor &costFunc)
{
	if (startArea == NULL)
		return -1.0f;

	if (endArea == NULL)
		return -1.0f;

	if (startArea == endArea)
		return 0.0f;

	// compute path between areas using given cost heuristic
	if (NavAreaBuildPath(startArea, endArea, NULL, costFunc) == false)
		return -1.0f;

	// compute distance along path
	float_precision distance = 0.0f;
	for (CNavArea *area = endArea; area->GetParent(); area = area->GetParent())
	{
		distance += (*area->GetCenter() - *area->GetParent()->GetCenter()).Length();
	}

	return distance;
}

// Compute distance from area to position. Return -1 if can't reach position.
template <typename CostFunctor>
float NavAreaTravelDistance(const Vector *startPos, CNavArea *startArea, const Vector *goalPos, CostFunctor &costFunc)
{
	if (startArea == NULL || startPos == NULL || goalPos == NULL)
		return -1.0f;

	// compute path between areas using given cost heuristic
	CNavArea *goalArea = NULL;
	if (NavAreaBuildPath(startArea, TheNavAreaGrid.GetNearestNavArea(goalPos), goalPos, costFunc, &goalArea) == false)
		return -1.0f;

	if (goalArea == NULL)
		return -1.0f;

	// compute distance along path
	if (goalArea->GetParent() == NULL)
	{
		return (*goalPos - *startPos).Length();
	}
	else
	{
		CNavArea *area = goalArea->GetParent();
		float distance = (*goalPos - *area->GetCenter()).Length();

		for (; area->GetParent(); area = area->GetParent())
		{
			distance += (*area->GetCenter() - *area->GetParent()->GetCenter()).Length();
		}

		return distance;
	}
}

// Do a breadth-first search, invoking functor on each area.
// If functor returns 'true', continue searching from this area.
// If functor returns 'false', the area's adjacent areas are not explored (dead end).
// If 'maxRange' is 0 or less, no range check is done (all areas will be examined).
// NOTE: Returns all areas that overlap range, even partially
// TODO: Use ladder connections
//
// helper function
inline void AddAreaToOpenList(CNavArea *area, CNavArea *parent, const Vector *startPos, float maxRange)
{
	if (area == NULL)
		return;

	if (!area->IsMarked())
	{
		area->Mark();
		area->SetTotalCost(0.0f);
		area->SetParent(parent);

		if (maxRange > 0.0f)
		{
			// make sure this area overlaps range
			Vector closePos;
			area->GetClosestPointOnArea(startPos, &closePos);
			if ((closePos - *startPos).Make2D().IsLengthLessThan(maxRange))
			{
				// compute approximate distance along path to limit travel range, too
				float distAlong = parent->GetCostSoFar();
				distAlong += (*area->GetCenter() - *parent->GetCenter()).Length();
				area->SetCostSoFar(distAlong);

				// allow for some fudge due to large size areas
				if (distAlong <= 1.5f * maxRange)
					area->AddToOpenList();
			}
		}
		else
		{
			// infinite range
			area->AddToOpenList();
		}
	}
}

template <typename Functor>
void SearchSurroundingAreas(CNavArea *startArea, const Vector *startPos, Functor &func, float maxRange = -1.0f)
{
	if (startArea == NULL || startPos == NULL)
		return;

	CNavArea::MakeNewMarker();
	CNavArea::ClearSearchLists();

	startArea->AddToOpenList();
	startArea->SetTotalCost(0.0f);
	startArea->SetCostSoFar(0.0f);
	startArea->SetParent(NULL);
	startArea->Mark();

	while (!CNavArea::IsOpenListEmpty())
	{
		// get next area to check
		CNavArea *area = CNavArea::PopOpenList();

		// invoke functor on area
		if (func(area))
		{
			// explore adjacent floor areas
			for (int dir = 0; dir < NUM_DIRECTIONS; ++dir)
			{
				int count = area->GetAdjacentCount((NavDirType)dir);
				for (int i = 0; i < count; ++i)
				{
					CNavArea *adjArea = area->GetAdjacentArea((NavDirType)dir, i);
					AddAreaToOpenList(adjArea, area, startPos, maxRange);
				}
			}

			// explore adjacent areas connected by ladders
			NavLadderList::const_iterator ladderIt;

			// check up ladders
			const NavLadderList *ladderList = area->GetLadderList(LADDER_UP);
			if (ladderList != NULL)
			{
				for (ladderIt = ladderList->begin(); ladderIt != ladderList->end(); ++ladderIt)
				{
					const CNavLadder *ladder = *ladderIt;

					// cannot use this ladder if the ladder bottom is hanging above our head
					if (ladder->m_isDangling)
					{
						continue;
					}

					// do not use BEHIND connection, as its very hard to get to when going up a ladder
					AddAreaToOpenList(ladder->m_topForwardArea, area, startPos, maxRange);
					AddAreaToOpenList(ladder->m_topLeftArea, area, startPos, maxRange);
					AddAreaToOpenList(ladder->m_topRightArea, area, startPos, maxRange);
				}
			}

			// check down ladders
			ladderList = area->GetLadderList(LADDER_DOWN);
			if (ladderList != NULL)
			{
				for (ladderIt = ladderList->begin(); ladderIt != ladderList->end(); ++ladderIt)
				{
					const CNavLadder *ladder = *ladderIt;
					AddAreaToOpenList(ladder->m_bottomArea, area, startPos, maxRange);
				}
			}
		}
	}
}

// Apply the functor to all navigation areas
// If functor returns false, stop processing and return false.
template <typename Functor>
bool ForAllAreas(Functor &func)
{
	NavAreaList::iterator iter;
	for (iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter)
	{
		CNavArea *area = (*iter);
		if (func(area) == false)
			return false;
	}

	return true;
}

// Fuctor that returns lowest cost for farthest away areas
// For use with FindMinimumCostArea()
class FarAwayFunctor
{
public:
	float operator()(CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
	{
		if (area == fromArea)
			return 9999999.9f;

		return 1.0f / (*fromArea->GetCenter() - *area->GetCenter()).Length();
	}
};

// Fuctor that returns lowest cost for farthest away areas
// For use with FindMinimumCostArea()
class FarAwayFromPositionFunctor
{
public:
	FarAwayFromPositionFunctor(const Vector *pos) { m_pos = pos; }

	float operator() (CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
	{
		return 1.0f / (*m_pos - *area->GetCenter()).Length();
	}

private:
	const Vector *m_pos;
};

// Pick a low-cost area of "decent" size
template <typename CostFunctor>
CNavArea *FindMinimumCostArea(CNavArea *startArea, CostFunctor &costFunc)
{
	const float minSize = 150.0f;

	// collect N low-cost areas of a decent size
	enum { NUM_CHEAP_AREAS = 32 };
	struct
	{
		CNavArea *area;
		float cost;
	}
	cheapAreaSet[NUM_CHEAP_AREAS];
	int cheapAreaSetCount = 0;

	NavAreaList::iterator iter;
	for (iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter)
	{
		CNavArea *area = (*iter);

		// skip the small areas
		const Extent *extent = area->GetExtent();
		if (extent->hi.x - extent->lo.x < minSize || extent->hi.y - extent->lo.y < minSize)
			continue;

		// compute cost of this area
		float cost = costFunc(area, startArea, NULL);

		if (cheapAreaSetCount < NUM_CHEAP_AREAS)
		{
			cheapAreaSet[cheapAreaSetCount].area = area;
			cheapAreaSet[cheapAreaSetCount++].cost = cost;
		}
		else
		{
			// replace most expensive cost if this is cheaper
			int expensive = 0;
			for (int i = 1; i < NUM_CHEAP_AREAS; ++i)
			{
				if (cheapAreaSet[i].cost > cheapAreaSet[expensive].cost)
					expensive = i;
			}

			if (cheapAreaSet[expensive].cost > cost)
			{
				cheapAreaSet[expensive].area = area;
				cheapAreaSet[expensive].cost = cost;
			}
		}
	}
	if (cheapAreaSetCount)
	{
		// pick one of the areas at random
		return cheapAreaSet[RANDOM_LONG(0, cheapAreaSetCount - 1)].area;
	}
	else
	{
		// degenerate case - no decent sized areas - pick a random area
		int numAreas = TheNavAreaList.size();
		int which = RANDOM_LONG(0, numAreas - 1);

		NavAreaList::iterator iter;
		for (iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter)
		{
			if (which-- == 0)
				break;
		}

		return (*iter);
	}
}

bool IsHidingSpotInCover(const Vector *spot);
void ClassifySniperSpot(HidingSpot *spot);
void DestroyHidingSpots();
void EditNavAreas(NavEditCmdType cmd);
bool GetGroundHeight(const Vector *pos, float *height, Vector *normal = NULL);
bool GetSimpleGroundHeight(const Vector *pos, float *height, Vector *normal = NULL);
CNavArea *GetMarkedArea();
void EditNavAreasReset();
void DrawHidingSpots(const CNavArea *area);
void IncreaseDangerNearby(int teamID, float amount, CNavArea *startArea, const Vector *pos, float maxRadius);
void DrawDanger();
bool IsSpotOccupied(CBaseEntity *me, const Vector *pos);	// if a player is at the given spot, return true
const Vector *FindNearbyHidingSpot(CBaseEntity *me, const Vector *pos, CNavArea *startArea, float maxRange = 1000.0f, bool isSniper = false, bool useNearest = false);
const Vector *FindNearbyRetreatSpot(CBaseEntity *me, const Vector *start, CNavArea *startArea, float maxRange = 1000.0f, int avoidTeam = 0, bool useCrouchAreas = true);

// return true if moving from "start" to "finish" will cross a player's line of fire
bool IsCrossingLineOfFire(const Vector &start, const Vector &finish, CBaseEntity *ignore = NULL, int ignoreTeam = 0);
const Vector *FindRandomHidingSpot(CBaseEntity *me, Place place, bool isSniper = false);
HidingSpot *GetHidingSpotByID(unsigned int id);
void ApproachAreaAnalysisPrep();
void CleanupApproachAreaAnalysisPrep();
void DestroyLadders();
void DestroyNavigationMap();
void StripNavigationAreas();
CNavArea *FindFirstAreaInDirection(const Vector *start, NavDirType dir, float range, float beneathLimit, CBaseEntity *traceIgnore, Vector *closePos);
bool testJumpDown(const Vector *fromPos, const Vector *toPos);
void ConnectGeneratedAreas();
void MergeGeneratedAreas();
bool IsAreaRoughlySquare(const CNavArea *area);
void SplitX(CNavArea *area);
void SplitY(CNavArea *area);
void SquareUpAreas();
bool TestArea(CNavNode *node, int width, int height);
int BuildArea(CNavNode *node, int width, int height);
void BuildLadders();
void MarkJumpAreas();
void GenerateNavigationAreaMesh();

#endif // NAV_AREA_H