namespace BP
{
	const float AI_DAMAGE_TIME = 25.0f * GAME_SECONDS; //in particle time converted to seconds
	const float AI_SHOW_DAMAGE_TIME = 1.25f; //seconds before the next damage display will be shown
	
	kActor@ realOnDamageSource;
	//self.EnemyAIComponent().SpawnFlags() |= EAIGF_CLIMBUP | EAIGF_DROPOFF;
	// AI@ triggerAIResult;
	
	class AI : BP::ScriptActor
	{
		float onFireTime; //[Serialize] time until fire caught on fire effects turn off
		bool isSpawned = false; //[Serialize] was custom spawned
		float deadTimer; //time this AI has been dead
		
		bool editorLoad = false; //is loaded from the map and not spawned
		float lifeTime;
		float lastDamageTime;
		float lastDamage; //last amount of total damage caused
		int damageDisplay; //the damage number to show for popup text
		bool stopDamageLoop; //prevents an infinite loop of inflicting damage on self in the OnDamage callback
		float damageTime; //amount of time until damage popup will go away
		bool showDamagePopup; //need this for particle spawn reasons to show damage OnTick instead of in OnDamage
		// WorldText damageText;
		kActor@ movePoint; //a custom point to move to. Null if not moving to custom point
		kActor@ attackPoint; //a custom point to attack. Null if not attacking a custom point
		float damage;
		float speed;
		array<int> pathPoints;
		array<kActor@> pathPointActors;
		int pathPointIndex; //current path point ai is pathing to
		//kActor@ pathPointActor = null;
		bool isPathMoving;
		bool pathIgnorePlayer;
		
		BP::Events::DamageInfo onPreDamage;
		BP::Events::DamageInfo onDamage;
		BP::Events::DamageInfo onDeath;
		//------------------------------------------------------------------------------------------------------------------------
		AI(kActor @actor)
		{
			super(@actor);
		}
		//------------------------------------------------------------------------------------------------------------------------
		// Called on AI that is in the map data not ones created with ActorFactory
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnSpawn(void) override
		{
			editorLoad = true;
		}
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnSerialize(kDict dict) override
		{
			SERIALIZE_FLOAT(onFireTime);
			SERIALIZE_BOOL(isSpawned);
			SERIALIZE_BOOL(editorLoad);
			SERIALIZE_FLOAT(damage);
			SERIALIZE_FLOAT(speed);
			SERIALIZE_BOOL(isPathMoving);
			SERIALIZE_BOOL(pathIgnorePlayer);
			
			SERIALIZE_INT(pathPointIndex);
			int pathPointsLength = int(pathPoints.length());
			SERIALIZE_INT(pathPointsLength);
			for (int i = 0; i < pathPointsLength; i++)
			{
				SERIALIZEKEY_INT("pathPoint" + i, pathPoints[i]);
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnDeserialize(kDict& in dict) override
		{
			DESERIALIZE_FLOAT(onFireTime);
			DESERIALIZE_BOOL(isSpawned);
			DESERIALIZE_BOOL(editorLoad);
			DESERIALIZE_FLOAT(damage);
			DESERIALIZE_FLOAT(speed);
			DESERIALIZE_BOOL(isPathMoving);
			DESERIALIZE_INT(pathPointIndex);
			DESERIALIZE_BOOL(pathIgnorePlayer);
			@movePoint = null;
			@attackPoint = null;
			
			int pathPointsLength;
			DESERIALIZE_INT(pathPointsLength);
			pathPoints.resize(pathPointsLength);
			for (int i = 0; i < pathPointsLength; i++)
			{
				DESERIALIZEKEY_INT("pathPoint" + i, pathPoints[i]);
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnStart()
		{
			//because spawned enemies default health to 100 set spawned enemies to definition start health
			if (!isSpawned && self.Definition() !is null && !editorLoad)
			{
				float health;
				self.Definition().GetFloat("health", health, 1.0f);
				self.Health() = health;
			}
			//DebugLog("[BP::AI::BP_OnStart] Health: " + self.Health());
		}
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnDeserializeStart()
		{
			if (isPathMoving)
			{
				SetPath(pathPoints, pathIgnorePlayer, pathPointIndex);
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnPreDamage(kDamageInfo& in dmgInfo) override
		{
			self.Flags() &= ~AF_NOINFLICTDAMAGE;
			kActor @src = dmgInfo.source;
			
			if (!stopDamageLoop)
			{
				bool isSrcPlayer = @src == @BP::LocalPlayer::Actor;
				//if source of damage is from player or an enemy
				if (src !is null && (isSrcPlayer || src.EnemyAIComponent() !is null))
				{
					stopDamageLoop = true;
					kDamageInfo cDamageInfo;
					cDamageInfo.radius = dmgInfo.radius;
					cDamageInfo.flags = dmgInfo.flags;
					@cDamageInfo.inflictor = @dmgInfo.source;
					@cDamageInfo.source = @dmgInfo.target;
					@cDamageInfo.target = @dmgInfo.target; //not even nesscary
					@cDamageInfo.particle = @dmgInfo.particle;
					cDamageInfo.hits = dmgInfo.hits;
					if (isSrcPlayer)
					{
						cDamageInfo.hits *= BP::LocalPlayer::Script.damage;
					}
					@realOnDamageSource = @dmgInfo.source;
					self.InflictDamage(cDamageInfo); //calls on predamage then (ondeath and then) ondamage with this new damage
					@realOnDamageSource = null;
					self.Flags() |= AF_NOINFLICTDAMAGE; //do not call ondeath or ondamage
					
					return;
				}
			}
			else
			{
				//take damage with our new kDamageInfo that we set
				stopDamageLoop = false;
			}
			
			onPreDamage.Invoke(dmgInfo);
		}
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnDamage(kDamageInfo& in dmgInfo) override
		{
			// DebugLog("AI Hurt for " + dmgInfo.hits);
			
			kActor @src = dmgInfo.source;
			
			//Setup Damage Popup
			float damage = dmgInfo.hits;
			if (damageTime > 0.0f)
			{
				damage = lastDamage + damage;
			}
			lastDamage = damage;
			damageTime = AI_DAMAGE_TIME;
			damageDisplay = (damage > 0.0f && damage < 1.0f) ? Math::CeilToInt(damage) : int(damage);
			showDamagePopup = BP::Game::enableDamageDisplay;
			
			//Check if damaged with fire effect
			if (@src != null)
			{
				bool hasFlameEffect;
				kDictMem @def = src.Definition();
				if (@def != null)
				{
					def.GetBool("BP_FlameEffect", hasFlameEffect);
					if (hasFlameEffect)
					{
						onFireTime = 4.0f;
					}
				}
			}
			
			onDamage.Invoke(dmgInfo);
		}
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnDeath(kDamageInfo& in dmgInfo) override
		{
			//if killed by player
			if (@dmgInfo.inflictor == @BP::LocalPlayer::Actor)
			{
				BP::LocalPlayer::Script.OnAIKIll(@self, dmgInfo);
			}
			
			onDeath.Invoke(dmgInfo);
		}
		//------------------------------------------------------------------------------------------------------------------------
	    void BP_OnTrigger(kActor@ pInstigator, const int msg) override
		{
			switch (self.TriggerMessageID())
			{
				case TRIGGER_MSG_SPAWN:
				{
					isSpawned = true;
					break;
				}
				case TRIGGER_MSG_AI_MOVE_POINT:
				{
					kVec3 point = BP::Trigger::argVectors[0];
					MoveToPoint(point);
					break;
				}
				case TRIGGER_MSG_AI_ATTACK_POINT:
				{
					kVec3 point = BP::Trigger::argVectors[0];
					AttackPoint(point);
					break;
				}
				// case TRIGGER_MSG_AI_GET_SCRIPT:
				// {
					// @triggerAIResult = @this;
					// break;
				// }
			}
		}
		//------------------------------------------------------------------------------------------------------------------------
		void BP_OnTick() override
		{
			lifeTime += GAME_DELTA_TIME;
			
			if (isPathMoving)
			{
				if (!BP::Actor::IsDead(@self))
				{
					if (!pathIgnorePlayer && @self.EnemyAIComponent().SightTarget().Target() == @BP::LocalPlayer::Actor)
					{
						//stop pathing
						// if (self.AnimTrackComponent().IsPlaying(2000))
						// {
							// self.AnimTrackComponent().Flags() |= ANF_CYCLECOMPLETED;
						// }
					}
					else //continue pathing
					{
						// if (!self.AnimTrackComponent().IsPlaying(2000))
						// {
							// self.AnimTrackComponent().Set(2000, 2.0, ANF_LOOP | ANF_ROOTMOTION);
						// }
						
						BP::Actor::AIIgnorePlayer(@self, pathIgnorePlayer);
						
						kActor @pathPointActor = @pathPointActors[pathPointIndex];
						float radius = pathPointActor.WorldComponent().Radius();
						//if reached path point go to next one
						if (BP::Actor::InRadius(@self, @pathPointActor, radius * radius))
						{
							pathPointIndex++;
							if (pathPointIndex >= int(pathPoints.length()))
							{
								pathPointIndex = 0;
							}
							@pathPointActor = @pathPointActors[pathPointIndex];
							BP::Actor::AIMoveToTarget(@self, @pathPointActor);
						}
					}
				}
			}
			
			// if (!BP::Actor::IsDead(@self))
			// {
				
			// }
			
			if (showDamagePopup && lifeTime > lastDamageTime + AI_SHOW_DAMAGE_TIME)
			{
				lastDamageTime = lifeTime;
				showDamagePopup = false;
				BP::WorldText::Show(@self, "" + damageDisplay);
			}
			
			if (onFireTime > 0.0f)
			{
				onFireTime -= GAME_DELTA_TIME;
				self.RenderMeshComponent().ToggleHotPoint(1, true);
			}
			
			if (damageTime > 0.0f)
			{
				damageTime -= GAME_DELTA_TIME;
			}
			
			// damageText.OnTick();
		}
		//------------------------------------------------------------------------------------------------------------------------
		void MoveToPoint(const kVec3&in point)
		{
			if (BP::Actor::Exists(@movePoint))
			{
				movePoint.WorldComponent().SetNearestPosition(point);
			}
			else
			{
				@movePoint = BP::Spawn::Actor(kActor_WayPoint, point);
				movePoint.SetTarget(@self);
			}
			BP::Actor::AIMoveToTarget(@self, @movePoint);
		}
		//------------------------------------------------------------------------------------------------------------------------
		void AttackPoint(const kVec3&in point)
		{
			if (BP::Actor::Exists(@attackPoint))
			{
				attackPoint.WorldComponent().SetNearestPosition(point);
			}
			else
			{
				@attackPoint = BP::Spawn::Actor(kActor_WayPoint, point);
				attackPoint.SetTarget(@self);
			}
			BP::Actor::AIAttackTarget(@self, @attackPoint);
		}
		//------------------------------------------------------------------------------------------------------------------------
		void SetPath(const array<int>& tagIDs, bool ignorePlayer, const int startPathIndex = 0)
		{
			pathPoints = tagIDs;
			pathPointIndex = startPathIndex;
			pathIgnorePlayer = ignorePlayer;
			isPathMoving = true;
			int pathPointsLength = int(pathPoints.length());
			pathPointActors.resize(pathPointsLength);
			if (pathPointsLength > 0)
			{
				for (int i = 0; i < pathPointsLength; i++)
				{
					@pathPointActors[i] = BP::Actor::FindActorWithTID(pathPoints[i]);
				}
				BP::Actor::AIMoveToTarget(@self, pathPointActors[pathPointIndex]);
			}			
			BP::Actor::AIIgnorePlayer(@self, pathIgnorePlayer);
		}
		//------------------------------------------------------------------------------------------------------------------------
	};
}
