enum PlayerCombatState
{
	Invalid = 0,
	InCombat = 1,
	OutOfCombat = 2,
	Stealth = 3,
}

class PlayerCombatControllerInvalidateEvent extends Event
{
	var m_state : PlayerCombatState;
}

struct PlayerCombatControllerBBListeners
{
	var m_crouchActive : CallbackHandle;
}

struct PlayerCombatControllerBlackboardListenersFunctions
{
	var m_crouchActive : CName;
}

struct PlayerCombatControllerBBIds
{
	var m_crouchActive : BlackboardDefinition;
}

struct PlayerCombatControllerBBValuesIds
{
	var m_crouchActive : BlackboardID_Int;
}

struct PlayerCombatControllerActiveFlags
{
	var m_crouchActive : Bool;
	var m_crouchTimerPassed : Bool;
	var m_isTracked : Bool;
	var m_usingJhonnyReplacer : Bool;
	var m_usingQuickHack : Bool;
}

enum PlayerCombatControllerRefreshPolicyEnum
{
	Persistent = 0,
	Eventful = 1,
}

struct PlayerCombatControllerRefreshPolicy
{
	var m_crouchActive : PlayerCombatControllerRefreshPolicyEnum;
	var m_crouchTimerPassed : PlayerCombatControllerRefreshPolicyEnum;
	var m_isTracked : PlayerCombatControllerRefreshPolicyEnum;
	var m_usingJhonnyReplacer : PlayerCombatControllerRefreshPolicyEnum;
	var m_usingQuickHack : PlayerCombatControllerRefreshPolicyEnum;
}

struct PlayerCombatControllerDelayCallbacksIds
{
	var m_crouch : DelayID;
}

struct PlayerCombatControllerOtherVars
{
	var m_state : PlayerCombatState;
}

class CrouchDelayEvent extends Event
{
}

class PlayerCombatController extends IScriptable
{

	public function RegisterOwner( owner : GameObject )
	{
		if( m_owner != NULL )
		{
			UnregisterBlackboardListeners();
		}
		m_owner = owner;
		if( owner != NULL )
		{
			InitPlayerCombatControllerRefreshPolicy();
			InitBlackboardIds();
			InitBlackboardValuesIds();
			InitBlackboardFunctions();
			RegisterBlackboardListeners();
		}
		InitOwnerVars( owner );
		VerifyActivation();
	}

	private function InitOwnerVars( owner : GameObject )
	{
		m_gameplayActiveFlags.m_usingJhonnyReplacer = owner.IsReplacer();
	}

	public function UnregisterOwner()
	{
		if( m_owner == NULL )
		{
		}
		else
		{
			UnregisterBlackboardListeners();
		}
		m_owner = NULL;
	}

	private function InitPlayerCombatControllerRefreshPolicy()
	{
		m_gameplayActiveFlagsRefreshPolicy.m_crouchActive = PlayerCombatControllerRefreshPolicyEnum.Persistent;
		m_gameplayActiveFlagsRefreshPolicy.m_isTracked = PlayerCombatControllerRefreshPolicyEnum.Persistent;
		m_gameplayActiveFlagsRefreshPolicy.m_usingJhonnyReplacer = PlayerCombatControllerRefreshPolicyEnum.Persistent;
		m_gameplayActiveFlagsRefreshPolicy.m_usingQuickHack = PlayerCombatControllerRefreshPolicyEnum.Persistent;
	}

	private function InitBlackboardIds()
	{
		m_blackboardIds.m_crouchActive = GetAllBlackboardDefs().PlayerStateMachine;
	}

	private function InitBlackboardValuesIds()
	{
		m_blackboardValuesIds.m_crouchActive = GetAllBlackboardDefs().PlayerStateMachine.Locomotion;
	}

	private function InitBlackboardFunctions()
	{
		m_blackboardListenersFunctions.m_crouchActive = 'OnCrouchActiveChanged';
	}

	private function RegisterBlackboardListeners()
	{
		var blackboardSystem : BlackboardSystem;
		var bb : IBlackboard;
		if( m_owner != NULL )
		{
			blackboardSystem = GameInstance.GetBlackboardSystem( m_owner.GetGame() );
		}
		if( blackboardSystem != NULL )
		{
			bb = blackboardSystem.GetLocalInstanced( m_owner.GetEntityID(), m_blackboardIds.m_crouchActive );
			if( bb != NULL )
			{
				m_blackboardListeners.m_crouchActive = bb.RegisterListenerInt( m_blackboardValuesIds.m_crouchActive, this, m_blackboardListenersFunctions.m_crouchActive );
			}
		}
		else
		{
		}
	}

	private function UnregisterBlackboardListeners()
	{
		var blackboardSystem : BlackboardSystem;
		blackboardSystem = GameInstance.GetBlackboardSystem( m_owner.GetGame() );
		blackboardSystem.Get( m_blackboardIds.m_crouchActive ).UnregisterListenerInt( m_blackboardValuesIds.m_crouchActive, m_blackboardListeners.m_crouchActive );
	}

	private function VerifyActivation()
	{
		if( ( m_gameplayActiveFlags.m_crouchActive && !( m_gameplayActiveFlags.m_isTracked ) ) && !( m_gameplayActiveFlags.m_usingJhonnyReplacer ) )
		{
			if( m_otherVars.m_state != PlayerCombatState.Stealth )
			{
				InvalidateActivationState( PlayerCombatState.Stealth );
			}
		}
		else
		{
			if( !( m_gameplayActiveFlags.m_crouchActive ) && !( m_gameplayActiveFlags.m_isTracked ) )
			{
				if( m_otherVars.m_state != PlayerCombatState.OutOfCombat )
				{
					InvalidateActivationState( PlayerCombatState.OutOfCombat );
				}
			}
			else
			{
				if( m_gameplayActiveFlags.m_isTracked )
				{
					if( m_otherVars.m_state != PlayerCombatState.InCombat )
					{
						InvalidateActivationState( PlayerCombatState.InCombat );
					}
				}
			}
		}
	}

	private function InvalidateActivationState( state : PlayerCombatState )
	{
		var invalidateEvent : PlayerCombatControllerInvalidateEvent;
		invalidateEvent = new PlayerCombatControllerInvalidateEvent;
		invalidateEvent.m_state = state;
		m_owner.QueueEvent( invalidateEvent );
	}

	public function OnInvalidateActiveState( evt : PlayerCombatControllerInvalidateEvent )
	{
		m_otherVars.m_state = evt.m_state;
		switch( evt.m_state )
		{
			case PlayerCombatState.InCombat:
				ActivateCombat();
			break;
			case PlayerCombatState.OutOfCombat:
				ActivateOutOfCombat();
			break;
			case PlayerCombatState.Stealth:
				ActivateStealth();
			break;
		}
		ProcessFlagsRefreshPolicy();
	}

	private function ProcessFlagsRefreshPolicy()
	{
		if( m_gameplayActiveFlagsRefreshPolicy.m_crouchActive == PlayerCombatControllerRefreshPolicyEnum.Eventful )
		{
			m_gameplayActiveFlags.m_crouchActive = false;
		}
		if( m_gameplayActiveFlagsRefreshPolicy.m_isTracked == PlayerCombatControllerRefreshPolicyEnum.Eventful )
		{
			m_gameplayActiveFlags.m_isTracked = false;
		}
		if( m_gameplayActiveFlagsRefreshPolicy.m_usingJhonnyReplacer == PlayerCombatControllerRefreshPolicyEnum.Eventful )
		{
			m_gameplayActiveFlags.m_usingJhonnyReplacer = false;
		}
		if( m_gameplayActiveFlagsRefreshPolicy.m_usingQuickHack == PlayerCombatControllerRefreshPolicyEnum.Eventful )
		{
			m_gameplayActiveFlags.m_usingQuickHack = false;
		}
	}


	private var m_gameplayActiveFlagsRefreshPolicy : PlayerCombatControllerRefreshPolicy;
	private var m_blackboardIds : PlayerCombatControllerBBIds;
	private var m_blackboardValuesIds : PlayerCombatControllerBBValuesIds;
	private var m_blackboardListenersFunctions : PlayerCombatControllerBlackboardListenersFunctions;
	private var m_blackboardListeners : PlayerCombatControllerBBListeners;
	private var m_delayEventsIds : PlayerCombatControllerDelayCallbacksIds;
	private var m_gameplayActiveFlags : PlayerCombatControllerActiveFlags;
	private var m_otherVars : PlayerCombatControllerOtherVars;
	private var m_owner : weak< GameObject >;

	private export function OnCrouchActiveChanged( value : Int32 )
	{
		var crouchDelayEvent : CrouchDelayEvent;
		var active : Bool;
		var factVal : Int32;
		crouchDelayEvent = new CrouchDelayEvent;
		factVal = GetFact( m_owner.GetGame(), 'SniperNestSaveHack' );
		if( factVal > 0 )
		{
			return;
		}
		if( value == ( ( Int32 )( gamePSMLocomotionStates.Crouch ) ) )
		{
			active = true;
		}
		else
		{
			active = false;
		}
		if( m_gameplayActiveFlags.m_crouchActive != active )
		{
			m_gameplayActiveFlags.m_crouchActive = active;
			if( active )
			{
				m_delayEventsIds.m_crouch = GameInstance.GetDelaySystem( m_owner.GetGame() ).DelayEvent( m_owner, crouchDelayEvent, 0.15000001 );
			}
			else
			{
				GameInstance.GetDelaySystem( m_owner.GetGame() ).CancelCallback( m_delayEventsIds.m_crouch );
				m_gameplayActiveFlags.m_crouchTimerPassed = false;
				VerifyActivation();
			}
		}
	}

	public function OnStartedBeingTrackedAsHostile( evt : StartedBeingTrackedAsHostile )
	{
		m_gameplayActiveFlags.m_isTracked = true;
		VerifyActivation();
	}

	public function OnStoppedBeingTrackedAsHostile()
	{
		m_gameplayActiveFlags.m_isTracked = false;
		VerifyActivation();
	}

	public function OnCrouchDelayEvent( evt : CrouchDelayEvent )
	{
		if( m_gameplayActiveFlags.m_crouchTimerPassed != true && m_gameplayActiveFlags.m_crouchActive )
		{
			m_gameplayActiveFlags.m_crouchTimerPassed = true;
			VerifyActivation();
		}
	}

	public function OnStatusEffectApplied( evt : ApplyStatusEffectEvent, gameplayTags : ref< array< CName > > )
	{
		if( gameplayTags.Contains( 'Quickhack' ) )
		{
			if( m_gameplayActiveFlags.m_usingQuickHack != true )
			{
				m_gameplayActiveFlags.m_usingQuickHack = true;
				VerifyActivation();
			}
		}
	}

	public function OnStatusEffectRemoved( evt : RemoveStatusEffect, gameplayTags : ref< array< CName > > )
	{
		if( gameplayTags.Contains( 'Quickhack' ) )
		{
			if( m_gameplayActiveFlags.m_usingQuickHack != false )
			{
				m_gameplayActiveFlags.m_usingQuickHack = false;
				VerifyActivation();
			}
		}
	}

	private function SetBlackboardIntVariable( id : BlackboardID_Int, value : Int32 )
	{
		var blackboardSystem : BlackboardSystem;
		var blackboard : IBlackboard;
		blackboardSystem = GameInstance.GetBlackboardSystem( m_owner.GetGame() );
		blackboard = blackboardSystem.GetLocalInstanced( m_owner.GetEntityID(), GetAllBlackboardDefs().PlayerStateMachine );
		return blackboard.SetInt( id, value );
	}

	public const function GetBoolFromQuestDB( varName : CName ) : Bool
	{
		return GameInstance.GetQuestsSystem( m_owner.GetGame() ).GetFact( varName ) != 0;
	}

	private function SendAnimFeatureData( inCombat : Bool )
	{
		var animFeature : AnimFeature_CombatState;
		var evt : AnimInputSetterAnimFeature;
		animFeature = new AnimFeature_CombatState;
		evt = new AnimInputSetterAnimFeature;
		animFeature.isInCombat = inCombat;
		evt.key = 'CombatData';
		evt.value = animFeature;
		m_owner.QueueEvent( evt );
	}

	private function TutorialSetFact( factName : CName )
	{
		var questSystem : QuestsSystem;
		questSystem = GameInstance.GetQuestsSystem( m_owner.GetGame() );
		if( ( questSystem.GetFact( factName ) == 0 ) && ( questSystem.GetFact( 'disable_tutorials' ) == 0 ) )
		{
			questSystem.SetFact( factName, 1 );
		}
	}

	private const function IsRightHandInUnequippedState() : Bool
	{
		return GameInstance.GetTransactionSystem( m_owner.GetGame() ).IsSlotEmpty( m_owner, T"RightHand" );
	}

	private function ActivateCombat()
	{
		SetBlackboardIntVariable( GetAllBlackboardDefs().PlayerStateMachine.Combat, ( ( Int32 )( gamePSMCombat.InCombat ) ) );
		SendAnimFeatureData( true );
		PlayerPuppet.ReevaluateAllBreathingEffects( ( ( PlayerPuppet )( m_owner ) ) );
		if( ( !( IsMultiplayer() ) && !( GetFact( m_owner.GetGame(), 'story_mode' ) ) ) && !( m_owner.IsReplacer() ) )
		{
			GameInstance.GetStatPoolsSystem( m_owner.GetGame() ).RequestSettingModifierWithRecord( m_owner.GetEntityID(), gamedataStatPoolType.Health, gameStatPoolModificationTypes.Regeneration, T"BaseStatPools.PlayerBaseInCombatHealthRegen" );
		}
		FastTravelSystem.AddFastTravelLock( 'InCombat', m_owner.GetGame() );
		ChatterHelper.TryPlayEnterCombatChatter( m_owner );
		GameInstance.GetAudioSystem( m_owner.GetGame() ).NotifyGameTone( 'EnterCombat' );
		GameInstance.GetAudioSystem( m_owner.GetGame() ).HandleCombatMix( m_owner );
		if( ( !( GetBoolFromQuestDB( 'block_combat_scripts_tutorials' ) ) && IsRightHandInUnequippedState() ) && !( GetBoolFromQuestDB( 'disable_tutorials' ) ) )
		{
			TutorialSetFact( 'combat_tutorial' );
		}
		GameObjectEffectHelper.BreakEffectLoopEvent( m_owner, 'stealth_mode' );
	}

	private function ActivateStealth()
	{
		if( m_gameplayActiveFlags.m_crouchActive )
		{
			GameObjectEffectHelper.StartEffectEvent( m_owner, 'stealth_mode', false );
			SetBlackboardIntVariable( GetAllBlackboardDefs().PlayerStateMachine.Combat, ( ( Int32 )( gamePSMCombat.Stealth ) ) );
		}
		else
		{
			VerifyActivation();
		}
	}

	private function ActivateOutOfCombat()
	{
		SetBlackboardIntVariable( GetAllBlackboardDefs().PlayerStateMachine.Combat, ( ( Int32 )( gamePSMCombat.OutOfCombat ) ) );
		SendAnimFeatureData( false );
		PlayerPuppet.ReevaluateAllBreathingEffects( ( ( PlayerPuppet )( m_owner ) ) );
		if( !( IsMultiplayer() ) && ScriptedPuppet.IsActive( m_owner ) )
		{
			GameInstance.GetStatPoolsSystem( m_owner.GetGame() ).RequestSettingModifierWithRecord( m_owner.GetEntityID(), gamedataStatPoolType.Health, gameStatPoolModificationTypes.Regeneration, T"BaseStatPools.PlayerBaseOutOfCombatHealthRegen" );
		}
		ChatterHelper.TryPlayLeaveCombatChatter( m_owner );
		GameInstance.GetAudioSystem( m_owner.GetGame() ).NotifyGameTone( 'LeaveCombat' );
		GameInstance.GetAudioSystem( m_owner.GetGame() ).HandleOutOfCombatMix( m_owner );
		FastTravelSystem.RemoveFastTravelLock( 'InCombat', m_owner.GetGame() );
		GameObjectEffectHelper.BreakEffectLoopEvent( m_owner, 'stealth_mode' );
	}

}

