namespace BP
{
	namespace Controller
	{
		const float RAZOR_TIME_BEFORE_RETURNING      = 12.0f;
		const float RAZOR_SNAP_TO_HAND_RADIUS        = 15.0f * GAME_SCALE;
		const float RAZOR_SNAP_BACK_FAILSAFE_TIME    = 30.0f;
		const float RAZOR_SEEK_RANGE = 1024.0f * 1024.0f;
		const float RAZOR_SEEK_CHECK_TIME = 0.15f;
		const float RAZOR_SEEK_TARGET_HEIGHT_RATIO = 0.75f;

		class RazorWind : ScriptObject
		{
			kActor@ self;
			array<kActor@> seekIgnoreEnemies;
			float seekCheckTime;
			//------------------------------------------------------------------------------------------------------------------------
			RazorWind(kActor@ actor)
			{
				@self = actor;
				seekCheckTime = RAZOR_SEEK_CHECK_TIME;
			}
			//------------------------------------------------------------------------------------------------------------------------
			void OnTick(void)
			{
				if (self.GetTarget() is null)
				{
					self.Remove();
					return;
				}
				
				kParticle@ pParticle = self.GetTarget().CastToParticle();
				if (pParticle is null)
				{
					self.Remove();
					return;
				}
				
				if (pParticle.IsStale())
				{
					self.Remove();
					return;
				}
				
				self.Origin() = pParticle.Origin();
				self.PlayLoopingSound("sounds/shaders/Razor Wind Fly Loop.ksnd");
				
				if (pParticle.LifeFrames() > RAZOR_SNAP_BACK_FAILSAFE_TIME)
				{
					pParticle.Flags() |= FXF_NOCOLLISION;
				}
				
				// after a certain time has passed, return back to hand
				if (pParticle.LifeFrames() > RAZOR_TIME_BEFORE_RETURNING)
				{
					// get current speed
					float speed = pParticle.Velocity().Unit();
					kActor@ pOwner = pParticle.Owner();
					
					if (pOwner is null)
					{
						pParticle.Remove();
						self.Remove();
						return;
					}
					
					// check if blade has struck something that bleeds
					if ((pParticle.Flags() & (FXF_DREW_BLOOD | FXF_DREW_BLOOD_GREEN)) != 0)
					{
						if (pOwner.InstanceOf("kexPuppet"))
						{
							kPuppet@ pPuppet = pOwner.CastToPuppet();
							kWeapon@ pWeapon = pPuppet.PlayerOwner().WeaponActor();
							if (!(pWeapon is null) && pWeapon.Type() == kActor_Wpn_RazorWind)
							{
								int textureNum = (pParticle.Flags() & FXF_DREW_BLOOD_GREEN) != 0 ? 1 : 0;
								pWeapon.RenderMeshComponent().AltTexture() = textureNum;
								BP::Weapon::RazorNoBloodThrowCount = BP::Weapon::RAZOR_THROWS_TILL_NOBLOOD;
								BP::Weapon::RazorTextureIndex = textureNum;
							}
						}
					}
					
					// get position to player's hand and distance to it
					kVec3 vHandPos(2.6f*GAME_SCALE, 14.0f*GAME_SCALE, 8.0f*GAME_SCALE);
					kVec3 vHandWorldPos = pOwner.LocalVectorToWorldSpace(vHandPos);
					kVec3 vTargetPos = vHandWorldPos - pParticle.Origin();
					
					float dist = vTargetPos.Unit();
					kVec3 vAim;
					
					// if close to player's hand
					if (dist <= RAZOR_SNAP_TO_HAND_RADIUS + 3.0f * GAME_SCALE)
					{
						// snap it back?
						if (dist < RAZOR_SNAP_TO_HAND_RADIUS / 3.0f)
						{
							pParticle.Origin() = vHandWorldPos;
							pParticle.Direction().x = 0;
							pParticle.Direction().y = 0;
							pParticle.Direction().z = 1;
							pParticle.Velocity().Clear();
							pParticle.Kill();
							self.Remove();
							return;
						}
						
						vAim = vTargetPos;
					}
					else
					{
						vHandPos = kVec3(10.0f*GAME_SCALE, RAZOR_SNAP_TO_HAND_RADIUS, 0.0f);
						
						if (!(pOwner.WorldComponent() is null))
						{
							vHandPos.z += pOwner.WorldComponent().Height() * 0.8f;
						}
						
						vHandWorldPos = pOwner.LocalVectorToWorldSpace(vHandPos);
						vAim = vHandWorldPos - pParticle.Origin();
						dist = vAim.Unit();
					}
					
					// blend new directions together
					vAim.Normalize();
					float dot = vAim.Dot(pParticle.Direction());
					float angle = Math::ACos(dot);
					float blend = 1.0f;
					
					if (angle >= Math::Deg2Rad(15.0f))
					{
						blend = 0.5f;
					}
					
					// interpolate direction and change velocity
					pParticle.Direction().Lerp(vAim, blend);
					pParticle.Velocity() = (pParticle.Direction() * speed);
				}
				else //Not returning yet
				{
					if (BP::Inventory::Has(kActor_RazorWindUpgrade))
					{
						seekCheckTime -= GAME_DELTA_TIME;
						if (seekCheckTime <= 0.0f)
						{
							seekCheckTime = RAZOR_SEEK_CHECK_TIME;
							uint cfFlags = uint(CF_POLYCOLLISION | CF_COLLIDEJOINTS | CF_EXPANDJOINTNODES);
							float closestDist = 9999999;
							float dist;
							kActorIterator cIterator;
							kActor@ pActor;
							while ((@pActor = cIterator.GetNext()) !is null)
							{
								if (pActor.EnemyAIComponent() !is null)
								{
									dist = self.Origin().DistanceSq(pActor.Origin());
									//is not dead and not hidden and closer than the closest found enemy and
									//within razor seeking range and not in the ignore list
									if (!BP::Actor::IsDead(@pActor) && !BP::Actor::IsHidden(@pActor) && dist < closestDist &&
										dist <= RAZOR_SEEK_RANGE && seekIgnoreEnemies.findByRef(@pActor) < 0)
									{
										kClipInfo rayInfo;
										self.WorldComponent().HitScan(rayInfo, self.Origin(), BP::Actor::CenterOrigin(@pActor, RAZOR_SEEK_TARGET_HEIGHT_RATIO), cfFlags);
										//can see the actor
										if (rayInfo.pContactCollider is pActor.WorldComponent())
										{
											pParticle.SetTarget(@pActor);
											closestDist = dist;
										}
									}
								}
							}
						}
					}
					
					//if has a target that's not null and not stale then move toward it
					kActor @pTarget = pParticle.GetTarget();
					if (@pTarget != null && !pTarget.IsStale())
					{
						
						kVec3 pos = BP::Actor::CenterOrigin(@pTarget, RAZOR_SEEK_TARGET_HEIGHT_RATIO);
						
						//if is within the enemies WorldComponent().Radius() then add this enemy to an ignore list
						float targetRadius = pTarget.WorldComponent().Radius();
						float targetHeight = pTarget.WorldComponent().Height();
						float targetSize = targetRadius < targetHeight ? targetRadius : targetHeight;
						float dist = self.Origin().DistanceSq(pos);
						if (dist <= targetSize * targetSize)
						{
							seekIgnoreEnemies.insertLast(@pTarget);
							pParticle.SetTarget(BP::Actor::Null);
						}
						else
						{
							MoveToTarget(@pParticle, pos);
						}
					}
				}
			}
			//------------------------------------------------------------------------------------------------------------------------
			void MoveToTarget(kParticle@ pParticle, kVec3&in targetPos)
			{
				float speed = pParticle.Velocity().Unit();
				
				kVec3 vAim = targetPos - pParticle.Origin();
					
				// blend new directions together
				vAim.Normalize();
				float dot = vAim.Dot(pParticle.Direction());
				float angle = Math::ACos(dot);
				float blend = 1.0f;
				
				if (angle >= Math::Deg2Rad(15.0f))
				{
					blend = 0.5f;
				}
				
				// interpolate direction and change velocity
				pParticle.Direction().Lerp(vAim, blend);
				pParticle.Velocity() = (pParticle.Direction() * speed);
			}
			//------------------------------------------------------------------------------------------------------------------------
		};
	}
}
