class BossHealthBarGameController extends inkHUDGameController
{
	private editable var m_healthControllerRef : inkWidgetRef;
	private editable var m_healthPercentage : inkTextRef;
	private editable var m_bossName : inkTextRef;
	private editable var m_dividerContainerRef : inkCompoundRef;
	private var m_statListener : BossHealthStatListener;
	private var m_boss : weak< NPCPuppet >;
	private var m_healthController : weak< NameplateBarLogicController >;
	private var m_thresholds : array< Float >;
	private var m_root : weak< inkWidget >;
	private var m_unfoldAnimation : inkAnimProxy;
	private var m_foldAnimation : inkAnimProxy;
	private var m_fastTravelCallbackID : CallbackHandle;
	private var m_bossPuppets : array< weak< NPCPuppet > >;

	protected event OnInitialize()
	{
		m_healthController = ( ( NameplateBarLogicController )( inkWidgetRef.GetController( m_healthControllerRef ) ) );
		m_statListener = new BossHealthStatListener;
		m_statListener.BindHealthbar( this );
		m_root = GetRootWidget();
		m_root.SetVisible( false );
		IBlackboard.Create( GetAllBlackboardDefs().PuppetState );
		m_boss = NULL;
		m_bossPuppets.Clear();
		RegisterFastTravelCallback();
		ReevaluatePlayerInBossCombat();
	}

	protected event OnUninitialize()
	{
		UnregisterFastTravelCallback();
	}

	public function UpdateHealthValue( newValue : Float, optional silent : Bool )
	{
		m_healthController.SetNameplateBarProgress( newValue / 100.0, silent );
		inkTextRef.SetText( m_healthPercentage, IntToString( CeilF( newValue ) ) );
	}

	protected event OnBossCombatNotifier( evt : BossCombatNotifier )
	{
		if( evt.combatEnded )
		{
			ReevaluateBossArray();
		}
		else if( !( StatusEffectSystem.ObjectHasStatusEffect( ( ( NPCPuppet )( evt.bossEntity ) ), T"Chimera.HideBossHealthBar" ) ) )
		{
			AddBoss( ( ( NPCPuppet )( evt.bossEntity ) ) );
		}
	}

	protected event OnThreatDefeated( evt : ThreatDefeated )
	{
		if( evt.threat )
		{
			RemoveBoss( ( ( NPCPuppet )( evt.threat ) ) );
		}
		else
		{
			ReevaluateBossArray();
		}
	}

	protected event OnThreatUnconscious( evt : ThreatUnconscious )
	{
		if( evt.threat )
		{
			RemoveBoss( ( ( NPCPuppet )( evt.threat ) ) );
		}
		else
		{
			ReevaluateBossArray();
		}
	}

	protected event OnThreatKilled( evt : ThreatDeath )
	{
		if( evt.threat )
		{
			RemoveBoss( ( ( NPCPuppet )( evt.threat ) ) );
		}
		else
		{
			ReevaluateBossArray();
		}
	}

	protected event OnThreatRemoved( evt : ThreatRemoved )
	{
		if( evt.threat )
		{
			RemoveBoss( ( ( NPCPuppet )( evt.threat ) ) );
		}
		else
		{
			ReevaluateBossArray();
		}
	}

	protected event OnThreatInvalid( evt : ThreatInvalid )
	{
		if( evt.threat )
		{
			RemoveBoss( ( ( NPCPuppet )( evt.threat ) ) );
		}
		else
		{
			ReevaluateBossArray();
		}
	}

	protected event OnAnimationEnd( e : inkAnimProxy )
	{
		m_root.SetVisible( false );
	}

	protected event OnDamageDealt( evt : gameTargetDamageEvent )
	{
		var puppet : NPCPuppet;
		var isDeadOrDefeated : Bool;
		var canHaveBossHealthbar : Bool;
		var isHealthbarHidden : Bool;
		puppet = ( ( NPCPuppet )( evt.target ) );
		canHaveBossHealthbar = puppet.IsBoss() || puppet.GetNPCRarity() == gamedataNPCRarity.MaxTac;
		isDeadOrDefeated = ScriptedPuppet.IsDefeated( puppet ) || puppet.IsDead();
		isHealthbarHidden = StatusEffectSystem.ObjectHasStatusEffect( puppet, T"Chimera.HideBossHealthBar" );
		if( ( canHaveBossHealthbar && !( isDeadOrDefeated ) ) && !( isHealthbarHidden ) )
		{
			AddBoss( puppet );
		}
	}

	private function AddBoss( boss : NPCPuppet )
	{
		if( !( boss ) || ( !( boss.IsBoss() ) && boss.GetNPCRarity() != gamedataNPCRarity.MaxTac ) )
		{
			return;
		}
		if( m_bossPuppets.Contains( boss ) )
		{
			if( boss != m_boss )
			{
				ShowBossHealthBar( boss, true );
			}
			return;
		}
		m_bossPuppets.PushBack( boss );
		ShowBossHealthBar( boss, false );
	}

	private function RemoveBoss( boss : NPCPuppet )
	{
		if( !( boss ) || !( m_bossPuppets.Contains( boss ) ) )
		{
			return;
		}
		m_bossPuppets.Remove( boss );
		if( ( m_bossPuppets.Size() > 0 ) && m_bossPuppets[ 0 ] )
		{
			ShowBossHealthBar( m_bossPuppets[ 0 ], true );
		}
		else
		{
			HideBossHealthBar();
		}
	}

	private function ReevaluateBossArray()
	{
		var i : Int32;
		for( i = m_bossPuppets.Size() - 1; i >= 0; i -= 1 )
		{
			if( ( !( m_bossPuppets[ i ] ) || !( ScriptedPuppet.IsActive( m_bossPuppets[ i ] ) ) ) || GameObject.IsFriendlyTowardsPlayer( m_bossPuppets[ i ] ) )
			{
				m_bossPuppets.Erase( i );
			}
		}
		if( m_bossPuppets.Size() == 0 )
		{
			HideBossHealthBar();
		}
	}

	private function ShowBossHealthBar( puppet : NPCPuppet, useSilentUpdate : Bool )
	{
		var playUnfoldAnim : Bool;
		if( ( !( puppet ) || ( !( puppet.IsBoss() ) && puppet.GetNPCRarity() != gamedataNPCRarity.MaxTac ) ) || !( ScriptedPuppet.IsAlive( puppet ) ) )
		{
			return;
		}
		playUnfoldAnim = !( m_boss );
		UnregisterPreviousBoss();
		RegisterToNewBoss( puppet, useSilentUpdate );
		m_root.SetVisible( true );
		if( playUnfoldAnim )
		{
			if( m_foldAnimation && m_foldAnimation.IsPlaying() )
			{
				m_foldAnimation.UnregisterFromAllCallbacks( inkanimEventType.OnFinish );
				m_foldAnimation.Stop();
			}
			if( !( m_unfoldAnimation ) || !( m_unfoldAnimation.IsPlaying() ) )
			{
				m_unfoldAnimation = PlayLibraryAnimation( 'unfold' );
			}
		}
	}

	private function HideBossHealthBar()
	{
		UnregisterPreviousBoss();
		if( !( m_foldAnimation ) || !( m_foldAnimation.IsPlaying() ) )
		{
			if( m_unfoldAnimation && m_unfoldAnimation.IsPlaying() )
			{
				m_unfoldAnimation.Stop();
			}
			m_foldAnimation = PlayLibraryAnimation( 'fold' );
			m_foldAnimation.RegisterToCallback( inkanimEventType.OnFinish, this, 'OnAnimationEnd' );
		}
	}

	private function RegisterToNewBoss( boss : NPCPuppet, useSilentUpdate : Bool )
	{
		var NPCName : String;
		var characterRecord : Character_Record;
		m_boss = boss;
		UpdateHealthValue( GameInstance.GetStatPoolsSystem( m_boss.GetGame() ).GetStatPoolValue( m_boss.GetEntityID(), gamedataStatPoolType.Health ), useSilentUpdate );
		GameInstance.GetStatPoolsSystem( m_boss.GetGame() ).RequestRegisteringListener( m_boss.GetEntityID(), gamedataStatPoolType.Health, m_statListener );
		characterRecord = TweakDBInterface.GetCharacterRecord( m_boss.GetRecordID() );
		SetUpThresholds( characterRecord );
		if( IsNameValid( characterRecord.FullDisplayName() ) )
		{
			NPCName = LocKeyToString( characterRecord.FullDisplayName() );
		}
		else
		{
			NPCName = m_boss.GetDisplayName();
		}
		inkTextRef.SetText( m_bossName, NPCName );
	}

	private function UnregisterPreviousBoss()
	{
		if( !( m_boss ) )
		{
			return;
		}
		GameInstance.GetStatPoolsSystem( m_boss.GetGame() ).RequestUnregisteringListener( m_boss.GetEntityID(), gamedataStatPoolType.Health, m_statListener );
		m_boss = NULL;
	}

	public static function ReevaluateBossHealthBar( puppet : weak< NPCPuppet >, target : weak< GameObject >, optional combatEnded : Bool )
	{
		var bossCombatEvent : BossCombatNotifier;
		if( ( ( !( puppet ) || ( !( puppet.IsBoss() ) && puppet.GetNPCRarity() != gamedataNPCRarity.MaxTac ) ) || !( target ) ) || !( target.IsPlayer() ) )
		{
			return;
		}
		bossCombatEvent = new BossCombatNotifier;
		bossCombatEvent.bossEntity = puppet;
		bossCombatEvent.combatEnded = combatEnded;
		target.QueueEvent( bossCombatEvent );
	}

	public static function ReevaluateBossHealthBar( puppet : weak< NPCPuppet >, targetTracker : weak< TargetTrackingExtension >, optional combatEnded : Bool )
	{
		var hostileThreats : array< TrackedLocation >;
		var bossCombatEvent : BossCombatNotifier;
		var target : weak< GameObject >;
		if( ( !( puppet ) || ( !( puppet.IsBoss() ) && puppet.GetNPCRarity() != gamedataNPCRarity.MaxTac ) ) || !( targetTracker ) )
		{
			return;
		}
		hostileThreats = targetTracker.GetHostileThreats( false );
		if( ( hostileThreats.Size() > 0 ) && TargetTrackingExtension.GetPlayerFromThreats( hostileThreats, target ) )
		{
			bossCombatEvent = new BossCombatNotifier;
			bossCombatEvent.bossEntity = puppet;
			bossCombatEvent.combatEnded = combatEnded;
			target.QueueEvent( bossCombatEvent );
		}
	}

	private function ReevaluatePlayerInBossCombat()
	{
		var player : weak< PlayerPuppet >;
		var threatPuppet : weak< NPCPuppet >;
		var threatsArray : array< TrackedLocation >;
		var i : Int32;
		player = ( ( PlayerPuppet )( GetOwnerEntity() ) );
		if( player )
		{
			threatsArray = player.GetTargetTrackerComponent().GetThreats( false );
			for( i = 0; i < threatsArray.Size(); i += 1 )
			{
				threatPuppet = ( ( NPCPuppet )( threatsArray[ i ].entity ) );
				if( threatPuppet && ( threatPuppet.IsBoss() || threatPuppet.GetNPCRarity() == gamedataNPCRarity.MaxTac ) )
				{
					BossHealthBarGameController.ReevaluateBossHealthBar( threatPuppet, player );
				}
			}
		}
	}

	private function SetUpThresholds( boss : Character_Record )
	{
		var i : Int32;
		m_thresholds.Clear();
		for( i = 0; i < boss.GetBossHealthBarThresholdsCount(); i += 1 )
		{
			m_thresholds.PushBack( boss.GetBossHealthBarThresholdsItem( i ) );
		}
		SortThresholds();
		MoveDividers();
	}

	private function SortThresholds()
	{
		var tempVar : Float;
		var i, j : Int32;
		for( i = 0; i < ( m_thresholds.Size() - 1 ); i += 1 )
		{
			for( j = i + 1; j < m_thresholds.Size(); j += 1 )
			{
				if( m_thresholds[ i ] > m_thresholds[ j ] )
				{
					tempVar = m_thresholds[ j ];
					m_thresholds[ j ] = m_thresholds[ i ];
					m_thresholds[ i ] = tempVar;
				}
			}
		}
	}

	private function MoveDividers()
	{
		var widget : weak< inkWidget >;
		var i, j : Int32;
		var tempSize : Vector2;
		var leftThreshold, rightThreshold : Float;
		var totalWidth, gapWidth : Float;
		inkCompoundRef.RemoveAllChildren( m_dividerContainerRef );
		tempSize = inkCompoundRef.GetSize( m_dividerContainerRef );
		totalWidth = tempSize.X;
		for( i = 0; i < m_thresholds.Size(); i += 1 )
		{
			j = i - 1;
			leftThreshold = ( ( j >= 0 ) ? ( m_thresholds[ j ] ) : ( 0.0 ) );
			rightThreshold = ( ( i < m_thresholds.Size() ) ? ( m_thresholds[ i ] ) : ( 100.0 ) );
			gapWidth = ( ( rightThreshold - leftThreshold ) / 100.0 ) * totalWidth;
			widget = SpawnFromLocal( inkCompoundRef.Get( m_dividerContainerRef ), 'divider' );
			if( i != 0 )
			{
				tempSize = widget.GetSize();
				gapWidth -= tempSize.X;
			}
			widget.SetMargin( gapWidth, 0.0, 0.0, 0.0 );
		}
	}

	private function ScaleBetween( value : Float, inMin : Float, inMax : Float, outMin : Float, outMax : Float ) : Float
	{
		var dividend, divisor : Float;
		dividend = ( outMax - outMin ) * ( value - inMin );
		divisor = inMax - inMin;
		return ( dividend / divisor ) + outMin;
	}

	private function RegisterFastTravelCallback()
	{
		var blackboard : IBlackboard;
		blackboard = GetBlackboardSystem().Get( GetAllBlackboardDefs().FastTRavelSystem );
		if( blackboard && !( m_fastTravelCallbackID ) )
		{
			m_fastTravelCallbackID = blackboard.RegisterListenerBool( GetAllBlackboardDefs().FastTRavelSystem.FastTravelLoadingScreenFinished, this, 'OnFastTravelFinished' );
		}
	}

	private function UnregisterFastTravelCallback()
	{
		var blackboard : IBlackboard;
		blackboard = GetBlackboardSystem().Get( GetAllBlackboardDefs().FastTRavelSystem );
		if( ( blackboard != NULL ) && m_fastTravelCallbackID )
		{
			blackboard.UnregisterListenerBool( GetAllBlackboardDefs().FastTRavelSystem.FastTravelLoadingScreenFinished, m_fastTravelCallbackID );
		}
	}

	private function OnFastTravelFinished( value : Bool )
	{
		if( value )
		{
			ReevaluateBossArray();
		}
	}

}

class BossHealthStatListener extends ScriptStatPoolsListener
{
	private var m_healthbar : weak< BossHealthBarGameController >;

	public function BindHealthbar( bar : weak< BossHealthBarGameController > )
	{
		m_healthbar = bar;
	}

	public override function OnStatPoolValueChanged( oldValue : Float, newValue : Float, percToPoints : Float )
	{
		m_healthbar.UpdateHealthValue( newValue );
	}

}

class BossCombatNotifier extends Event
{
	var bossEntity : weak< Entity >;
	var combatEnded : Bool;
}

