class PoliceAgentRegistry
{
	private var m_game : GameInstance;
	private var m_vehicleAgents : array< VehicleAgent >;
	private var m_npcAgents : array< NPCAgent >;
	private var m_requestTickets : array< TicketData >;

	public function GetNPCList() : array< NPCAgent >
	{
		return m_npcAgents;
	}

	public function GetVehicleList() : array< VehicleAgent >
	{
		return m_vehicleAgents;
	}

	public function GetTotalVehicleCount() : Int32
	{
		return m_vehicleAgents.Size();
	}

	public function GetTotalPendingTicketsCount() : Int32
	{
		return m_requestTickets.Size();
	}

	public function GetTotalNPCCount() : Int32
	{
		var i, count : Int32;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( !( m_npcAgents[ i ].isQuestNPC ) )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetFallbackNPCCount() : Int32
	{
		var i, count : Int32;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( m_npcAgents[ i ].spawnedAsFallback )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetPendingFallbackOnFootTicketCount() : Int32
	{
		var i, count : Int32;
		for( i = 0; i < m_requestTickets.Size(); i += 1 )
		{
			if( m_requestTickets[ i ].isFallback )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetPendingVehicleTicketsCount() : Int32
	{
		var i, count : Int32;
		for( i = 0; i < m_requestTickets.Size(); i += 1 )
		{
			if( m_requestTickets[ i ].policeStrategy != vehiclePoliceStrategy.None )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetExternalNPCCount() : Int32
	{
		var i : Int32;
		var count : Int32;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( !( m_npcAgents[ i ].spawnedAsFallback ) && m_npcAgents[ i ].spawnedType == DynamicVehicleType.None )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetMaxTacNPCCount() : Int32
	{
		var i : Int32;
		var count : Int32;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( m_npcAgents[ i ].spawnedType == DynamicVehicleType.AV )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetMaxTacNPCList() : array< NPCAgent >
	{
		var i : Int32;
		var list : array< NPCAgent >;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( m_npcAgents[ i ].spawnedType == DynamicVehicleType.AV )
			{
				list.PushBack( m_npcAgents[ i ] );
			}
		}
		return list;
	}

	public function IsPreventionMaxTac( puppet : gamePuppet ) : Bool
	{
		var i : Int32;
		var arr : array< NPCAgent >;
		arr = GetMaxTacNPCList();
		for( i = 0; i < arr.Size(); i += 1 )
		{
			if( arr[ i ].id == puppet.GetEntityID() )
			{
				return true;
			}
		}
		return false;
	}

	public function DistanceSquaredToTClosestMaxTacAgent( positionToCheck : Vector4 ) : Float
	{
		var i : Int32;
		var maxTacAgentsList : array< NPCAgent >;
		var minDistanceSquared : Float;
		var currentDistanceSquared : Float;
		maxTacAgentsList = GetMaxTacNPCList();
		minDistanceSquared = Vector4.DistanceSquared( maxTacAgentsList[ 0 ].unit.GetWorldPosition(), positionToCheck );
		for( i = 1; i < maxTacAgentsList.Size(); i += 1 )
		{
			currentDistanceSquared = Vector4.DistanceSquared( maxTacAgentsList[ i ].unit.GetWorldPosition(), positionToCheck );
			minDistanceSquared = ( ( currentDistanceSquared < minDistanceSquared ) ? ( currentDistanceSquared ) : ( minDistanceSquared ) );
		}
		return minDistanceSquared;
	}

	public function GetEngagedVehicleList() : array< VehicleAgent >
	{
		var i : Int32;
		var engagedVehicles : array< VehicleAgent >;
		var vehicle : VehicleAgent;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( m_npcAgents[ i ].spawnedType != DynamicVehicleType.Car || !( NPCPuppet.IsInCombat( m_npcAgents[ i ].unit ) ) )
			{
				continue;
			}
			if( !( TryGetVehicleAgentByID( m_npcAgents[ i ].TryGetAssignedVehicleId(), vehicle ) ) )
			{
				continue;
			}
			if( engagedVehicles.Contains( vehicle ) )
			{
				continue;
			}
			engagedVehicles.PushBack( vehicle );
		}
		return engagedVehicles;
	}

	public function GetEngagedNotDisengagingVehicleList( out vehicleArray : array< VehicleAgent > ) : Bool
	{
		var i : Int32;
		vehicleArray.Clear();
		vehicleArray = GetEngagedVehicleList();
		for( i = vehicleArray.Size() - 1; i >= 0; i -= 1 )
		{
			if( vehicleArray[ i ].lifetimeStatus == LifetimeStatus.Disengaging )
			{
				vehicleArray.Erase( i );
			}
		}
		return vehicleArray.Size() > 0;
	}

	public function GetSupportVehicleList() : array< VehicleAgent >
	{
		var i : Int32;
		var supportVehicles : array< VehicleAgent >;
		var vehicle : VehicleAgent;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( m_npcAgents[ i ].spawnedType != DynamicVehicleType.Car || NPCPuppet.IsInCombat( m_npcAgents[ i ].unit ) )
			{
				continue;
			}
			if( !( TryGetVehicleAgentByID( m_npcAgents[ i ].TryGetAssignedVehicleId(), vehicle ) ) )
			{
				continue;
			}
			if( supportVehicles.Contains( vehicle ) )
			{
				continue;
			}
			supportVehicles.PushBack( vehicle );
		}
		return supportVehicles;
	}

	public function GetAvCount() : Int32
	{
		var i : Int32;
		var count : Int32;
		for( i = 0; i < m_vehicleAgents.Size(); i += 1 )
		{
			if( m_vehicleAgents[ i ].spawnedType == DynamicVehicleType.AV )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetRoadblockCount() : Int32
	{
		var i : Int32;
		var count : Int32;
		for( i = 0; i < m_vehicleAgents.Size(); i += 1 )
		{
			if( m_vehicleAgents[ i ].spawnedType == DynamicVehicleType.RoadBlockade || m_vehicleAgents[ i ].spawnedType == DynamicVehicleType.RoadBlockadeWithAV )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetRoadblockNPCList() : array< NPCAgent >
	{
		var i : Int32;
		var list : array< NPCAgent >;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( m_npcAgents[ i ].spawnedType == DynamicVehicleType.RoadBlockade || m_npcAgents[ i ].spawnedType == DynamicVehicleType.RoadBlockadeWithAV )
			{
				list.PushBack( m_npcAgents[ i ] );
			}
		}
		return list;
	}

	public function GetRoadblockVehicleList() : array< VehicleAgent >
	{
		var i : Int32;
		var list : array< VehicleAgent >;
		for( i = 0; i < m_vehicleAgents.Size(); i += 1 )
		{
			if( m_vehicleAgents[ i ].spawnedType == DynamicVehicleType.RoadBlockade || m_vehicleAgents[ i ].spawnedType == DynamicVehicleType.RoadBlockadeWithAV )
			{
				list.PushBack( m_vehicleAgents[ i ] );
			}
		}
		return list;
	}

	public function GetSupportVehiclesWithStrategyCount( strategy : vehiclePoliceStrategy ) : Int32
	{
		var count : Int32;
		var i : Int32;
		var list : array< VehicleAgent >;
		list = GetSupportVehicleList();
		for( i = 0; i < list.Size(); i += 1 )
		{
			if( list[ i ].unit.GetPoliceStrategy() == strategy )
			{
				count += 1;
			}
		}
		return count;
	}

	public function GetEngagedVehicleCount() : Int32
	{
		var list : array< VehicleAgent >;
		list = GetEngagedVehicleList();
		return list.Size();
	}

	public function GetSupportVehicleCount() : Int32
	{
		var list : array< VehicleAgent >;
		list = GetSupportVehicleList();
		return list.Size();
	}

	public function GetNPCsAssignedToVehicle( vehicleEntityId : EntityID, toFill : ref< array< NPCAgent > > )
	{
		var i : Int32;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			if( m_npcAgents[ i ].TryGetAssignedVehicleId() == vehicleEntityId )
			{
				toFill.PushBack( m_npcAgents[ i ] );
			}
		}
	}

	public function GetVehiclesWithoutRegisteredPassengers( toFill : ref< array< VehicleAgent > > )
	{
		var i : Int32;
		var vehiclesAssignedToNPCs : array< EntityID >;
		var vehicleID : EntityID;
		for( i = 0; i < m_npcAgents.Size(); i += 1 )
		{
			vehicleID = m_npcAgents[ i ].TryGetAssignedVehicleId();
			if( !( vehiclesAssignedToNPCs.Contains( vehicleID ) ) )
			{
				vehiclesAssignedToNPCs.PushBack( vehicleID );
			}
		}
		for( i = 0; i < m_vehicleAgents.Size(); i += 1 )
		{
			if( m_vehicleAgents[ i ].spawnedType != DynamicVehicleType.Car )
			{
				continue;
			}
			if( !( m_vehicleAgents[ i ].everHadPassengers ) )
			{
				continue;
			}
			vehicleID = m_vehicleAgents[ i ].id;
			if( vehiclesAssignedToNPCs.Contains( vehicleID ) )
			{
				vehiclesAssignedToNPCs.Remove( vehicleID );
				continue;
			}
			toFill.PushBack( m_vehicleAgents[ i ] );
		}
	}

	public function IsPoliceInCombatWithPalyer() : Bool
	{
		var i : Int32;
		var player : Entity;
		player = GetPlayerObject( m_game );
		for( i = 0; i <= m_npcAgents.Size(); i += 1 )
		{
			if( NPCPuppet.IsInCombatWithTarget( m_npcAgents[ i ].unit, player ) )
			{
				return true;
			}
		}
		return false;
	}

	public function HasPoliceRecentlyDeescalated() : Bool
	{
		var threatData : DroppedThreatData;
		var targetTrackingExtension : TargetTrackingExtension;
		var i : Int32;
		for( i = 0; i <= m_npcAgents.Size(); i += 1 )
		{
			targetTrackingExtension = ( ( TargetTrackingExtension )( m_npcAgents[ i ].unit.GetTargetTrackerComponent() ) );
			if( targetTrackingExtension )
			{
				threatData = targetTrackingExtension.GetRecentlyDroppedThreat();
				if( threatData.threat && ( ( GameObject )( threatData.threat ) ).IsPlayer() )
				{
					return true;
				}
			}
		}
		return false;
	}

	public function HasNPCBeenAttackedByPlayer( id : EntityID ) : Bool
	{
		var agent : NPCAgent;
		if( TryGetNPCAgentByID( id, agent ) )
		{
			if( agent.hasBeenAttackedByPlayer )
			{
				return true;
			}
		}
		return false;
	}

	public static function Construct( game : GameInstance ) : PoliceAgentRegistry
	{
		var registry : PoliceAgentRegistry;
		registry = new PoliceAgentRegistry;
		registry.m_game = game;
		return registry;
	}

	public function Contains( id : EntityID ) : Bool
	{
		var contains : Bool;
		contains = TryGetIndexOf_NPC( id ) || TryGetIndexOf_Vehicle( id );
		return contains;
	}

	public function TryGetNPCAgentByID( id : EntityID, agent : ref< NPCAgent > ) : Bool
	{
		var i : Int32;
		if( TryGetIndexOf_NPC( id, i ) )
		{
			agent = m_npcAgents[ i ];
			return true;
		}
		return false;
	}

	public function TryGetVehicleAgentByID( id : EntityID, agent : ref< VehicleAgent > ) : Bool
	{
		var i : Int32;
		if( TryGetIndexOf_Vehicle( id, i ) )
		{
			agent = m_vehicleAgents[ i ];
			return true;
		}
		return false;
	}

	public function GetNPCSpawnedType( id : EntityID ) : DynamicVehicleType
	{
		var agent : NPCAgent;
		if( TryGetNPCAgentByID( id, agent ) )
		{
			return agent.spawnedType;
		}
		return DynamicVehicleType.None;
	}

	public function RegisterAgent( unit : GameObject, type : DynamicVehicleType, optional strategy : vehiclePoliceStrategy, optional isFallback : Bool, overrideExisting : Bool ) : Bool
	{
		var npcAgent : NPCAgent;
		var vehicleAgent : VehicleAgent;
		var i : Int32;
		var id : EntityID;
		id = unit.GetEntityID();
		if( unit.IsPuppet() )
		{
			if( TryGetIndexOf_NPC( id, i ) )
			{
				if( overrideExisting )
				{
					npcAgent = m_npcAgents[ i ];
				}
				else
				{
					return false;
				}
			}
			else
			{
				npcAgent = new NPCAgent;
				m_npcAgents.PushBack( npcAgent );
			}
			npcAgent.id = id;
			npcAgent.gameObject = unit;
			npcAgent.unit = ( ( ScriptedPuppet )( unit ) );
			npcAgent.spawnedType = type;
			npcAgent.spawnedAsFallback = isFallback;
			npcAgent.isQuestNPC = NPCManager.HasTag( ( ( ScriptedPuppet )( unit ) ).GetRecordID(), 'Scripted_Patrol' );
			if( type == DynamicVehicleType.AV )
			{
				StatusEffectHelper.ApplyStatusEffectOnSelf( unit.GetGame(), T"BaseStatusEffect.MaxtacCloaked", unit.GetEntityID() );
			}
		}
		else if( unit.IsVehicle() )
		{
			if( TryGetIndexOf_Vehicle( id, i ) )
			{
				if( overrideExisting )
				{
					vehicleAgent = m_vehicleAgents[ i ];
				}
				else
				{
					return false;
				}
			}
			else
			{
				vehicleAgent = new VehicleAgent;
				m_vehicleAgents.PushBack( vehicleAgent );
			}
			vehicleAgent.id = id;
			vehicleAgent.gameObject = unit;
			vehicleAgent.unit = ( ( VehicleObject )( unit ) );
			vehicleAgent.spawnedType = type;
		}
		return true;
	}

	public function UnregisterAgent( id : EntityID ) : UnregisterResult
	{
		var i : Int32;
		var result : UnregisterResult;
		if( TryGetIndexOf_NPC( id, i ) )
		{
			result.success = true;
			result.isVehicle = false;
			result.spawnedType = m_npcAgents[ i ].spawnedType;
			m_npcAgents.Erase( i );
		}
		else if( TryGetIndexOf_Vehicle( id, i ) )
		{
			result.success = true;
			result.isVehicle = true;
			result.spawnedType = m_vehicleAgents[ i ].spawnedType;
			m_vehicleAgents.Erase( i );
		}
		return result;
	}

	public function UnregisterAll()
	{
		m_npcAgents.Clear();
		m_vehicleAgents.Clear();
	}

	public function UpdateVehiclePassengerCount( vehicleID : EntityID, passengers : Int32 )
	{
		var i : Int32;
		var vehicleAgent : VehicleAgent;
		if( TryGetIndexOf_Vehicle( vehicleID, i ) )
		{
			vehicleAgent = m_vehicleAgents[ i ];
			vehicleAgent.passangers = passengers;
			vehicleAgent.slotsAvailable = vehicleAgent.slotsTotal - passengers;
			if( passengers > 0 )
			{
				vehicleAgent.everHadPassengers = true;
			}
		}
	}

	private function TryGetIndexOf_NPC( id : EntityID, optional index : ref< Int32 > ) : Bool
	{
		var i : Int32;
		for( i = 0; i <= ( m_npcAgents.Size() - 1 ); i += 1 )
		{
			if( m_npcAgents[ i ].id == id )
			{
				index = i;
				return true;
			}
		}
		return false;
	}

	private function TryGetIndexOf_Vehicle( id : EntityID, optional index : ref< Int32 > ) : Bool
	{
		var i : Int32;
		for( i = 0; i <= ( m_vehicleAgents.Size() - 1 ); i += 1 )
		{
			if( m_vehicleAgents[ i ].id == id )
			{
				index = i;
				return true;
			}
		}
		return false;
	}

	private const function TryGetIndexOf_Ticket( id : Uint32, optional index : ref< Int32 > ) : Bool
	{
		var i : Int32;
		for( i = 0; i <= ( m_requestTickets.Size() - 1 ); i += 1 )
		{
			if( m_requestTickets[ i ].requestID == id )
			{
				index = i;
				return true;
			}
		}
		return false;
	}

	public function CreateTicket( request : Uint32, strategy : vehiclePoliceStrategy, optional isFallback : Bool )
	{
		var ticket : TicketData;
		ticket.requestID = request;
		ticket.policeStrategy = strategy;
		ticket.isFallback = isFallback;
		m_requestTickets.PushBack( ticket );
	}

	public function PopRequestTicket( request : Uint32, optional ticketData : ref< TicketData > ) : Bool
	{
		var i : Int32;
		if( !( TryGetIndexOf_Ticket( request, i ) ) )
		{
			return false;
		}
		ticketData = m_requestTickets[ i ];
		m_requestTickets.Erase( i );
		return true;
	}

}

class AgentBase
{
	var id : EntityID;
	var gameObject : weak< GameObject >;
	var spawnedType : DynamicVehicleType;
}

class NPCAgent extends AgentBase
{
	var unit : weak< ScriptedPuppet >;
	var hasBeenAttackedByPlayer : Bool;
	var isQuestNPC : Bool;
	var spawnedAsFallback : Bool;
	var markedToBeDespawned : Bool;

	public function TryGetAssignedVehicleId() : EntityID
	{
		var assignedVehicleID : EntityID;
		if( unit )
		{
			assignedVehicleID = unit.GetAIControllerComponent().GetAssignedVehicleId();
		}
		return assignedVehicleID;
	}

}

enum LifetimeStatus
{
	Base = 0,
	Near = 1,
	Disengaging = 2,
}

class VehicleAgent extends AgentBase
{
	var unit : weak< VehicleObject >;
	var passangers : Int32;
	var slotsTotal : Int32;
	var slotsReserved : Int32;
	var slotsAvailable : Int32;
	var everHadPassengers : Bool;
	default everHadPassengers = false;
	var distanceToPlayerSquared : Float;
	var lifetimeStatus : LifetimeStatus;
	var nearTimeStamp : Float;
	default nearTimeStamp = -1.f;

	public function UpdateLifetimeStatus( playerPos : Vector4 )
	{
		var now : Float;
		var unitStrong : VehicleObject;
		var vehicleStrategyDespawnDistanceSquared : Float;
		var vehicleStrategyNearDespawnDistanceSquared : Float;
		var vehicleStrategyEnterNearStateDistanceSquared : Float;
		unitStrong = unit;
		vehicleStrategyDespawnDistanceSquared = TweakDBInterface.GetFloat( T"PreventionSystem.setup.vehicleStrategyDespawnDistanceSquared", 490000.0 );
		vehicleStrategyNearDespawnDistanceSquared = TweakDBInterface.GetFloat( T"PreventionSystem.setup.vehicleStrategyNearDespawnDistanceSquared", 40000.0 );
		vehicleStrategyEnterNearStateDistanceSquared = TweakDBInterface.GetFloat( T"PreventionSystem.setup.vehicleStrategyEnterNearStateDistanceSquared", 40000.0 );
		if( !( unitStrong ) )
		{
			return;
		}
		if( spawnedType != DynamicVehicleType.Car )
		{
			return;
		}
		if( lifetimeStatus != LifetimeStatus.Disengaging )
		{
			if( unitStrong.IsDestroyed() )
			{
				Disengage();
				return;
			}
		}
		distanceToPlayerSquared = Vector4.DistanceSquared( playerPos, unitStrong.GetWorldPosition() );
		now = EngineTime.ToFloat( GameInstance.GetEngineTime( unitStrong.GetGame() ) );
		if( lifetimeStatus == LifetimeStatus.Base )
		{
			if( distanceToPlayerSquared > vehicleStrategyDespawnDistanceSquared )
			{
				Disengage();
			}
			else if( distanceToPlayerSquared < vehicleStrategyEnterNearStateDistanceSquared )
			{
				if( nearTimeStamp < 0.0 )
				{
					nearTimeStamp = now;
				}
				else if( ( now - nearTimeStamp ) > 3.0 )
				{
					lifetimeStatus = LifetimeStatus.Near;
				}
			}
			else if( nearTimeStamp > 0.0 )
			{
				nearTimeStamp = -1.0;
			}
		}
		else if( lifetimeStatus == LifetimeStatus.Near && ( distanceToPlayerSquared > vehicleStrategyNearDespawnDistanceSquared ) )
		{
			Disengage();
		}
	}

	public function Disengage()
	{
		var unitStrong : VehicleObject;
		unitStrong = unit;
		if( lifetimeStatus == LifetimeStatus.Disengaging )
		{
			return;
		}
		if( unitStrong )
		{
			GameInstance.GetPreventionSpawnSystem( unitStrong.GetGame() ).RequestDespawnVehicleAndPassengers( unitStrong );
		}
		lifetimeStatus = LifetimeStatus.Disengaging;
	}

}

struct TicketData
{
	var requestID : Uint32;
	var policeStrategy : vehiclePoliceStrategy;
	var vehicleType : DynamicVehicleType;
	var isFallback : Bool;
}

struct UnregisterResult
{
	var success : Bool;
	default success = false;
	var isVehicle : Bool;
	default isVehicle = false;
	var spawnedType : DynamicVehicleType;
	default spawnedType = DynamicVehicleType.None;
}

class IsNPCMarkedForDespawn extends AIbehaviorconditionScript
{

	protected override function Check( context : ScriptExecutionContext ) : AIbehaviorConditionOutcomes
	{
		var preventionSystem : PreventionSystem;
		var isMarkedForDespawn : Bool;
		preventionSystem = ( ( PreventionSystem )( GameInstance.GetScriptableSystemsContainer( ScriptExecutionContext.GetOwner( context ).GetGame() ).Get( PreventionSystem.GetSystemName() ) ) );
		if( !( preventionSystem ) )
		{
			return false;
		}
		if( preventionSystem.TryGetNPCMarkedForDespawnAI( ScriptExecutionContext.GetOwner( context ).GetEntityID(), isMarkedForDespawn ) )
		{
			return isMarkedForDespawn;
		}
		return false;
	}

}

class MarkNPCAgentForDespawn extends AIbehaviortaskScript
{

	protected override function Activate( context : ScriptExecutionContext )
	{
		var preventionSystem : PreventionSystem;
		preventionSystem = ( ( PreventionSystem )( GameInstance.GetScriptableSystemsContainer( ScriptExecutionContext.GetOwner( context ).GetGame() ).Get( PreventionSystem.GetSystemName() ) ) );
		if( !( preventionSystem ) )
		{
			return;
		}
		preventionSystem.TrySetNPCMarkedForDespawnAI( ScriptExecutionContext.GetOwner( context ).GetEntityID(), true );
	}

}

