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

#ifndef AI_SQUAD_H
#define AI_SQUAD_H

#include "ai_memory.h"
#include "ai_squadslot.h"
#include "bitstring.h"

class CAI_Squad;
typedef CHandle<CAI_BaseNPC> AIHANDLE;

#define PER_ENEMY_SQUADSLOTS 1


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

DECLARE_POINTER_HANDLE(AISquadsIter_t);
DECLARE_POINTER_HANDLE(AISquadIter_t);

#define	MAX_SQUAD_MEMBERS	16
#define MAX_SQUAD_DATA_SLOTS 4

//-----------------------------------------------------------------------------
// CAI_SquadManager
//
// Purpose: Manages all the squads in the system
//
//-----------------------------------------------------------------------------

class CAI_SquadManager
{
public:
	CAI_SquadManager()
	{
		m_pSquads = NULL;
	}

	CAI_Squad *		GetFirstSquad( AISquadsIter_t *pIter );
	CAI_Squad *		GetNextSquad( AISquadsIter_t *pIter );
	int				NumSquads();

	CAI_Squad *		FindSquad( string_t squadName );	// Returns squad of the given name
	CAI_Squad *		CreateSquad( string_t squadName );	// Returns squad of the given name
	CAI_Squad *		FindCreateSquad( string_t squadName );	// Returns squad of the given name
	CAI_Squad *		FindCreateSquad( CAI_BaseNPC *pNPC, string_t squadName );	// Returns squad of the given name

	void			DeleteSquad( CAI_Squad *pSquad );
	void			DeleteAllSquads(void);

#ifdef MAPBASE_VSCRIPT
	HSCRIPT			ScriptGetFirstSquad();
	HSCRIPT			ScriptGetNextSquad( HSCRIPT hStart );

	HSCRIPT			ScriptFindSquad( const char *squadName );
	HSCRIPT			ScriptFindCreateSquad( const char *squadName );
#endif

private:

	CAI_Squad *		m_pSquads;										// A linked list of all squads

};

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

extern CAI_SquadManager g_AI_SquadManager;

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

#ifdef PER_ENEMY_SQUADSLOTS

struct AISquadEnemyInfo_t
{
	EHANDLE 						hEnemy;
	CBitVec<MAX_SQUADSLOTS>	slots;									// What squad slots are filled?

	DECLARE_SIMPLE_DATADESC();
};

#endif

//-----------------------------------------------------------------------------
// CAI_Squad
//
// Purpose: Tracks enemies, squad slots, squad members
//
//-----------------------------------------------------------------------------

class CAI_Squad
{
public:

	const char *			GetName() const	{ return STRING(m_Name); }

	void					RemoveFromSquad( CAI_BaseNPC *pNPC, bool bDeath = false );

	CAI_BaseNPC *			GetFirstMember( AISquadIter_t *pIter = NULL, bool bIgnoreSilentMembers = true );
	CAI_BaseNPC *			GetNextMember( AISquadIter_t *pIter, bool bIgnoreSilentMembers = true );
	CAI_BaseNPC *			GetAnyMember();
	int						NumMembers( bool bIgnoreSilentMembers = true );
	int						GetSquadIndex( CAI_BaseNPC * );

	void					SquadNewEnemy ( CBaseEntity *pEnemy );
	void					UpdateEnemyMemory( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy, const Vector &position );

	bool 					OccupyStrategySlotRange( CBaseEntity *pEnemy, int slotIDStart, int slotIDEnd, int *pSlot );
	void 					VacateStrategySlot( CBaseEntity *pEnemy, int slot);
	bool					IsStrategySlotRangeOccupied( CBaseEntity *pEnemy, int slotIDStart, int slotIDEnd );
	
	CAI_BaseNPC	*			SquadMemberInRange( const Vector &vecLocation, float flDist );
	CAI_BaseNPC *			NearestSquadMember( CAI_BaseNPC *pMember );
	int						GetVisibleSquadMembers( CAI_BaseNPC *pMember );
	CAI_BaseNPC *			GetSquadMemberNearestTo( const Vector &vecLocation );
	bool					SquadIsMember( CBaseEntity *pMember );
	bool					IsLeader( CAI_BaseNPC *pLeader );
	CAI_BaseNPC				*GetLeader( void );

	int						BroadcastInteraction( int interactionType, void *data, CBaseCombatCharacter *sender = NULL );

	void					AddToSquad(CAI_BaseNPC *pNPC);
	bool					FOkToMakeSound( int soundPriority );
	void					JustMadeSound( int soundPriority, float time );
	float					GetSquadSoundWaitTime() const		{ return m_flSquadSoundWaitTime; }
	void					SetSquadSoundWaitTime( float time ) { m_flSquadSoundWaitTime = time; }
	void					SquadRemember( int iMemory );

	void					SetSquadInflictor( CBaseEntity *pInflictor );
	bool					IsSquadInflictor( CBaseEntity *pInflictor );

	static bool				IsSilentMember( const CAI_BaseNPC *pNPC );

	template <typename T>
	void					SetSquadData( unsigned slot, const T &data )
	{
		Assert( slot < MAX_SQUAD_DATA_SLOTS );
		if ( slot < MAX_SQUAD_DATA_SLOTS )
		{
			m_SquadData[slot] = *((int *)&data);
		}
	}

	template <typename T>
	void					GetSquadData( unsigned slot, T *pData )
	{
		Assert( slot < MAX_SQUAD_DATA_SLOTS );
		if ( slot < MAX_SQUAD_DATA_SLOTS )
		{
			*pData = *((T *)&m_SquadData[slot]);
		}
	}


private:
	void OccupySlot( CBaseEntity *pEnemy, int i );
	void VacateSlot( CBaseEntity *pEnemy, int i );
	bool IsSlotOccupied( CBaseEntity *pEnemy, int i ) const;

#ifdef MAPBASE_VSCRIPT
	// Functions tailored specifically for VScript.
	ALLOW_SCRIPT_ACCESS();
private:

	HSCRIPT					ScriptGetFirstMember( bool bIgnoreSilentMembers );
	HSCRIPT					ScriptGetMember( int iIndex );
	HSCRIPT					ScriptGetAnyMember();
	//int					ScriptNumMembers( bool bIgnoreSilentMembers );
	int						ScriptGetSquadIndex( HSCRIPT hNPC );

	void					ScriptUpdateEnemyMemory( HSCRIPT hUpdater, HSCRIPT hEnemy, const Vector &position );
	
	HSCRIPT					ScriptSquadMemberInRange( const Vector &vecLocation, float flDist );
	HSCRIPT					ScriptNearestSquadMember( HSCRIPT hMember );
	int						ScriptGetVisibleSquadMembers( HSCRIPT hMember );
	HSCRIPT					ScriptGetSquadMemberNearestTo( const Vector &vecLocation );
	bool					ScriptIsMember( HSCRIPT hMember );
	bool					ScriptIsLeader( HSCRIPT hLeader );
	HSCRIPT					ScriptGetLeader( void );

	void					ScriptAddToSquad( HSCRIPT hNPC );
	void					ScriptRemoveFromSquad( HSCRIPT hNPC );

	bool					ScriptIsSilentMember( HSCRIPT hNPC );

	void					ScriptSetSquadData( int iSlot, const char *data );
	const char				*ScriptGetSquadData( int iSlot );
#endif

private:
	friend class CAI_SaveRestoreBlockHandler;
	friend class CAI_SquadManager;

	CAI_Squad();
	CAI_Squad(string_t squadName);
	~CAI_Squad(void);

	CAI_Squad*				GetNext() { return m_pNextSquad; }

	void Init( string_t squadName );

	CAI_Squad *										m_pNextSquad;								// The next squad is list of all squads

	string_t										m_Name;
	CUtlVectorFixed<AIHANDLE, MAX_SQUAD_MEMBERS>	m_SquadMembers;

	float											m_flSquadSoundWaitTime;			// Time when I'm allowed to make another sound
	int												m_nSquadSoundPriority;			// if we're still waiting, this is the priority of the current sound

	EHANDLE											m_hSquadInflictor;

	int												m_SquadData[MAX_SQUAD_DATA_SLOTS];

#ifdef PER_ENEMY_SQUADSLOTS

	AISquadEnemyInfo_t *FindEnemyInfo( CBaseEntity *pEnemy );
	const AISquadEnemyInfo_t *FindEnemyInfo( CBaseEntity *pEnemy ) const	{ return const_cast<CAI_Squad *>(this)->FindEnemyInfo( pEnemy ); }

	AISquadEnemyInfo_t *			m_pLastFoundEnemyInfo; // Occupy/Vacate need to be reworked to not want this
	
	CUtlVector<AISquadEnemyInfo_t>	m_EnemyInfos;
	float							m_flEnemyInfoCleanupTime;

#else
	
	CVarBitVec	m_squadSlotsUsed;							// What squad slots are filled?

#endif

	//---------------------------------
public:
	DECLARE_SIMPLE_DATADESC();
};

//-----------------------------------------------------------------------------
//
// Purpose: CAI_SquadManager inline functions
//
//-----------------------------------------------------------------------------

inline CAI_Squad *CAI_SquadManager::GetFirstSquad( AISquadsIter_t *pIter )
{
	*pIter = (AISquadsIter_t)m_pSquads;
	return m_pSquads;
}

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

inline CAI_Squad *CAI_SquadManager::GetNextSquad( AISquadsIter_t *pIter )
{
	CAI_Squad *pSquad = (CAI_Squad *)*pIter;
	if ( pSquad )
		pSquad = pSquad->m_pNextSquad;
	*pIter = (AISquadsIter_t)pSquad;
	return pSquad;
}

//-------------------------------------
// Purpose: Returns squad of the given name or creates a new squad with the
//			given name if none exists and add pNPC to the list of members
//-------------------------------------

inline CAI_Squad *CAI_SquadManager::FindCreateSquad(CAI_BaseNPC *pNPC, string_t squadName)
{
	CAI_Squad* pSquad = FindSquad( squadName );
	
	if ( !pSquad )
		pSquad = CreateSquad( squadName );
	
	pSquad->AddToSquad( pNPC );

	return pSquad;
}

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

inline CAI_Squad *CAI_SquadManager::FindCreateSquad(string_t squadName)
{
	CAI_Squad* pSquad = FindSquad( squadName );
	
	if ( !pSquad )
		pSquad = CreateSquad( squadName );
	
	return pSquad;
}

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

inline CAI_BaseNPC *CAI_Squad::GetAnyMember()
{
	if ( m_SquadMembers.Count() )
		return m_SquadMembers[random->RandomInt( 0, m_SquadMembers.Count()-1 )];
	return NULL;
}

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

inline int CAI_Squad::GetSquadIndex( CAI_BaseNPC *pAI )
{
	for ( int i = 0; i < m_SquadMembers.Count(); i++ )
	{
		if ( m_SquadMembers[i] == pAI )
			return i;
	}
	return -1;
}


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

#endif // AI_SQUAD_H
