class SecurityAgentSpawnedEvent extends Event
{
	var spawnedAgent : DeviceLink;
	var eventType : gameEntitySpawnerEventType;
	var securityAreas : array< SecurityAreaControllerPS >;

	public static function Construct( agentLink : DeviceLink, type : gameEntitySpawnerEventType, const areas : ref< array< SecurityAreaControllerPS > > ) : SecurityAgentSpawnedEvent
	{
		var spawnEvent : SecurityAgentSpawnedEvent;
		spawnEvent = new SecurityAgentSpawnedEvent;
		spawnEvent.spawnedAgent = agentLink;
		spawnEvent.eventType = type;
		spawnEvent.securityAreas = areas;
		return spawnEvent;
	}

}

struct OnDisableAreaData
{
	var agent : PersistentID;
	var remainingAreas : array< SecurityAreaControllerPS >;
}

class AgentRegistry
{
	[ unsavable = "true" ]
	protected persistent var m_isInitialized : Bool;
	[ unsavable = "true" ]
	protected persistent var m_agents : array< Agent >;
	var m_agentsLock : RWLock;
	var m_maxReprimandsPerNPC : Int32;
	default m_maxReprimandsPerNPC = 2;
	var m_maxReprimandsPerDEVICE : Int32;
	default m_maxReprimandsPerDEVICE = 1;

	public static function Construct() : AgentRegistry
	{
		var registry : AgentRegistry;
		registry = new AgentRegistry;
		registry.m_isInitialized = true;
		return registry;
	}

	private const function IsInitialized() : Bool
	{
		return m_isInitialized;
	}

	public const function IsReady() : Bool
	{
		var i : Int32;
		if( !( IsInitialized() ) )
		{
			return false;
		}
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			if( !( Agent.IsValid( m_agents[ i ] ) ) )
			{
				RWLock.ReleaseShared( m_agentsLock );
				return false;
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return true;
	}

	public function RegisterAgent( agentPS : DeviceLink, const areas : ref< array< SecurityAreaControllerPS > > )
	{
		var index : Int32;
		var agent : Agent;
		RWLock.Acquire( m_agentsLock );
		if( !( GetAgentIndex_NoLock( DeviceLink.GetLinkID( agentPS ), index ) ) )
		{
			agent = Agent.Construct( agentPS, areas );
			SaveAgent_NoLock( agent );
		}
		RWLock.Release( m_agentsLock );
	}

	public function UnregisterAgent( agentID : PersistentID )
	{
		var index : Int32;
		RWLock.Acquire( m_agentsLock );
		if( GetAgentIndex_NoLock( agentID, index ) )
		{
			m_agents.Erase( index );
		}
		RWLock.Release( m_agentsLock );
	}

	public const function IsAgent( id : PersistentID ) : Bool
	{
		var ix : Int32;
		return GetAgentIndex( id, ix );
	}

	public const function GetAgent( id : PersistentID, recordCopy : ref< Agent > ) : Bool
	{
		var index : Int32;
		var found : Bool;
		found = false;
		RWLock.AcquireShared( m_agentsLock );
		if( GetAgentIndex_NoLock( id, index ) )
		{
			recordCopy = m_agents[ index ];
			found = true;
		}
		RWLock.ReleaseShared( m_agentsLock );
		return found;
	}

	public const function GetAgentsIDs() : array< PersistentID >
	{
		var ids : array< PersistentID >;
		var i : Int32;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			ids.PushBack( DeviceLink.GetLinkID( m_agents[ i ].link ) );
		}
		RWLock.ReleaseShared( m_agentsLock );
		return ids;
	}

	public const function GetAgents() : array< Agent >
	{
		var i : Int32;
		var data : array< Agent >;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			data.PushBack( m_agents[ i ] );
		}
		RWLock.ReleaseShared( m_agentsLock );
		return data;
	}

	public const function GetAgents( const filter : ref< array< SecurityAreaControllerPS > > ) : array< Agent >
	{
		var agents : array< Agent >;
		var i : Int32;
		var k : Int32;
		var y : Int32;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			for( k = 0; k < m_agents[ i ].areas.Size(); k += 1 )
			{
				for( y = 0; y < filter.Size(); y += 1 )
				{
					if( DeviceLink.GetLinkID( m_agents[ i ].areas[ k ] ) == filter[ y ].GetID() )
					{
						agents.PushBack( m_agents[ i ] );
					}
				}
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return agents;
	}

	public const function GetAgentsIDs( const filter : ref< array< SecurityAreaControllerPS > > ) : array< PersistentID >
	{
		var agentIDs : array< PersistentID >;
		var i : Int32;
		var k : Int32;
		var y : Int32;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			for( k = 0; k < m_agents[ i ].areas.Size(); k += 1 )
			{
				for( y = 0; y < filter.Size(); y += 1 )
				{
					if( DeviceLink.GetLinkID( m_agents[ i ].areas[ k ] ) == filter[ y ].GetID() )
					{
						agentIDs.PushBack( DeviceLink.GetLinkID( m_agents[ i ].link ) );
					}
				}
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return agentIDs;
	}

	public function AddArea( area : SecurityAreaControllerPS, const agents : ref< array< DeviceComponentPS > > )
	{
		var i, index : Int32;
		RWLock.Acquire( m_agentsLock );
		for( i = 0; i < agents.Size(); i += 1 )
		{
			if( GetAgentIndex_NoLock( agents[ i ].GetID(), index ) )
			{
				Agent.AddArea( m_agents[ index ], area );
			}
		}
		RWLock.Release( m_agentsLock );
	}

	public function RemoveArea( const data : ref< array< OnDisableAreaData > > )
	{
		var i, index : Int32;
		RWLock.Acquire( m_agentsLock );
		for( i = 0; i < data.Size(); i += 1 )
		{
			if( GetAgentIndex_NoLock( data[ i ].agent, index ) )
			{
				Agent.RemoveArea( m_agents[ index ], data[ i ].remainingAreas );
			}
		}
		RWLock.Release( m_agentsLock );
	}

	public const function GetTurrets() : array< Agent >
	{
		var i : Int32;
		var matchingAgents : array< Agent >;
		var agentClassName : CName;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			agentClassName = DeviceLink.GetLinkClassName( m_agents[ i ].link );
			if( agentClassName == 'SecurityTurretControllerPS' )
			{
				matchingAgents.PushBack( m_agents[ i ] );
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return matchingAgents;
	}

	public const function GetSensors() : array< Agent >
	{
		var i : Int32;
		var matchingAgents : array< Agent >;
		var agentClassName : CName;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			agentClassName = DeviceLink.GetLinkClassName( m_agents[ i ].link );
			if( agentClassName == 'SurveillanceCameraControllerPS' || agentClassName == 'SecurityTurretControllerPS' )
			{
				matchingAgents.PushBack( m_agents[ i ] );
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return matchingAgents;
	}

	public const function HowManyTimesEntityReprimandedByThisAgentAlready( target : EntityID, agentID : PersistentID ) : Int32
	{
		var result, index : Int32;
		result = 0;
		RWLock.AcquireShared( m_agentsLock );
		if( GetAgentIndex_NoLock( agentID, index ) )
		{
			result = Agent.GetReprimandsCount( m_agents[ index ], target );
		}
		RWLock.ReleaseShared( m_agentsLock );
		return result;
	}

	public const function HowManyTimesEntityReprimandedByThisAgentAlready( tresspasser : GameObject, agent : PersistentID ) : Int32
	{
		if( tresspasser )
		{
			return HowManyTimesEntityReprimandedByThisAgentAlready( tresspasser.GetEntityID(), agent );
		}
		return 0;
	}

	public const function HasEntityBeenSpottedTooManyTimes( agent : PersistentID, target : EntityID ) : Bool
	{
		var maxCount, count, index : Int32;
		var isTooManyTimes : Bool;
		isTooManyTimes = false;
		RWLock.AcquireShared( m_agentsLock );
		if( GetAgentIndex_NoLock( agent, index ) )
		{
			if( DeviceLink.GetLinkClassName( m_agents[ index ].link ) == 'PuppetDeviceLinkPS' )
			{
				maxCount = m_maxReprimandsPerNPC;
			}
			else
			{
				maxCount = m_maxReprimandsPerDEVICE;
			}
			count = Agent.GetReprimandsCount( m_agents[ index ], target );
			isTooManyTimes = count > maxCount;
		}
		RWLock.ReleaseShared( m_agentsLock );
		return isTooManyTimes;
	}

	public function StoreReprimand( agentID : PersistentID, target : EntityID, reprimandID : Int32, targetAttitude : CName )
	{
		var reprimandData : ReprimandData;
		var index : Int32;
		reprimandData.isActive = true;
		reprimandData.receiver = target;
		reprimandData.reprimandID = reprimandID;
		reprimandData.receiverAttitudeGroup = targetAttitude;
		RWLock.Acquire( m_agentsLock );
		if( GetAgentIndex_NoLock( agentID, index ) )
		{
			Agent.StoreReprimand( m_agents[ index ], reprimandData );
		}
		RWLock.Release( m_agentsLock );
	}

	public function WipeReprimandData( target : EntityID )
	{
		var i : Int32;
		RWLock.Acquire( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			Agent.WipeReprimand( m_agents[ i ], target );
		}
		RWLock.Release( m_agentsLock );
	}

	public const function IsReprimandOngoing() : Bool
	{
		var i : Int32;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			if( Agent.IsPerformingReprimand( m_agents[ i ] ) )
			{
				RWLock.ReleaseShared( m_agentsLock );
				return true;
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return false;
	}

	public const function GetReprimandReceiver( agentID : PersistentID ) : EntityID
	{
		var index : Int32;
		var receiver : EntityID;
		RWLock.AcquireShared( m_agentsLock );
		if( GetAgentIndex_NoLock( agentID, index ) )
		{
			receiver = Agent.GetReprimandReceiver( m_agents[ index ] );
		}
		RWLock.ReleaseShared( m_agentsLock );
		return receiver;
	}

	public const function IsReprimandOngoingAgainst( suspect : EntityID ) : Bool
	{
		var i : Int32;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			if( Agent.IsPerformingReprimandAgainst( m_agents[ i ], suspect ) )
			{
				RWLock.ReleaseShared( m_agentsLock );
				return true;
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return false;
	}

	public const function GetReprimandPerformer( target : EntityID, agent : ref< Agent > ) : Bool
	{
		var i : Int32;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			if( Agent.IsPerformingReprimandAgainst( m_agents[ i ], target ) )
			{
				agent = m_agents[ i ];
				RWLock.ReleaseShared( m_agentsLock );
				return true;
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return false;
	}

	public function ReleaseFromReprimandAgainst( target : EntityID, optional agent : PersistentID )
	{
		var i, index : Int32;
		if( PersistentID.IsDefined( agent ) )
		{
			RWLock.Acquire( m_agentsLock );
			if( GetAgentIndex_NoLock( agent, index ) )
			{
				Agent.ReleaseFromReprimand( m_agents[ index ], target );
			}
			RWLock.Release( m_agentsLock );
			return;
		}
		RWLock.Acquire( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			Agent.ReleaseFromReprimand( m_agents[ i ], target );
		}
		RWLock.Release( m_agentsLock );
	}

	public function ReleaseAllReprimands( agents : ref< array< Agent > > )
	{
		var i : Int32;
		RWLock.Acquire( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			if( Agent.IsPerformingReprimand( m_agents[ i ] ) )
			{
				agents.PushBack( m_agents[ i ] );
				Agent.ForceRelaseReprimands( m_agents[ i ] );
			}
		}
		RWLock.Release( m_agentsLock );
	}

	public function CleanUpOnNewAttitudeGroup( gameInstance : GameInstance, newGroup : CName )
	{
		var i : Int32;
		var attSystem : AttitudeSystem;
		attSystem = GameInstance.GetAttitudeSystem( gameInstance );
		if( !( attSystem ) )
		{
			return;
		}
		RWLock.Acquire( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			Agent.WipeReprimand( m_agents[ i ], newGroup, attSystem );
		}
		RWLock.Release( m_agentsLock );
	}

	protected function SaveAgent_NoLock( const agent : ref< Agent > )
	{
		if( !( Agent.IsValid( agent ) ) )
		{
			return;
		}
		m_agents.PushBack( agent );
	}

	protected const function GetAgentIndex( id : PersistentID, out index : Int32 ) : Bool
	{
		var found : Bool;
		found = false;
		RWLock.AcquireShared( m_agentsLock );
		found = GetAgentIndex_NoLock( id, index );
		RWLock.ReleaseShared( m_agentsLock );
		return found;
	}

	protected const function GetAgentIndex_NoLock( id : PersistentID, out index : Int32 ) : Bool
	{
		var i : Int32;
		index = -1;
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			if( ( PersistentID.ExtractEntityID( DeviceLink.GetLinkID( m_agents[ i ].link ) ) == PersistentID.ExtractEntityID( id ) ) || DeviceLink.GetLinkID( m_agents[ i ].link ) == id )
			{
				index = i;
				return true;
			}
		}
		return false;
	}

	public const function GetValidAgents( state : ESecuritySystemState, const breachedAreas : ref< array< SecurityAreaControllerPS > > ) : array< SecuritySystemOutputData >
	{
		var i : Int32;
		var outgoingFilterType : EFilterType;
		var validAgent : SecuritySystemOutputData;
		var validAgents : array< SecuritySystemOutputData >;
		var breachOrigin : EBreachOrigin;
		for( i = 0; i < breachedAreas.Size(); i += 1 )
		{
			if( ( ( Int32 )( breachedAreas[ i ].GetOutgoingFilter() ) ) > ( ( Int32 )( outgoingFilterType ) ) )
			{
				outgoingFilterType = breachedAreas[ i ].GetOutgoingFilter();
			}
		}
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			if( Agent.IsEligible( m_agents[ i ], state, breachedAreas, outgoingFilterType, breachOrigin ) )
			{
				validAgent.link = m_agents[ i ].link;
				validAgent.delayDuration = m_agents[ i ].cachedDelayDuration;
				validAgent.breachOrigin = breachOrigin;
				validAgents.PushBack( validAgent );
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return validAgents;
	}

	public const function GetAgentAreas( id : PersistentID ) : array< DeviceLink >
	{
		var areas : array< DeviceLink >;
		var index : Int32;
		RWLock.AcquireShared( m_agentsLock );
		if( GetAgentIndex_NoLock( id, index ) )
		{
			Agent.GetAreas( m_agents[ index ], areas );
		}
		RWLock.ReleaseShared( m_agentsLock );
		return areas;
	}

	public const function GetSupportedAgents() : array< Agent >
	{
		var i : Int32;
		var supportedAgents : array< Agent >;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			if( Agent.HasSupport( m_agents[ i ] ) )
			{
				supportedAgents.PushBack( m_agents[ i ] );
			}
		}
		RWLock.ReleaseShared( m_agentsLock );
		return supportedAgents;
	}

	public function ClearSupport()
	{
		var i : Int32;
		RWLock.AcquireShared( m_agentsLock );
		for( i = 0; i < m_agents.Size(); i += 1 )
		{
			Agent.ClearSupport( m_agents[ i ] );
		}
		RWLock.ReleaseShared( m_agentsLock );
	}

	public function ProcessOnPlayerSpotted( evt : PlayerSpotted, modifiedAgents : ref< array< Agent > >, out hasAnySupport : Bool ) : Bool
	{
		var i : Int32;
		var spottee : PersistentID;
		var isNPC : Bool;
		var isSpotted : Bool;
		var agentAreas : array< SecurityAreaControllerPS >;
		var outgoingFilterType : EFilterType;
		spottee = evt.GetOwnerID();
		isNPC = evt.GetComesFromNPC();
		isSpotted = evt.DoesSee();
		agentAreas = evt.GetAgentAreas();
		hasAnySupport = false;
		if( isSpotted )
		{
			for( i = 0; i < agentAreas.Size(); i += 1 )
			{
				if( ( ( Int32 )( agentAreas[ i ].GetOutgoingFilter() ) ) > ( ( Int32 )( outgoingFilterType ) ) )
				{
					outgoingFilterType = agentAreas[ i ].GetOutgoingFilter();
					if( outgoingFilterType == EFilterType.ALLOW_ALL )
					{
						break;
					}
				}
			}
			RWLock.Acquire( m_agentsLock );
			for( i = 0; i < m_agents.Size(); i += 1 )
			{
				if( ( isNPC && DeviceLink.GetLinkClassName( m_agents[ i ].link ) == 'PuppetDeviceLinkPS' ) || spottee == DeviceLink.GetLinkID( m_agents[ i ].link ) )
				{
					continue;
				}
				if( Agent.IsEligibleToShareData( m_agents[ i ], ESecuritySystemState.COMBAT, agentAreas, outgoingFilterType ) )
				{
					if( Agent.AddSupport( m_agents[ i ], spottee, true ) )
					{
						modifiedAgents.PushBack( m_agents[ i ] );
					}
				}
				if( !( hasAnySupport ) && Agent.HasSupport( m_agents[ i ] ) )
				{
					hasAnySupport = true;
				}
			}
			RWLock.Release( m_agentsLock );
		}
		else
		{
			RWLock.Acquire( m_agentsLock );
			for( i = 0; i < m_agents.Size(); i += 1 )
			{
				if( Agent.AddSupport( m_agents[ i ], spottee, false ) )
				{
					modifiedAgents.PushBack( m_agents[ i ] );
				}
			}
			if( !( hasAnySupport ) && Agent.HasSupport( m_agents[ i ] ) )
			{
				hasAnySupport = true;
			}
			RWLock.Release( m_agentsLock );
		}
		return modifiedAgents.Size() > 0;
	}

	private const function IsIndexOutOfBound( index : Int32 ) : Bool
	{
		RWLock.AcquireShared( m_agentsLock );
		if( ( index < 0 ) || ( index > ( m_agents.Size() - 1 ) ) )
		{
			RWLock.ReleaseShared( m_agentsLock );
			return true;
		}
		RWLock.ReleaseShared( m_agentsLock );
		return false;
	}

}

