class TargetHitIndicatorGameController extends inkGameController
{
	private var m_currentAnim : inkAnimProxy;
	private var m_bonusAnim : inkAnimProxy;
	private var m_currentAnimWidget : weak< inkWidget >;
	private var m_currentPriority : Int32;
	private var m_currentController : weak< TargetHitIndicatorLogicController >;
	private var m_damageController : weak< TargetHitIndicatorLogicController >;
	private var m_defeatController : weak< TargetHitIndicatorLogicController >;
	private var m_killController : weak< TargetHitIndicatorLogicController >;
	private var m_bonusController : weak< TargetHitIndicatorLogicController >;
	private var m_damageListBlackboardId : CallbackHandle;
	private var m_killListBlackboardId : CallbackHandle;
	private var m_indicatorEnabledBlackboardId : CallbackHandle;
	private var m_weaponSwayBlackboardId : CallbackHandle;
	private var m_weaponChangedListener : AttachmentSlotsScriptListener;
	private var m_aimingStatusBlackboardId : CallbackHandle;
	private var m_zoomLevelBlackboardId : CallbackHandle;
	private var m_realOwner : weak< GameObject >;
	private var m_hitIndicatorEnabled : Bool;
	private var m_entityHit : weak< GameObject >;
	private var m_rootWidget : weak< inkWidget >;
	private var m_player : weak< PlayerPuppet >;
	private var m_currentSway : Vector2;
	var m_currentWeaponZoom : Float;
	var m_weaponZoomNeedsUpdate : Bool;
	private var m_currentZoomLevel : Float;
	private var m_weaponZoomListener : HitIndicatorWeaponZoomListener;
	private var m_weaponID : StatsObjectID;
	private var m_isAimingDownSights : Bool;
	private var m_uiSystem : UISystem;

	protected event OnInitialize()
	{
		var damageInfoBB : IBlackboard;
		var weaponInfoBB : IBlackboard;
		m_rootWidget = GetRootWidget();
		m_realOwner = ( ( GameObject )( GetOwnerEntity() ) );
		m_damageController = SpawnIndicator( 'Damage' );
		m_defeatController = SpawnIndicator( 'Defeat' );
		m_killController = SpawnIndicator( 'Kill' );
		m_bonusController = SpawnIndicator( 'Bonus' );
		damageInfoBB = GetBlackboardSystem().Get( GetAllBlackboardDefs().UI_DamageInfo );
		m_damageListBlackboardId = damageInfoBB.RegisterListenerVariant( GetAllBlackboardDefs().UI_DamageInfo.DamageList, this, 'OnDamageAdded' );
		m_killListBlackboardId = damageInfoBB.RegisterListenerVariant( GetAllBlackboardDefs().UI_DamageInfo.KillList, this, 'OnKillAdded' );
		m_indicatorEnabledBlackboardId = damageInfoBB.RegisterListenerBool( GetAllBlackboardDefs().UI_DamageInfo.HitIndicatorEnabled, this, 'OnHitIndicatorEnabledChanged' );
		m_hitIndicatorEnabled = damageInfoBB.GetBool( GetAllBlackboardDefs().UI_DamageInfo.HitIndicatorEnabled );
		weaponInfoBB = GetBlackboardSystem().Get( GetAllBlackboardDefs().UIGameData );
		m_weaponSwayBlackboardId = weaponInfoBB.RegisterListenerVector2( GetAllBlackboardDefs().UIGameData.WeaponSway, this, 'OnSway' );
	}

	protected event OnPlayerAttach( player : GameObject )
	{
		var playerStateMachineBB : IBlackboard;
		var stats : StatsSystem;
		var weapon : WeaponObject;
		m_realOwner = player;
		m_player = ( ( PlayerPuppet )( player ) );
		if( m_player )
		{
			playerStateMachineBB = m_player.GetPlayerStateMachineBlackboard();
			m_aimingStatusBlackboardId = playerStateMachineBB.RegisterListenerInt( GetAllBlackboardDefs().PlayerStateMachine.UpperBody, this, 'OnAimStatusChange' );
			m_zoomLevelBlackboardId = playerStateMachineBB.RegisterListenerFloat( GetAllBlackboardDefs().PlayerStateMachine.ZoomLevel, this, 'OnZoomLevelChange' );
			m_isAimingDownSights = playerStateMachineBB.GetInt( GetAllBlackboardDefs().PlayerStateMachine.UpperBody ) == ( ( Int32 )( gamePSMUpperBodyStates.Aim ) );
			if( !( m_weaponZoomListener ) )
			{
				m_weaponZoomListener = new HitIndicatorWeaponZoomListener;
				m_weaponZoomListener.m_gameController = this;
			}
			stats = GameInstance.GetStatsSystem( m_player.GetGame() );
			weapon = ( ( WeaponObject )( GameInstance.GetTransactionSystem( m_player.GetGame() ).GetItemInSlot( m_player, T"AttachmentSlots.WeaponRight" ) ) );
			if( weapon )
			{
				m_weaponID = weapon.GetItemData().GetStatsObjectID();
				m_weaponZoomListener.SetStatType( gamedataStatType.ZoomLevel );
				stats.RegisterListener( m_weaponID, m_weaponZoomListener );
				m_weaponZoomNeedsUpdate = true;
			}
			RegisterWeaponChangedListener();
		}
		m_uiSystem = GameInstance.GetUISystem( m_player.GetGame() );
	}

	protected event OnUninitialize()
	{
		var damageInfoBB : IBlackboard;
		var weaponInfoBB : IBlackboard;
		damageInfoBB = GetBlackboardSystem().Get( GetAllBlackboardDefs().UI_DamageInfo );
		if( m_damageListBlackboardId )
		{
			damageInfoBB.UnregisterListenerVariant( GetAllBlackboardDefs().UI_DamageInfo.DamageList, m_damageListBlackboardId );
		}
		if( m_killListBlackboardId )
		{
			damageInfoBB.UnregisterListenerVariant( GetAllBlackboardDefs().UI_DamageInfo.KillList, m_killListBlackboardId );
		}
		if( m_indicatorEnabledBlackboardId )
		{
			damageInfoBB.UnregisterListenerBool( GetAllBlackboardDefs().UI_DamageInfo.HitIndicatorEnabled, m_indicatorEnabledBlackboardId );
		}
		weaponInfoBB = GetBlackboardSystem().Get( GetAllBlackboardDefs().UIGameData );
		if( m_weaponSwayBlackboardId )
		{
			weaponInfoBB.UnregisterListenerVector2( GetAllBlackboardDefs().UIGameData.WeaponSway, m_weaponSwayBlackboardId );
		}
	}

	protected event OnPlayerDetach( player : GameObject )
	{
		var playerStateMachineBB : IBlackboard;
		if( m_player )
		{
			playerStateMachineBB = m_player.GetPlayerStateMachineBlackboard();
			if( m_aimingStatusBlackboardId )
			{
				playerStateMachineBB.UnregisterListenerInt( GetAllBlackboardDefs().PlayerStateMachine.UpperBody, m_aimingStatusBlackboardId );
			}
			if( m_zoomLevelBlackboardId )
			{
				playerStateMachineBB.UnregisterListenerFloat( GetAllBlackboardDefs().PlayerStateMachine.ZoomLevel, m_zoomLevelBlackboardId );
			}
			GameInstance.GetStatsSystem( m_player.GetGame() ).UnregisterListener( m_weaponID, m_weaponZoomListener );
			UnregisterWeaponChangedListener();
			m_player = NULL;
		}
	}

	private function RegisterWeaponChangedListener()
	{
		var puppetListener : WeaponChangedListener;
		puppetListener = new WeaponChangedListener;
		puppetListener.m_gameController = this;
		m_weaponChangedListener = GameInstance.GetTransactionSystem( m_player.GetGame() ).RegisterAttachmentSlotListener( m_player, puppetListener );
	}

	private function UnregisterWeaponChangedListener()
	{
		if( m_weaponChangedListener )
		{
			GameInstance.GetTransactionSystem( m_player.GetGame() ).UnregisterAttachmentSlotListener( m_player, m_weaponChangedListener );
			m_weaponChangedListener = NULL;
		}
	}

	private function SpawnIndicator( type : CName ) : TargetHitIndicatorLogicController
	{
		var newInkWidget : inkWidget;
		newInkWidget = ( ( inkWidget )( SpawnFromLocal( GetRootWidget(), type ) ) );
		newInkWidget.SetAnchor( inkEAnchor.Centered );
		newInkWidget.SetAnchorPoint( 0.5, 0.5 );
		return ( ( TargetHitIndicatorLogicController )( newInkWidget.GetController() ) );
	}

	protected event OnDamageAdded( value : Variant )
	{
		var damageList : array< DamageInfo >;
		var i : Int32;
		var hitEntity : weak< GameObject >;
		damageList = ( ( array< DamageInfo > )value );
		for( i = 0; i < damageList.Size(); i += 1 )
		{
			if( ( ( damageList[ i ].entityHit != NULL ) && ( m_realOwner == damageList[ i ].instigator ) ) && ShouldShowDamage( damageList[ i ] ) )
			{
				if( ShouldShowBonus( damageList[ i ] ) )
				{
					ShowBonus();
				}
				hitEntity = damageList[ i ].entityHit;
				break;
			}
		}
		if( hitEntity != NULL )
		{
			Show( hitEntity, false );
		}
	}

	protected event OnKillAdded( value : Variant )
	{
		var killList : array< KillInfo >;
		var i : Int32;
		var killedEntity : weak< GameObject >;
		var killType : gameKillType;
		killList = ( ( array< KillInfo > )value );
		for( i = 0; i < killList.Size(); i += 1 )
		{
			if( ( killList[ i ].victimEntity != NULL ) && ( m_realOwner == killList[ i ].killerEntity ) )
			{
				killedEntity = killList[ i ].victimEntity;
				killType = killList[ i ].killType;
				Show( killedEntity, true, killType );
			}
		}
	}

	private function ShouldShowDamage( const damageInfo : ref< DamageInfo > ) : Bool
	{
		var i : Int32;
		if( damageInfo.damageValue == 0.0 )
		{
			return false;
		}
		for( i = 0; i < damageInfo.userData.flags.Size(); i += 1 )
		{
			if( ( damageInfo.userData.flags[ i ].flag == hitFlag.ImmortalTarget || damageInfo.userData.flags[ i ].flag == hitFlag.DealNoDamage ) || damageInfo.userData.flags[ i ].flag == hitFlag.DontShowDamageFloater )
			{
				return false;
			}
		}
		return true;
	}

	private function ShouldShowBonus( const damageInfo : ref< DamageInfo > ) : Bool
	{
		var i : Int32;
		for( i = 0; i < damageInfo.userData.flags.Size(); i += 1 )
		{
			if( damageInfo.userData.flags[ i ].flag == hitFlag.Headshot || damageInfo.userData.flags[ i ].flag == hitFlag.WeakspotHit )
			{
				return true;
			}
		}
		return false;
	}

	private function Show( entity : weak< GameObject >, isDead : Bool, optional killType : gameKillType )
	{
		if( m_hitIndicatorEnabled )
		{
			if( isDead )
			{
				if( killType == gameKillType.Normal )
				{
					m_currentController = m_killController;
				}
				else if( killType == gameKillType.Defeat )
				{
					m_currentController = m_defeatController;
				}
			}
			else
			{
				m_currentController = m_damageController;
			}
			m_entityHit = entity;
			if( m_currentPriority <= m_currentController.m_animationPriority )
			{
				if( m_currentAnim && m_currentAnim.IsPlaying() )
				{
					m_currentAnimWidget.SetVisible( false );
					m_currentAnim.UnregisterFromCallback( inkanimEventType.OnFinish, this, 'OnAnimFinished' );
					m_currentAnim.Stop();
				}
				PlayAnimation();
			}
		}
	}

	public function PlayAnimation()
	{
		m_currentAnimWidget = m_currentController.GetRootWidget();
		m_currentAnimWidget.SetOpacity( 1.0 );
		m_currentAnimWidget.SetVisible( true );
		m_currentAnim = m_currentController.PlayLibraryAnimation( m_currentController.m_animName );
		m_currentAnim.RegisterToCallback( inkanimEventType.OnFinish, this, 'OnAnimFinished' );
		m_currentPriority = m_currentController.m_animationPriority;
	}

	protected event OnAnimFinished( anim : inkAnimProxy )
	{
		m_currentAnimWidget.SetVisible( false );
		m_currentAnim.UnregisterFromCallback( inkanimEventType.OnFinish, this, 'OnAnimFinished' );
		m_currentPriority = 0;
	}

	private function ShowBonus()
	{
		var bonusAnimWidget : weak< inkWidget >;
		if( m_hitIndicatorEnabled )
		{
			if( m_bonusAnim && m_bonusAnim.IsPlaying() )
			{
				m_bonusAnim.UnregisterFromCallback( inkanimEventType.OnFinish, this, 'OnBonusAnimFinished' );
				m_bonusAnim.Stop();
			}
			bonusAnimWidget = m_bonusController.GetRootWidget();
			bonusAnimWidget.SetOpacity( 1.0 );
			bonusAnimWidget.SetVisible( true );
			m_bonusAnim = m_bonusController.PlayLibraryAnimation( m_bonusController.m_animName );
			m_bonusAnim.RegisterToCallback( inkanimEventType.OnFinish, this, 'OnBonusAnimFinished' );
		}
	}

	protected event OnBonusAnimFinished( anim : inkAnimProxy )
	{
		m_bonusController.GetRootWidget().SetVisible( false );
		m_bonusAnim.UnregisterFromCallback( inkanimEventType.OnFinish, this, 'OnBonusAnimFinished' );
	}

	protected event OnSway( pos : Vector2 )
	{
		m_currentSway = pos;
		UpdateWidgetPosition();
	}

	protected event OnAimStatusChange( value : Int32 )
	{
		m_isAimingDownSights = value == ( ( Int32 )( gamePSMUpperBodyStates.Aim ) );
		if( m_player && m_weaponZoomNeedsUpdate )
		{
			m_currentWeaponZoom = GameInstance.GetStatsSystem( m_player.GetGame() ).GetStatValue( m_weaponID, gamedataStatType.ZoomLevel );
			m_weaponZoomNeedsUpdate = false;
		}
		UpdateWidgetPosition();
	}

	protected event OnZoomLevelChange( value : Float )
	{
		m_currentZoomLevel = value;
		UpdateWidgetPosition();
	}

	private function UpdateWidgetPosition()
	{
		var multiplier, rFov : Float;
		var sway : Vector2;
		var evt : NormalizeAndSaveSwayEvent;
		if( m_isAimingDownSights )
		{
			if( m_player )
			{
				rFov = Deg2Rad( GameInstance.GetCameraSystem( m_player.GetGame() ).GetActiveCameraFOV() );
				if( rFov == 0.0 )
				{
					rFov = 0.88999999;
				}
			}
			else
			{
				rFov = 0.88999999;
			}
			if( m_currentZoomLevel >= 2.0 )
			{
				multiplier = m_currentZoomLevel / TanF( rFov * 0.5 );
			}
			else
			{
				multiplier = m_currentWeaponZoom / TanF( rFov * 0.5 );
			}
			sway.X = ( -19.20000076 * m_currentSway.X ) * multiplier;
			sway.Y = ( -18.89999962 * m_currentSway.Y ) * multiplier;
		}
		else
		{
			m_currentSway = Vector2( 0.0, 0.0 );
			sway = Vector2( 0.0, 0.0 );
		}
		m_rootWidget.SetMargin( inkMargin( sway.X, sway.Y, 0.0, 0.0 ) );
		evt = new NormalizeAndSaveSwayEvent;
		evt.sway = sway;
		QueueEvent( evt );
	}

	public function OnWeaponChanged()
	{
		var stats : StatsSystem;
		var weapon : WeaponObject;
		if( m_player )
		{
			stats = GameInstance.GetStatsSystem( m_player.GetGame() );
			stats.UnregisterListener( m_weaponID, m_weaponZoomListener );
			weapon = ( ( WeaponObject )( GameInstance.GetTransactionSystem( m_player.GetGame() ).GetItemInSlot( m_player, T"AttachmentSlots.WeaponRight" ) ) );
			if( weapon )
			{
				m_weaponID = weapon.GetItemData().GetStatsObjectID();
				m_weaponZoomListener.SetStatType( gamedataStatType.ZoomLevel );
				stats.RegisterListener( m_weaponID, m_weaponZoomListener );
				m_weaponZoomNeedsUpdate = true;
			}
		}
	}

	protected event OnHitIndicatorEnabledChanged( value : Bool )
	{
		m_hitIndicatorEnabled = value;
	}

	protected event OnNormalizeAndSaveSwayEvent( evt : NormalizeAndSaveSwayEvent )
	{
		var UIGameDataBB : IBlackboard;
		var windowSize, uiSize, normSway : Vector2;
		var inverseScale : Float;
		windowSize = m_uiSystem.GetCurrentWindowSize();
		inverseScale = m_uiSystem.GetInverseUIScale();
		uiSize = Vector2( windowSize.X * inverseScale, windowSize.Y * inverseScale );
		normSway = Vector2( ( evt.sway.X / uiSize.X ) * 2.0, ( evt.sway.Y / uiSize.Y ) * 2.0 );
		UIGameDataBB = GetBlackboardSystem().Get( GetAllBlackboardDefs().UIGameData );
		UIGameDataBB.SetVector2( GetAllBlackboardDefs().UIGameData.NormalizedWeaponSway, normSway );
	}

}

class TargetHitIndicatorLogicController extends inkLogicController
{
	editable var m_animName : CName;
	editable var m_animationPriority : Int32;

	protected event OnInitialize()
	{
		GetRootWidget().SetVisible( false );
	}

}

class HitIndicatorWeaponZoomListener extends ScriptStatsListener
{
	var m_gameController : weak< TargetHitIndicatorGameController >;

	public override function OnStatChanged( ownerID : StatsObjectID, statType : gamedataStatType, diff : Float, total : Float )
	{
		m_gameController.m_currentWeaponZoom = total;
		m_gameController.m_weaponZoomNeedsUpdate = false;
	}

}

class WeaponChangedListener extends AttachmentSlotsScriptCallback
{
	var m_gameController : weak< TargetHitIndicatorGameController >;

	public override function OnItemEquipped( slot : TweakDBID, item : ItemID )
	{
		m_gameController.OnWeaponChanged();
	}

}

class NormalizeAndSaveSwayEvent extends Event
{
	var sway : Vector2;
}

