//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:		An NPC's memory of potential enemies 
//
//=============================================================================//

#include "cbase.h"
#include "isaverestore.h"
#include "ai_debug.h"
#include "ai_memory.h"
#include "ai_basenpc.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define	EMEMORY_POOL_SIZE		  64
#define AI_FREE_KNOWLEDGE_DURATION 1.75

//-----------------------------------------------------------------------------
// AI_EnemyInfo_t
//
//-----------------------------------------------------------------------------


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

AI_EnemyInfo_t::AI_EnemyInfo_t(void) 
{
	hEnemy				= NULL;
	vLastKnownLocation	= vec3_origin;
	vLastSeenLocation	= vec3_origin;
	timeLastSeen = 0;
	timeFirstSeen = 0;
	timeLastReacquired = 0;
	timeValidEnemy = 0;
	timeLastReceivedDamageFrom = 0;
	timeAtFirstHand = AI_INVALID_TIME;
	bDangerMemory = 0;
	bEludedMe = 0;
	bUnforgettable = 0;
	bMobbedMe = 0;
}

 
//-----------------------------------------------------------------------------
// CAI_EnemiesListSaveRestoreOps
//
// Purpose: Handles save and load for enemy memories
//
//-----------------------------------------------------------------------------

class CAI_EnemiesListSaveRestoreOps : public CDefSaveRestoreOps
{
public:
	CAI_EnemiesListSaveRestoreOps()
	{
	}

	virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave )
	{
		CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField;
		
		int nMemories = pMemMap->Count();
		pSave->WriteInt( &nMemories );
		
		for ( CAI_Enemies::CMemMap::IndexType_t i = pMemMap->FirstInorder(); i != pMemMap->InvalidIndex(); i = pMemMap->NextInorder( i ) )
		{
			pSave->WriteAll( (*pMemMap)[i] );
		}
	}
	
	virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore )
	{
		CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField;
		Assert( pMemMap->Count() == 0 );
		
		int nMemories = pRestore->ReadInt();
		
		while ( nMemories-- )
		{
			AI_EnemyInfo_t *pAddMemory = new AI_EnemyInfo_t;
			
			pRestore->ReadAll( pAddMemory );
			
			if ( pAddMemory->hEnemy != NULL )
			{
				pMemMap->Insert( pAddMemory->hEnemy, pAddMemory );
			}
			else
				delete pAddMemory;
		}
	}
	
	virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
	{
		CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField;
		
		for ( CAI_Enemies::CMemMap::IndexType_t i = pMemMap->FirstInorder(); i != pMemMap->InvalidIndex(); i = pMemMap->NextInorder( i ) )
		{
			delete (*pMemMap)[i];
		}
		
		pMemMap->RemoveAll();
	}

	virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
	{
		CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField;
		return ( pMemMap->Count() == 0 );
	}
	
} g_AI_MemoryListSaveRestoreOps;

//-----------------------------------------------------------------------------
// CAI_Enemies
//
// Purpose: Stores a set of AI_EnemyInfo_t's
//
//-----------------------------------------------------------------------------

BEGIN_SIMPLE_DATADESC( CAI_Enemies )

	DEFINE_CUSTOM_FIELD( m_Map, &g_AI_MemoryListSaveRestoreOps ),
  	DEFINE_FIELD( m_flFreeKnowledgeDuration,		FIELD_FLOAT ),
	DEFINE_FIELD( m_flEnemyDiscardTime,				FIELD_FLOAT ),
  	DEFINE_FIELD( m_vecDefaultLKP,					FIELD_POSITION_VECTOR ),
  	DEFINE_FIELD( m_vecDefaultLSP,					FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_serial,							FIELD_INTEGER ),

END_DATADESC()

BEGIN_SIMPLE_DATADESC( AI_EnemyInfo_t )
	DEFINE_FIELD( vLastKnownLocation, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( vLastSeenLocation, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( hEnemy, 			FIELD_EHANDLE ),
	DEFINE_FIELD( timeLastSeen, 	FIELD_TIME ),
	DEFINE_FIELD( timeFirstSeen, 	FIELD_TIME ),
	DEFINE_FIELD( timeLastReacquired, FIELD_TIME ),
	DEFINE_FIELD( timeValidEnemy, 	FIELD_TIME ),
	DEFINE_FIELD( timeLastReceivedDamageFrom, 	FIELD_TIME ),
	DEFINE_FIELD( timeAtFirstHand,	FIELD_TIME ),
	DEFINE_FIELD( bDangerMemory, 	FIELD_BOOLEAN ),
	DEFINE_FIELD( bEludedMe, 		FIELD_BOOLEAN ),
	DEFINE_FIELD( bUnforgettable,	FIELD_BOOLEAN ),
	DEFINE_FIELD( bMobbedMe,		FIELD_BOOLEAN ),
	// NOT SAVED nextEMemory
END_DATADESC()

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

CAI_Enemies::CAI_Enemies(void)
{
	m_flFreeKnowledgeDuration = AI_FREE_KNOWLEDGE_DURATION;
	m_flEnemyDiscardTime = AI_DEF_ENEMY_DISCARD_TIME;
	m_vecDefaultLKP = vec3_invalid;
	m_vecDefaultLSP = vec3_invalid;
	m_serial = 0;
	SetDefLessFunc( m_Map );
}


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

CAI_Enemies::~CAI_Enemies()
{
	for ( CMemMap::IndexType_t i = m_Map.FirstInorder(); i != m_Map.InvalidIndex(); i = m_Map.NextInorder( i ) )
	{
		delete m_Map[i];
	}
}

//-----------------------------------------------------------------------------
// Purpose:	Purges any dead enemies from memory
//-----------------------------------------------------------------------------

AI_EnemyInfo_t *CAI_Enemies::GetFirst( AIEnemiesIter_t *pIter )
{
	CMemMap::IndexType_t i = m_Map.FirstInorder();
	*pIter = (AIEnemiesIter_t)(uintp)i;

	if ( i == m_Map.InvalidIndex() )
		return NULL;

	if ( m_Map[i]->hEnemy == NULL )
		return GetNext( pIter );

	return m_Map[i];
}

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

AI_EnemyInfo_t *CAI_Enemies::GetNext( AIEnemiesIter_t *pIter )
{
	CMemMap::IndexType_t i = (CMemMap::IndexType_t)((uintp)(*pIter));

	if ( i == m_Map.InvalidIndex() )
		return NULL;

	i = m_Map.NextInorder( i );
	*pIter = (AIEnemiesIter_t)(uintp)i;
	if ( i == m_Map.InvalidIndex() )
		return NULL;

	if ( m_Map[i]->hEnemy == NULL )
		return GetNext( pIter );

	return m_Map[i];
}
	
//-----------------------------------------------------------------------------

AI_EnemyInfo_t *CAI_Enemies::Find( CBaseEntity *pEntity, bool bTryDangerMemory )
{
	if ( pEntity == AI_UNKNOWN_ENEMY )
		pEntity = NULL;

	CMemMap::IndexType_t i = m_Map.Find( pEntity );
	if ( i == m_Map.InvalidIndex() )
	{
		if ( !bTryDangerMemory || ( i = m_Map.Find( NULL ) ) == m_Map.InvalidIndex() )
			return NULL;
		Assert(m_Map[i]->bDangerMemory == true);
	}
	return m_Map[i];
}


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

AI_EnemyInfo_t *CAI_Enemies::GetDangerMemory()
{
	CMemMap::IndexType_t i = m_Map.Find( NULL );
	if ( i == m_Map.InvalidIndex() )
		return NULL;
	Assert(m_Map[i]->bDangerMemory == true);
	return m_Map[i];
}

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

bool CAI_Enemies::ShouldDiscardMemory( AI_EnemyInfo_t *pMemory )
{
	CBaseEntity *pEnemy = pMemory->hEnemy;

	if ( pEnemy )
	{
		CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer();
		if ( pEnemyNPC && pEnemyNPC->GetState() == NPC_STATE_DEAD )
			return true;
	}
	else
	{
		if ( !pMemory->bDangerMemory )
			return true;
	}

	if ( !pMemory->bUnforgettable &&
		 gpGlobals->curtime > pMemory->timeLastSeen + m_flEnemyDiscardTime )
	{
		return true;
	}

	return false;
}


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

void CAI_Enemies::RefreshMemories(void)
{
	AI_PROFILE_SCOPE(CAI_Enemies_RefreshMemories);

	if ( m_flFreeKnowledgeDuration >= m_flEnemyDiscardTime )
	{
		m_flFreeKnowledgeDuration = m_flEnemyDiscardTime - .1;
	}

	// -------------------
	// Check each record
	// -------------------
	
	CMemMap::IndexType_t i = m_Map.FirstInorder();
	while ( i != m_Map.InvalidIndex() )
	{	
		AI_EnemyInfo_t *pMemory = m_Map[i];
		
		CMemMap::IndexType_t iNext = m_Map.NextInorder( i ); // save so can remove
		if ( ShouldDiscardMemory( pMemory ) )
		{
			delete pMemory;
			m_Map.RemoveAt(i);
		}
		else if ( pMemory->hEnemy )
		{
			if ( gpGlobals->curtime <= pMemory->timeLastSeen + m_flFreeKnowledgeDuration )
			{
				// Free knowledge is ignored if the target has notarget on
				if ( !(pMemory->hEnemy->GetFlags() & FL_NOTARGET) )
				{
					pMemory->vLastKnownLocation = pMemory->hEnemy->GetAbsOrigin();
				}
			}

			if ( gpGlobals->curtime <= pMemory->timeLastSeen )
			{
				pMemory->vLastSeenLocation = pMemory->hEnemy->GetAbsOrigin();
			}
		}
		i = iNext;
	}
}

//-----------------------------------------------------------------------------
// Purpose:	Updates information about our enemies
// Output : Returns true if new enemy, false if already know of enemy
//-----------------------------------------------------------------------------

bool CAI_Enemies::UpdateMemory(CAI_Network* pAINet, CBaseEntity *pEnemy, const Vector &vPosition, float reactionDelay, bool firstHand )
{
	if ( pEnemy == AI_UNKNOWN_ENEMY )
		pEnemy = NULL;

	const float DIST_TRIGGER_REACQUIRE_SQ			= Square(20.0 * 12.0);
	const float TIME_TRIGGER_REACQUIRE				= 4.0;
	const float MIN_DIST_TIME_TRIGGER_REACQUIRE_SQ 	= Square(4.0 * 12.0);

	AI_EnemyInfo_t *pMemory = Find( pEnemy );
	// -------------------------------------------
	//  Otherwise just update my own
	// -------------------------------------------
	// Update enemy information
	if ( pMemory )
	{
		Assert(pEnemy || pMemory->bDangerMemory == true);

		if ( firstHand )
			pMemory->timeLastSeen = gpGlobals->curtime;
		pMemory->bEludedMe = false;
		
		float deltaDist = (pMemory->vLastKnownLocation - vPosition).LengthSqr();
		
		if (deltaDist>DIST_TRIGGER_REACQUIRE_SQ || ( deltaDist>MIN_DIST_TIME_TRIGGER_REACQUIRE_SQ && ( gpGlobals->curtime - pMemory->timeLastSeen ) > TIME_TRIGGER_REACQUIRE ) )
		{
			pMemory->timeLastReacquired = gpGlobals->curtime;
		}

		// Only update if the enemy has moved
		if (deltaDist>Square(12.0))
		{
			pMemory->vLastKnownLocation = vPosition;

		}

		// Update the time at which we first saw him firsthand
		if ( firstHand && pMemory->timeAtFirstHand == AI_INVALID_TIME )
		{
			pMemory->timeAtFirstHand = gpGlobals->curtime;
		}

		return false;
	}

	// If not on my list of enemies add it
	AI_EnemyInfo_t *pAddMemory = new AI_EnemyInfo_t;
	pAddMemory->vLastKnownLocation = vPosition;

	if ( firstHand )
	{
		pAddMemory->timeLastReacquired = pAddMemory->timeFirstSeen = pAddMemory->timeLastSeen = pAddMemory->timeAtFirstHand = gpGlobals->curtime;
	}
	else
	{
		// Block free knowledge
		pAddMemory->timeLastReacquired = pAddMemory->timeFirstSeen = pAddMemory->timeLastSeen = ( gpGlobals->curtime - (m_flFreeKnowledgeDuration + 0.01) );
		pAddMemory->timeAtFirstHand = AI_INVALID_TIME;
	}

	if ( reactionDelay > 0.0 )
		pAddMemory->timeValidEnemy = gpGlobals->curtime + reactionDelay;

	pAddMemory->bEludedMe = false;

	// I'm either remembering a postion of an enmey of just a danger position
	pAddMemory->hEnemy = pEnemy;
	pAddMemory->bDangerMemory = ( pEnemy == NULL );

	// add to the list
	m_Map.Insert( pEnemy, pAddMemory );
	m_serial++;

	return true;
}

//------------------------------------------------------------------------------
// Purpose : Returns true if this enemy is part of my memory
//------------------------------------------------------------------------------
void CAI_Enemies::OnTookDamageFrom( CBaseEntity *pEnemy )
{
	AI_EnemyInfo_t *pMemory = Find( pEnemy, true );
	if ( pMemory )
		pMemory->timeLastReceivedDamageFrom = gpGlobals->curtime;
}

//------------------------------------------------------------------------------
// Purpose : Returns true if this enemy is part of my memory
//------------------------------------------------------------------------------
bool CAI_Enemies::HasMemory( CBaseEntity *pEnemy )
{
	return ( Find( pEnemy ) != NULL );
}

//-----------------------------------------------------------------------------
// Purpose:	Clear information about our enemy
//-----------------------------------------------------------------------------
void CAI_Enemies::ClearMemory(CBaseEntity *pEnemy)
{
	CMemMap::IndexType_t i = m_Map.Find( pEnemy );
	if ( i != m_Map.InvalidIndex() )
	{
		delete m_Map[i];
		m_Map.RemoveAt( i );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Notes that the given enemy has eluded me
//-----------------------------------------------------------------------------
void CAI_Enemies::MarkAsEluded( CBaseEntity *pEnemy )
{
	AI_EnemyInfo_t *pMemory = Find( pEnemy );
	if ( pMemory )
	{
		pMemory->bEludedMe = true;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns last known posiiton of given enemy
//-----------------------------------------------------------------------------
const Vector &CAI_Enemies::LastKnownPosition( CBaseEntity *pEnemy )
{
	AI_EnemyInfo_t *pMemory = Find( pEnemy, true );
	if ( pMemory )
	{
		m_vecDefaultLKP = pMemory->vLastKnownLocation;
	}
	else
	{
		DevWarning( 2,"Asking LastKnownPosition for enemy that's not in my memory!!\n");
	}
	return m_vecDefaultLKP;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the last position the enemy was SEEN at. This will always be
// different than LastKnownPosition() when the enemy is out of sight, because
// the last KNOWN position will be updated for a number of seconds after the 
// player disappears. 
//-----------------------------------------------------------------------------
const Vector &CAI_Enemies::LastSeenPosition( CBaseEntity *pEnemy )
{
	AI_EnemyInfo_t *pMemory = Find( pEnemy, true );
	if ( pMemory )
	{
		m_vecDefaultLSP = pMemory->vLastSeenLocation;
	}
	else
	{
		DevWarning( 2,"Asking LastSeenPosition for enemy that's not in my memory!!\n");
	}
	return m_vecDefaultLSP;
}

float CAI_Enemies::TimeLastReacquired( CBaseEntity *pEnemy )
{
	// I've never seen something that doesn't exist
	if (!pEnemy)
		return 0;

	AI_EnemyInfo_t *pMemory = Find( pEnemy, true );
	if ( pMemory )
		return pMemory->timeLastReacquired;

	if ( pEnemy != AI_UNKNOWN_ENEMY )
		DevWarning( 2,"Asking TimeLastReacquired for enemy that's not in my memory!!\n");
	return AI_INVALID_TIME;
}

//-----------------------------------------------------------------------------
// Purpose: Sets position to the last known position of an enemy.  If enemy
//			was not found returns last memory of danger position if it exists
// Output : Returns false is no position is known
//-----------------------------------------------------------------------------
float CAI_Enemies::LastTimeSeen( CBaseEntity *pEnemy, bool bCheckDangerMemory /*= true*/ )
{
	// I've never seen something that doesn't exist
	if (!pEnemy)
		return 0;

	AI_EnemyInfo_t *pMemory = Find( pEnemy, bCheckDangerMemory );
	if ( pMemory )
		return pMemory->timeLastSeen;

	if ( pEnemy != AI_UNKNOWN_ENEMY )
		DevWarning( 2,"Asking LastTimeSeen for enemy that's not in my memory!!\n");
	return AI_INVALID_TIME;
}

//-----------------------------------------------------------------------------
// Purpose: Get the time at which the enemy was first seen.
// Output : Returns false is no position is known
//-----------------------------------------------------------------------------
float CAI_Enemies::FirstTimeSeen( CBaseEntity *pEnemy)
{
	// I've never seen something that doesn't exist
	if (!pEnemy)
		return 0;

	AI_EnemyInfo_t *pMemory = Find( pEnemy, true );
	if ( pMemory )
		return pMemory->timeFirstSeen;

	if ( pEnemy != AI_UNKNOWN_ENEMY )
		DevWarning( 2,"Asking FirstTimeSeen for enemy that's not in my memory!!\n");
	return AI_INVALID_TIME;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEnemy - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_Enemies::HasFreeKnowledgeOf( CBaseEntity *pEnemy )
{
	// I've never seen something that doesn't exist
	if (!pEnemy)
		return 0;

	AI_EnemyInfo_t *pMemory = Find( pEnemy, true );
	if ( pMemory )
	{
		float flFreeKnowledgeTime = pMemory->timeLastSeen + m_flFreeKnowledgeDuration;
		return ( gpGlobals->curtime < flFreeKnowledgeTime );
	}

	if ( pEnemy != AI_UNKNOWN_ENEMY )
		DevWarning( 2,"Asking HasFreeKnowledgeOf for enemy that's not in my memory!!\n");
	return AI_INVALID_TIME;
}

//-----------------------------------------------------------------------------
float CAI_Enemies::LastTimeTookDamageFrom( CBaseEntity *pEnemy)
{
	// I've never seen something that doesn't exist
	if (!pEnemy)
		return 0;

	AI_EnemyInfo_t *pMemory = Find( pEnemy, true );
	if ( pMemory )
		return pMemory->timeLastReceivedDamageFrom;

	if ( pEnemy != AI_UNKNOWN_ENEMY )
		DevWarning( 2,"Asking LastTimeTookDamageFrom for enemy that's not in my memory!!\n");
	return AI_INVALID_TIME;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the time at which the enemy was first seen firsthand
// Input  : *pEnemy - 
// Output : float
//-----------------------------------------------------------------------------
float CAI_Enemies::TimeAtFirstHand( CBaseEntity *pEnemy )
{
	// I've never seen something that doesn't exist
	if (!pEnemy)
		return 0;

	AI_EnemyInfo_t *pMemory = Find( pEnemy, true );
	if ( pMemory )
		return pMemory->timeAtFirstHand;

	if ( pEnemy != AI_UNKNOWN_ENEMY )
		DevWarning( 2,"Asking TimeAtFirstHand for enemy that's not in my memory!!\n");
	return AI_INVALID_TIME;
}

//-----------------------------------------------------------------------------
// Purpose: Sets position to the last known position of an enemy.  If enemy
//			was not found returns last memory of danger position if it exists
// Output : Returns false is no position is known
//-----------------------------------------------------------------------------
bool CAI_Enemies::HasEludedMe( CBaseEntity *pEnemy )
{
	AI_EnemyInfo_t *pMemory = Find( pEnemy );
	if ( pMemory )
		return pMemory->bEludedMe;
	return false;
}

void CAI_Enemies::SetTimeValidEnemy( CBaseEntity *pEnemy, float flTime )
{
	AI_EnemyInfo_t *pMemory = Find( pEnemy );
	if ( pMemory )
		pMemory->timeValidEnemy = flTime;
}

//-----------------------------------------------------------------------------
void CAI_Enemies::SetUnforgettable( CBaseEntity *pEnemy, bool bUnforgettable )
{
	AI_EnemyInfo_t *pMemory = Find( pEnemy );
	if ( pMemory )
		pMemory->bUnforgettable = bUnforgettable;
}

//-----------------------------------------------------------------------------
void CAI_Enemies::SetMobbedMe( CBaseEntity *pEnemy, bool bMobbedMe )
{
	AI_EnemyInfo_t *pMemory = Find( pEnemy );
	if ( pMemory )
		pMemory->bMobbedMe = bMobbedMe;
}

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

void CAI_Enemies::SetFreeKnowledgeDuration( float flDuration )
{ 
	m_flFreeKnowledgeDuration = flDuration;	

	if ( m_flFreeKnowledgeDuration >= m_flEnemyDiscardTime )
	{
		// If your free knowledge time is greater than your discard time,
		// you'll forget about secondhand enemies passed to you by squadmates
		// as soon as you're given them.
		Assert( m_flFreeKnowledgeDuration < m_flEnemyDiscardTime );

		m_flFreeKnowledgeDuration = m_flEnemyDiscardTime - .1;
	}
}

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

void CAI_Enemies::SetEnemyDiscardTime( float flTime )
{ 
	m_flEnemyDiscardTime = flTime;			

	if ( m_flFreeKnowledgeDuration >= m_flEnemyDiscardTime )
	{
		// If your free knowledge time is greater than your discard time,
		// you'll forget about secondhand enemies passed to you by squadmates
		// as soon as you're given them.
		Assert( m_flFreeKnowledgeDuration < m_flEnemyDiscardTime );

		m_flFreeKnowledgeDuration = m_flEnemyDiscardTime - .1;
	}
}