//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//

#ifndef AI_BEHAVIOR_FOLLOW_H
#define AI_BEHAVIOR_FOLLOW_H

#include "simtimer.h"
#include "ai_behavior.h"
#include "ai_goalentity.h"
#include "ai_utils.h"
#include "ai_moveshoot.h"

#ifdef HL2_EPISODIC
	#include "hl2_gamerules.h"
#endif

#if defined( _WIN32 )
#pragma once
#endif


//-----------------------------------------------------------------------------
// NOTE: these must correspond with the AI_FollowFormation_t array in AI_Behavior_Follow.cpp!!
//-----------------------------------------------------------------------------
enum AI_Formations_t
{
	AIF_SIMPLE,
	AIF_WIDE,
	AIF_ANTLION,
	AIF_COMMANDER,
	AIF_TIGHT,
	AIF_MEDIUM,
	AIF_SIDEKICK,
	AIF_HUNTER,
	AIF_VORTIGAUNT,
};

enum AI_FollowFormationFlags_t
{
	AIFF_DEFAULT 					= 0,
	AIFF_USE_FOLLOW_POINTS 			= 0x01,
	AIFF_REQUIRE_LOS_OUTSIDE_COMBAT	= 0x02,
};

//-----------------------------------------------------------------------------
//
// CAI_FollowGoal
//
// Purpose: A level tool to control the follow behavior. Use is not required
//			in order to use behavior.
//
//-----------------------------------------------------------------------------

class CAI_FollowGoal : public CAI_GoalEntity
{
	DECLARE_CLASS( CAI_FollowGoal, CAI_GoalEntity );

public:

	virtual void EnableGoal( CAI_BaseNPC *pAI );
	virtual void DisableGoal( CAI_BaseNPC *pAI  );
#ifdef HL2_EPISODIC
	virtual void InputOutsideTransition( inputdata_t &inputdata );
#endif

	int m_iFormation;

	DECLARE_DATADESC();
};

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

int AIGetNumFollowers( CBaseEntity *pEntity, string_t iszClassname = NULL_STRING );

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

struct AI_FollowNavInfo_t
{
	int		flags;
	Vector  position;
	float 	range;
	float	Zrange;
	float   tolerance;
	float   followPointTolerance;
	float	targetMoveTolerance;
	float	repathOnRouteTolerance;
	float	walkTolerance;
	float	coverTolerance;
	float	enemyLOSTolerance;
	float	chaseEnemyTolerance;

	DECLARE_SIMPLE_DATADESC();
};

struct AI_FollowGroup_t;

struct AI_FollowManagerInfoHandle_t
{
	AI_FollowGroup_t *m_pGroup;
	intp m_hFollower;
};

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

struct AI_FollowParams_t
{
	AI_FollowParams_t( AI_Formations_t formation = AIF_SIMPLE, bool bNormalMemoryDiscard = false )
	 :	formation(formation),
		bNormalMemoryDiscard( bNormalMemoryDiscard )
	{
	}
	
	AI_Formations_t formation;
	bool			bNormalMemoryDiscard;
	
	DECLARE_SIMPLE_DATADESC();
};

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

class CAI_FollowBehavior : public CAI_SimpleBehavior
{
	DECLARE_CLASS( CAI_FollowBehavior, CAI_SimpleBehavior );
public:
	CAI_FollowBehavior( const AI_FollowParams_t &params = AIF_SIMPLE );
	~CAI_FollowBehavior();

	virtual int		DrawDebugTextOverlays( int text_offset );
	virtual void	DrawDebugGeometryOverlays();
	
	// Returns true if the NPC is actively following a target.
	bool			IsActive( void );

	void			SetParameters( const AI_FollowParams_t &params );

	virtual const char *GetName() {	return "Follow"; }
	AI_Formations_t GetFormation() const { return m_params.formation; }

	virtual bool 	CanSelectSchedule();

	const AI_FollowNavInfo_t &GetFollowGoalInfo();
	CBaseEntity *	GetFollowTarget();
	void			SetFollowTarget( CBaseEntity *pLeader, bool fFinishCurSchedule = false );

	CAI_FollowGoal *GetFollowGoal()	{ return m_hFollowGoalEnt; } // if any
	bool			SetFollowGoal( CAI_FollowGoal *pGoal, bool fFinishCurSchedule = false );
	void			ClearFollowGoal( CAI_FollowGoal *pGoal );
	void			SetFollowGoalDirect( CAI_FollowGoal *pGoal );

	virtual bool	FarFromFollowTarget()	{ return ( m_hFollowTarget && (GetAbsOrigin() - m_hFollowTarget->GetAbsOrigin()).LengthSqr() > (75*12)*(75*12) ); }

	virtual bool	TargetIsUnreachable() { return m_bTargetUnreachable; }
	
	int				GetNumFailedFollowAttempts()	{ return m_nFailedFollowAttempts; }
	float			GetTimeFailFollowStarted()		{ return m_flTimeFailFollowStarted; }
	bool			FollowTargetVisible() { return HasCondition( COND_FOLLOW_TARGET_VISIBLE ); };

	bool			IsMovingToFollowTarget();

	float			GetGoalRange();
	float			GetGoalZRange();

	virtual Activity	NPC_TranslateActivity( Activity activity );
	virtual int			TranslateSchedule( int scheduleType );
	virtual void	StartTask( const Task_t *pTask );
	virtual int		SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
	virtual void	TaskComplete( bool fIgnoreSetFailedCondition = false );
	virtual void 	GatherConditions();

protected:

	const Vector	&GetGoalPosition();

	virtual bool	ShouldFollow();

	friend class CAI_FollowManager;

	virtual void	BeginScheduleSelection();
	virtual void	EndScheduleSelection();
	
	virtual void	CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput );

	virtual void	Precache();
	virtual int		SelectSchedule();
	virtual int		FollowCallBaseSelectSchedule() { return BaseClass::SelectSchedule(); }
	virtual void	OnStartSchedule( int scheduleType );
	virtual void	RunTask( const Task_t *pTask );
	void			BuildScheduleTestBits();

	bool			IsCurScheduleFollowSchedule();

	virtual bool	IsCurTaskContinuousMove();
	virtual void	OnMovementFailed();
	virtual void	OnMovementComplete();
	virtual bool	FValidateHintType( CAI_Hint *pHint );
	
	bool			IsValidCover( const Vector &vLocation, CAI_Hint const *pHint );
	bool			IsValidShootPosition( const Vector &vLocation, CAI_Node *pNode, CAI_Hint const *pHint );
	bool 			FindCoverFromEnemyAtFollowTarget( float coverRadius, Vector *pResult );
	
	bool			ShouldAlwaysThink();

	bool			ShouldMoveToFollowTarget();

	int 			SelectScheduleManagePosition();
	int 			SelectScheduleFollowPoints();
	int 			SelectScheduleMoveToFormation();

	void			GetFollowTargetViewLoc( Vector *pResult);
	bool			ValidateFaceTarget(  Vector *pFaceTarget );

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

	bool			ShouldUseFollowPoints();
	bool			HasFollowPoint();
	void 			SetFollowPoint( CAI_Hint *pHintNode );
	void			ClearFollowPoint();
	const Vector &	GetFollowPoint();
	CAI_Hint *		FindFollowPoint();
	bool			IsFollowPointInRange();
	bool			ShouldIgnoreFollowPointFacing();

	//----------------------------
	
	bool 			UpdateFollowPosition();
	const int		GetGoalFlags();
	float 			GetGoalTolerance();
	bool			PlayerIsPushing();

	bool IsFollowTargetInRange( float rangeMultiplier = 1.0 );

	bool			IsFollowGoalInRange( float tolerance, float zTolerance, int flags );
	virtual bool	IsChaseGoalInRange();

	void			NoteFailedFollow();
	void			NoteSuccessfulFollow();

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

protected:

	enum
	{
		SCHED_FOLLOWER_MOVE_AWAY_FAIL = BaseClass::NEXT_SCHEDULE,								// Turn back toward player
		SCHED_FOLLOWER_MOVE_AWAY_END,
		SCHED_FOLLOW,
		SCHED_FOLLOWER_IDLE_STAND,
		SCHED_MOVE_TO_FACE_FOLLOW_TARGET,
		SCHED_FACE_FOLLOW_TARGET,
		SCHED_FOLLOWER_GO_TO_WAIT_POINT,
		SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL,
		SCHED_FOLLOWER_STAND_AT_WAIT_POINT,
		SCHED_FOLLOWER_COMBAT_FACE,
		NEXT_SCHEDULE,

		TASK_CANT_FOLLOW = BaseClass::NEXT_TASK,
		TASK_FACE_FOLLOW_TARGET,
		TASK_MOVE_TO_FOLLOW_POSITION,
		TASK_GET_PATH_TO_FOLLOW_POSITION,
		TASK_SET_FOLLOW_TARGET_MARK,
		TASK_FOLLOWER_FACE_TACTICAL,
		TASK_SET_FOLLOW_DELAY,
		TASK_GET_PATH_TO_FOLLOW_POINT,
		TASK_ARRIVE_AT_FOLLOW_POINT,
		TASK_SET_FOLLOW_POINT_STAND_SCHEDULE,
		TASK_BEGIN_STAND_AT_WAIT_POINT,
		NEXT_TASK,

		COND_TARGET_MOVED_FROM_MARK = BaseClass::NEXT_CONDITION,
		COND_FOUND_WAIT_POINT,
		COND_FOLLOW_DELAY_EXPIRED,
		COND_FOLLOW_TARGET_VISIBLE,
		COND_FOLLOW_TARGET_NOT_VISIBLE,
		COND_FOLLOW_WAIT_POINT_INVALID,
		COND_FOLLOW_PLAYER_IS_LIT,
		COND_FOLLOW_PLAYER_IS_NOT_LIT,
		NEXT_CONDITION,
	};

	DEFINE_CUSTOM_SCHEDULE_PROVIDER;
	
protected:

	//----------------------------
	
	EHANDLE 		   				m_hFollowTarget;
	AI_FollowNavInfo_t 				m_FollowNavGoal;
	float							m_flTimeUpdatedFollowPosition;
	bool							m_bFirstFacing;
	float							m_flTimeFollowTargetVisible;
	
	CAI_MoveMonitor	   				m_TargetMonitor;
	bool							m_bTargetUnreachable;
	bool							m_bFollowNavFailed; // Set when pathfinding fails to limit impact of m_FollowDelay on ShouldFollow

	int								m_nFailedFollowAttempts;
	float							m_flTimeFailFollowStarted;
	Vector							m_vFollowMoveAnchor;

	bool							m_bMovingToCover;
	float							m_flOriginalEnemyDiscardTime;
	float							m_SavedDistTooFar;
	
	CRandStopwatch	   				m_FollowDelay;
	CSimpleSimTimer					m_RepathOnFollowTimer;
	
	//---------------------------------

	Activity						m_CurrentFollowActivity;

	//---------------------------------
	
	CRandSimTimer					m_TimeBlockUseWaitPoint;
	CSimTimer						m_TimeCheckForWaitPoint;
	CAI_Hint *						m_pInterruptWaitPoint;
	
	//---------------------------------

	CRandSimTimer					m_TimeBeforeSpreadFacing;
	CRandSimTimer					m_TimeNextSpreadFacing;

	//---------------------------------
	
	AI_FollowManagerInfoHandle_t 	m_hFollowManagerInfo;
	AI_FollowParams_t				m_params;

	//---------------------------------
	
	CHandle<CAI_FollowGoal>			m_hFollowGoalEnt;
	
	//---------------------------------
	
	DECLARE_DATADESC();
};

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

inline const AI_FollowNavInfo_t &CAI_FollowBehavior::GetFollowGoalInfo()
{
	return m_FollowNavGoal;
}

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

inline const int CAI_FollowBehavior::GetGoalFlags()
{
	return m_FollowNavGoal.flags;
}

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

inline const Vector &CAI_FollowBehavior::GetGoalPosition()
{
	return m_FollowNavGoal.position;
}

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

inline float CAI_FollowBehavior::GetGoalTolerance()
{
	return m_FollowNavGoal.tolerance;
}

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

inline float CAI_FollowBehavior::GetGoalRange()
{
	return m_FollowNavGoal.range;
}

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

inline float CAI_FollowBehavior::GetGoalZRange()
{
	return m_FollowNavGoal.Zrange;
}

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

#endif // AI_BEHAVIOR_FOLLOW_H