namespace BP
{
	const float BOT_RESPAWN_TIME = 10.0f;
	const float BOT_FXEVENT_TIME = 5.0f;
	
	class BotInfo
	{
		AI@ ai; //the ai script of this bot
		SActor sActor; //the actor of this bot
		array<HostileInfo@> hostiles; //the hostile targets of this bot
		int teamID;
		float deathTime;
		float fxEventTime;
		//------------------------------------------------------------------------------------------------------------------------
		BotInfo()
		{
		}
		//------------------------------------------------------------------------------------------------------------------------
		BotInfo(const int actorID, const int teamID)
		{
			this.teamID = teamID;
			kActor @actor = BP::Spawn::Enemy(actorID, Math::vecZero);
			AI @ai = BP::Trigger::AIGetScript(@actor);
			DebugAssert(@ai != null, "[BP::BotInfo::BotInfo] ai is null");
			if (@ai != null)
			{
				@this.ai = @ai;
				sActor = SActor(@actor);
				// actor.RenderMeshComponent().CanFlinch() = false;
				ai.onPreDamage.Add(BP::Events::DamageInfoCallback(OnPreDamage));
				ai.onDamage.Add(BP::Events::DamageInfoCallback(OnDamaged));
				ai.onDeath.Add(BP::Events::DamageInfoCallback(OnDeath));
				fxEventTime = BOT_FXEVENT_TIME;
				ApplyTeamFxEvent();
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		bool opEquals(const BotInfo &in other)
		{
			return @ai == @other.ai;
		}
		//------------------------------------------------------------------------------------------------------------------------
		BotInfo@ opAssign(const BotInfo &in other)
		{
			teamID = other.teamID;
			@ai = @other.ai;
			sActor = other.sActor;
			int hostilesLength = int(other.hostiles.length());
			hostiles.resize(hostilesLength);
			for (int i = 0; i < hostilesLength; i++)
			{
				@hostiles[i] = @other.hostiles[i];
			}
			
			return this;
		}
		//------------------------------------------------------------------------------------------------------------------------
		#ifdef BP_DEBUG
		kStr ToString()
		{
			int hostilesLength = int(hostiles.length());
			kStr s = "teamID: " + teamID + " deathTime: " + deathTime + " fxEventTime: " + fxEventTime + " sActor: " + sActor.ToString() +
			" hostilesLength: " + hostilesLength + " ";
			for (int i = 0; i < hostilesLength; i++)
			{
				if (@hostiles[i] != null)
				{
					s += hostiles[i].ToString();
				}
				else
				{
					s += "Null ";
				}
			}
			return s;
		}
		#endif
		//------------------------------------------------------------------------------------------------------------------------
		void Serialize(kDict dict, kStr& in key)
		{
			SERIALIZEKEY_INT(key, teamID);
			SERIALIZEKEY_FLOAT(key, deathTime);
			SERIALIZEKEY_FLOAT(key, fxEventTime);
			SERIALIZEKEY_SACTOR(key, sActor);
			int hostilesLength = int(hostiles.length());
			SERIALIZEKEY_INT(key, hostilesLength);
			for (int i = 0; i < hostilesLength; i++)
			{
				hostiles[i].Serialize(dict, key + "hostile" + i);
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void Deserialize(kDict& in dict, kStr& in key)
		{
			DESERIALIZEKEY_INT(key, teamID);
			DESERIALIZEKEY_FLOAT(key, deathTime);
			DESERIALIZEKEY_FLOAT(key, fxEventTime);
			DESERIALIZEKEY_SACTOR(key, sActor);
			int hostilesLength;
			DESERIALIZEKEY_INT(key, hostilesLength);
			hostiles.resize(hostilesLength);
			for (int i = 0; i < hostilesLength; i++)
			{
				if (@hostiles[i] == null)
				{
					@hostiles[i] = HostileInfo();
				}
				hostiles[i].Deserialize(dict, key + "hostile" + i);
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void OnDeserializeStart()
		{
			sActor.SetDeserializedActor();
			
			int hostilesLength = int(hostiles.length());
			for (int i = 0; i < hostilesLength; i++)
			{
				hostiles[i].sActor.SetDeserializedActor();
			}
			
			//set the AI Script from sActor and add callbacks
			if (sActor.actor !is null)
			{
				AI @ai = BP::Trigger::AIGetScript(@sActor.actor);
				DebugAssert(@ai != null, "[BP::BotInfo::OnDeserializeStart] bot ai is null");
				if (@ai != null)
				{
					@this.ai = @ai;
					ai.onPreDamage.Add(BP::Events::DamageInfoCallback(OnPreDamage));
					ai.onDamage.Add(BP::Events::DamageInfoCallback(OnDamaged));
					ai.onDeath.Add(BP::Events::DamageInfoCallback(OnDeath));
					fxEventTime = BOT_FXEVENT_TIME;
					ApplyTeamFxEvent();
				}
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		bool IsValid()
		{
			return sActor.Exists();
		}
		//------------------------------------------------------------------------------------------------------------------------
		void OnPreDamage(kDamageInfo& in dmgInfo)
		{
			//don't take damage from allies
			kActor@ src = @realOnDamageSource;
			if (@src != null)
			{
				int teamID = BP::Actor::GetTeamID(@src);
				if (teamID == this.teamID)
				{
					ai.self.Flags() |= AF_NOINFLICTDAMAGE;
				}
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void OnDamaged(kDamageInfo& in dmgInfo)
		{
			kActor@ src = dmgInfo.source;
			if (@src != null)
			{
				GainHostility(@src, dmgInfo.hits);
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void OnDeath(kDamageInfo& in dmgInfo)
		{
			this.deathTime = BOT_RESPAWN_TIME;
			ApplyTeamDeathFxEvent();
		}
		//------------------------------------------------------------------------------------------------------------------------
		void ApplyTeamFxEvent()
		{
			if (sActor.Exists())
			{
				if (teamID == 0)
				{
					sActor.actor.RunFxEvent("Team_Red");
				}
				else
				{
					sActor.actor.RunFxEvent("Team_Blue");
				}
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void ApplyTeamDeathFxEvent()
		{
			if (sActor.Exists())
			{
				if (teamID == 0)
				{
					sActor.actor.RunFxEvent("Team_Red_Fade");
				}
				else
				{
					sActor.actor.RunFxEvent("Team_Blue_Fade");
				}
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void OnTick()
		{
			if (deathTime > 0.0f)
			{
				deathTime -= GAME_DELTA_TIME;
				if (deathTime <= 0.0f)
				{
					BP::teams[teamID].OnBotDeath(this);
					//remove the actor
					if (IsValid())
					{
						sActor.actor.Remove();
					}
				}
			}
			else
			{
				//because the fxEvent can get overriden by other things such as weapon tracking every so often we reapply the team fx event
				fxEventTime -= GAME_DELTA_TIME;
				if (fxEventTime <= 0.0f)
				{
					fxEventTime = BOT_FXEVENT_TIME;
					ApplyTeamFxEvent();
				}
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void GainHostility(kActor@ actor, float amount)
		{
			int hostilesLength = int(hostiles.length());
			for (int i = 0; i < hostilesLength; i++)
			{
				BP::HostileInfo@ hostileInfo = @hostiles[i];
				if (hostileInfo.Exists() && @hostileInfo.sActor.actor == @actor)
				{
					hostiles[i].GainHostility(amount);
					return;
				}
			}
			
			hostiles.insertLast(@BP::HostileInfo(SActor(@actor), amount));
		}
		//------------------------------------------------------------------------------------------------------------------------
		void ClearHostility()
		{
			hostiles.resize(0);
		}
		//------------------------------------------------------------------------------------------------------------------------
	};
}
