class SubCharacterSystem extends ScriptableSystem
{
	private persistent var m_uniqueSubCharacters : array< SSubCharacter >;
	private var m_scriptSpawnedFlathead : Bool;
	private var m_isDespawningFlathead : Bool;

	private export override function OnAttach() {}

	private export override function OnRestored( saveVersion : Int32, gameVersion : Int32 ) {}

	private function AddSubCharacter( character : ScriptedPuppet )
	{
		var subCharacter : SSubCharacter;
		var equipmentData : EquipmentSystemPlayerData;
		var i : Int32;
		var subCharType : gamedataSubCharacter;
		var startingEquipment : array< weak< Item_Record > >;
		var equipRequest : EquipRequest;
		var itemID : ItemID;
		subCharType = TweakDBInterface.GetSubCharacterRecord( character.GetRecordID() ).Type();
		if( subCharType == gamedataSubCharacter.Flathead )
		{
			if( IsFlatheadFollowing() )
			{
				if( m_scriptSpawnedFlathead )
				{
					m_isDespawningFlathead = true;
					GameInstance.GetCompanionSystem( GetGameInstance() ).DespawnSubcharacter( T"Character.spiderbot_new" );
				}
				else
				{
					m_scriptSpawnedFlathead = false;
				}
			}
		}
		if( !( SubCharacterExists( subCharType ) ) || m_isDespawningFlathead )
		{
			subCharacter.persistentID = character.GetPersistentID();
			subCharacter.subCharType = subCharType;
			equipmentData = new EquipmentSystemPlayerData;
			equipmentData.SetOwner( character );
			equipmentData.OnInitialize();
			subCharacter.equipmentData = equipmentData;
			m_uniqueSubCharacters.PushBack( subCharacter );
			TweakDBInterface.GetSubCharacterRecord( character.GetRecordID() ).StartingEquippedItems( startingEquipment );
			for( i = 0; i < startingEquipment.Size(); i += 1 )
			{
				itemID = ItemID.CreateQuery( startingEquipment[ i ].GetID() );
				equipRequest = new EquipRequest;
				equipRequest.itemID = itemID;
				equipmentData.OnEquipRequest( equipRequest );
			}
		}
		else
		{
			m_uniqueSubCharacters[ GetSubCharacterIndex( subCharType ) ].equipmentData.SetOwner( character );
			for( i = 0; i < m_uniqueSubCharacters.Size(); i += 1 )
			{
				m_uniqueSubCharacters[ i ].equipmentData.OnRestored();
			}
		}
		if( subCharType == gamedataSubCharacter.Flathead )
		{
			AddFlathead();
		}
	}

	private function RemoveSubCharacter( subCharType : gamedataSubCharacter )
	{
		if( !( SubCharacterExists( subCharType ) ) )
		{
		}
		m_uniqueSubCharacters.Erase( GetSubCharacterIndex( subCharType ) );
		if( subCharType == gamedataSubCharacter.Flathead )
		{
			RemoveFlathead();
			m_isDespawningFlathead = false;
		}
	}

	private function AddFlathead()
	{
		var followerRole : AIFollowerRole;
		var autocraftSystem : AutocraftSystem;
		var autocraftActivateRequest : AutocraftActivateRequest;
		followerRole = new AIFollowerRole;
		followerRole.SetFollowTarget( GameInstance.GetPlayerSystem( GetGameInstance() ).GetLocalPlayerMainGameObject() );
		AIHumanComponent.SetCurrentRole( GetFlathead(), followerRole );
		autocraftSystem = ( ( AutocraftSystem )( GameInstance.GetScriptableSystemsContainer( GetGameInstance() ).Get( 'AutocraftSystem' ) ) );
		autocraftActivateRequest = new AutocraftActivateRequest;
		autocraftSystem.QueueRequest( autocraftActivateRequest );
		ShowFlatheadUI( true );
	}

	private function RemoveFlathead()
	{
		var autocraftSystem : AutocraftSystem;
		var autocraftDeactivateRequest : AutocraftDeactivateRequest;
		autocraftSystem = ( ( AutocraftSystem )( GameInstance.GetScriptableSystemsContainer( GetGameInstance() ).Get( 'AutocraftSystem' ) ) );
		autocraftDeactivateRequest = new AutocraftDeactivateRequest;
		autocraftDeactivateRequest.resetMemory = true;
		autocraftSystem.QueueRequest( autocraftDeactivateRequest );
		ShowFlatheadUI( false );
	}

	private function ShowFlatheadUI( value : Bool )
	{
		var bbCompanion : UI_CompanionDef;
		bbCompanion = GetAllBlackboardDefs().UI_Companion;
		GameInstance.GetBlackboardSystem( GetGameInstance() ).Get( bbCompanion ).SetBool( bbCompanion.flatHeadSpawned, value, true );
	}

	private const function SubCharacterExists( subCharType : gamedataSubCharacter ) : Bool
	{
		return GetSubCharacterIndex( subCharType ) >= 0;
	}

	private const function GetSubCharacterIndex( subCharType : gamedataSubCharacter ) : Int32
	{
		var i : Int32;
		for( i = 0; i < m_uniqueSubCharacters.Size(); i += 1 )
		{
			if( m_uniqueSubCharacters[ i ].subCharType == subCharType )
			{
				return i;
			}
		}
		return -1;
	}

	public const function GetAllSubCharacters() : array< SSubCharacter >
	{
		return m_uniqueSubCharacters;
	}

	public const function GetSubCharacterPuppet( subCharType : gamedataSubCharacter ) : weak< ScriptedPuppet >
	{
		if( SubCharacterExists( subCharType ) )
		{
			return m_uniqueSubCharacters[ GetSubCharacterIndex( subCharType ) ].equipmentData.GetOwner();
		}
		return NULL;
	}

	public const function GetSubCharacterPersistentID( subCharType : gamedataSubCharacter ) : PersistentID
	{
		var persistentID : PersistentID;
		if( SubCharacterExists( subCharType ) )
		{
			persistentID = m_uniqueSubCharacters[ GetSubCharacterIndex( subCharType ) ].persistentID;
		}
		return persistentID;
	}

	public const function GetSubCharacterEquipment( subCharType : gamedataSubCharacter ) : EquipmentSystemPlayerData
	{
		var equipmentData : EquipmentSystemPlayerData;
		if( SubCharacterExists( subCharType ) )
		{
			equipmentData = m_uniqueSubCharacters[ GetSubCharacterIndex( subCharType ) ].equipmentData;
		}
		return equipmentData;
	}

	public const function GetFlathead() : weak< ScriptedPuppet >
	{
		return GetSubCharacterPuppet( gamedataSubCharacter.Flathead );
	}

	public const function GetFlatheadPersistentID() : PersistentID
	{
		return GetSubCharacterPersistentID( gamedataSubCharacter.Flathead );
	}

	public const function GetFlatheadEquipment() : weak< EquipmentSystemPlayerData >
	{
		return GetSubCharacterEquipment( gamedataSubCharacter.Flathead );
	}

	public const function IsFlatheadFollowing() : Bool
	{
		return GetFlathead() != NULL;
	}

	public static function GetInstance( gameInstance : GameInstance ) : SubCharacterSystem
	{
		return ( ( SubCharacterSystem )( GameInstance.GetScriptableSystemsContainer( gameInstance ).Get( 'SubCharacterSystem' ) ) );
	}

	public static function IsFlathead( object : weak< GameObject > ) : Bool
	{
		var puppet : weak< ScriptedPuppet >;
		puppet = ( ( ScriptedPuppet )( object ) );
		if( puppet )
		{
			return puppet.GetRecordID() == T"Character.spiderbot_new";
		}
		return false;
	}

	public static function IsSubCharacterSpawned( gameInstance : GameInstance, optional characterID : TweakDBID ) : Bool
	{
		var subCharSys : SubCharacterSystem;
		var entities : array< weak< Entity > >;
		if( !( GameInstance.IsValid( gameInstance ) ) )
		{
			return false;
		}
		subCharSys = GetInstance( gameInstance );
		if( !( subCharSys ) )
		{
			return false;
		}
		GameInstance.GetCompanionSystem( gameInstance ).GetSpawnedEntities( entities, characterID );
		if( entities.Size() > 0 )
		{
			return true;
		}
		return false;
	}

	private export function OnAddSubCharacterRequest( request : AddSubCharacterRequest )
	{
		AddSubCharacter( request.subCharObject );
	}

	private function OnRemoveSubCharacterRequest( request : RemoveSubCharacterRequest )
	{
		RemoveSubCharacter( request.subCharType );
	}

	private function OnSubCharEquipRequest( request : SubCharEquipRequest )
	{
		if( SubCharacterExists( request.subCharType ) )
		{
			m_uniqueSubCharacters[ GetSubCharacterIndex( request.subCharType ) ].equipmentData.OnEquipRequest( ( ( EquipRequest )( request ) ) );
		}
	}

	private function OnSubCharEquipRequest( request : SubCharUnequipRequest )
	{
		if( SubCharacterExists( request.subCharType ) )
		{
			m_uniqueSubCharacters[ GetSubCharacterIndex( request.subCharType ) ].equipmentData.OnUnequipRequest( ( ( UnequipRequest )( request ) ) );
		}
	}

	private function OnSpawnUniqueSubCharacterRequest( request : SpawnUniqueSubCharacterRequest )
	{
		var subCharRecord : weak< SubCharacter_Record >;
		var subCharType : gamedataSubCharacter;
		var heading : Vector4;
		var offsetDir : Vector3;
		subCharRecord = TweakDBInterface.GetSubCharacterRecord( request.subCharacterID );
		if( !( subCharRecord ) )
		{
			return;
		}
		subCharType = subCharRecord.Type();
		if( SubCharacterExists( subCharType ) )
		{
			return;
		}
		heading = -1.0 * GameInstance.GetPlayerSystem( GetGameInstance() ).GetLocalPlayerMainGameObject().GetWorldForward();
		offsetDir = Vector3( heading.X, heading.Y, heading.Z );
		GameInstance.GetCompanionSystem( GetGameInstance() ).SpawnSubcharacter( request.subCharacterID, request.desiredDistance, offsetDir );
		if( request.subCharacterID == T"Character.spiderbot_new" )
		{
			m_scriptSpawnedFlathead = true;
		}
	}

	private function OnSpawnUniquePursuitSubCharacterRequest( request : SpawnUniquePursuitSubCharacterRequest )
	{
		var subCharRecord : weak< SubCharacter_Record >;
		subCharRecord = TweakDBInterface.GetSubCharacterRecord( request.subCharacterID );
		if( !( subCharRecord ) )
		{
			return;
		}
		GameInstance.GetCompanionSystem( GetGameInstance() ).SpawnSubcharacterOnPosition( request.subCharacterID, Vector4.Vector4To3( request.position ) );
	}

	private function OnDespawnUniqueSubCharacterRequest( request : DespawnUniqueSubCharacterRequest )
	{
		var subCharRecord : weak< SubCharacter_Record >;
		var subCharType : gamedataSubCharacter;
		subCharRecord = TweakDBInterface.GetSubCharacterRecord( request.subCharacterID );
		if( !( subCharRecord ) )
		{
			return;
		}
		subCharType = subCharRecord.Type();
		if( !( SubCharacterExists( subCharType ) ) )
		{
			return;
		}
		GameInstance.GetCompanionSystem( GetGameInstance() ).DespawnSubcharacter( request.subCharacterID );
	}

	private function OnSpawnSubCharacterRequest( request : SpawnSubCharacterRequest )
	{
		var subCharRecord : weak< SubCharacter_Record >;
		var heading : Vector4;
		var offsetDir : Vector3;
		subCharRecord = TweakDBInterface.GetSubCharacterRecord( request.subCharacterID );
		if( !( subCharRecord ) )
		{
			return;
		}
		heading = -1.0 * GameInstance.GetPlayerSystem( GetGameInstance() ).GetLocalPlayerMainGameObject().GetWorldForward();
		offsetDir = Vector3( heading.X, heading.Y, heading.Z );
		GameInstance.GetCompanionSystem( GetGameInstance() ).SpawnSubcharacter( request.subCharacterID, request.desiredDistance, offsetDir );
		if( request.subCharacterID == T"Character.spiderbot_new" )
		{
			m_scriptSpawnedFlathead = true;
		}
	}

	private function OnDespawnSubCharacterRequest( request : DespawnSubCharacterRequest )
	{
		var subCharRecord : weak< SubCharacter_Record >;
		subCharRecord = TweakDBInterface.GetSubCharacterRecord( request.subCharacterID );
		if( !( subCharRecord ) )
		{
			return;
		}
		GameInstance.GetCompanionSystem( GetGameInstance() ).DespawnSubcharacter( request.subCharacterID );
	}

	public static function DespawnRequest( gameInstance : GameInstance, optional characterID : TweakDBID ) : Bool
	{
		var entities : array< weak< Entity > >;
		var i : Int32;
		var npc : NPCPuppet;
		if( !( GameInstance.IsValid( gameInstance ) ) )
		{
			return false;
		}
		GameInstance.GetCompanionSystem( gameInstance ).GetSpawnedEntities( entities, characterID );
		for( i = 0; i < entities.Size(); i += 1 )
		{
			npc = ( ( NPCPuppet )( entities[ i ] ) );
			if( npc )
			{
				npc.QueueEvent( new SmartDespawnRequest );
			}
		}
		if( entities.Size() > 0 )
		{
			return true;
		}
		return false;
	}

	public static function CancelDespawnRequest( gameInstance : GameInstance, optional characterID : TweakDBID ) : Bool
	{
		var entities : array< weak< Entity > >;
		var i : Int32;
		var npc : NPCPuppet;
		if( !( GameInstance.IsValid( gameInstance ) ) )
		{
			return false;
		}
		GameInstance.GetCompanionSystem( gameInstance ).GetSpawnedEntities( entities, characterID );
		for( i = 0; i < entities.Size(); i += 1 )
		{
			npc = ( ( NPCPuppet )( entities[ i ] ) );
			if( npc )
			{
				npc.QueueEvent( new CancelSmartDespawnRequest );
			}
		}
		if( entities.Size() > 0 )
		{
			return true;
		}
		return false;
	}

}

class AddSubCharacterRequest extends ScriptableSystemRequest
{
	var subCharObject : weak< ScriptedPuppet >;
}

class RemoveSubCharacterRequest extends ScriptableSystemRequest
{
	var subCharType : gamedataSubCharacter;
}

class SubCharEquipRequest extends EquipRequest
{
	var subCharType : gamedataSubCharacter;
}

class SubCharUnequipRequest extends UnequipRequest
{
	var subCharType : gamedataSubCharacter;
}

class ClearSubCharacterRequest extends ScriptableSystemRequest
{
}

class SpawnUniqueSubCharacterRequest extends ScriptableSystemRequest
{
	var subCharacterID : TweakDBID;
	var desiredDistance : Float;
	default desiredDistance = 3f;
}

class DespawnUniqueSubCharacterRequest extends ScriptableSystemRequest
{
	var subCharacterID : TweakDBID;
}

class SpawnSubCharacterRequest extends SpawnUniqueSubCharacterRequest
{
	default desiredDistance = 3f;
}

class DespawnSubCharacterRequest extends DespawnUniqueSubCharacterRequest
{
}

class SpawnUniquePursuitSubCharacterRequest extends ScriptableSystemRequest
{
	var subCharacterID : TweakDBID;
	var position : Vector4;
}

struct SSubCharacter
{
	persistent var persistentID : PersistentID;
	persistent var subCharType : gamedataSubCharacter;
	persistent var equipmentData : EquipmentSystemPlayerData;
}

