context: using System;
  using System.Numerics;
  using ACE.Entity;
  using ACE.Entity.Enum;
  using ACE.Entity.Enum.Properties;
  using ACE.Server.Entity;
  using ACE.Server.Entity.Actions;
  using ACE.Server.Physics.Animation;
  using ACE.Server.Physics.Extensions;
  using ACE.Server.Network.GameMessages.Messages;
  
  namespace ACE.Server.WorldObjects
  {
      /// <summary>
      /// Creature navigation / position / rotation
      /// </summary>
      partial class Creature
      {
          /// <summary>
          /// Returns the 3D distance between this creature and target
          /// </summary>
          public float GetDistance(WorldObject target)
          {
              return Location.DistanceTo(target.Location);
          }
  
          /// <summary>
          /// Returns the 2D angle between current direction
          /// and position from an input target
          /// </summary>
          public float GetAngle(WorldObject target)
          {
              var currentDir = Location.GetCurrentDir();
  
              var targetDir = Vector3.Zero;
              if (Location.Indoors == target.Location.Indoors)
                  targetDir = GetDirection(Location.ToGlobal(), target.Location.ToGlobal());
              else
                  targetDir = GetDirection(Location.Pos, target.Location.Pos);
  
              targetDir.Z = 0.0f;
              targetDir = Vector3.Normalize(targetDir);
              
              // get the 2D angle between these vectors
              return GetAngle(currentDir, targetDir);
          }
  
          public float GetAngle_Physics(WorldObject target)
          {
              var currentDir = GetCurrentDir_Physics();
  
              var targetDir = Vector3.Zero;
              if (Location.Indoors == target.Location.Indoors)
                  targetDir = GetDirection(Location.ToGlobal(), target.Location.ToGlobal());
              else
                  targetDir = GetDirection(Location.Pos, target.Location.Pos);
  
              targetDir.Z = 0.0f;
              targetDir = Vector3.Normalize(targetDir);
  
              // get the 2D angle between these vectors
              return GetAngle(currentDir, targetDir);
          }
  
          public Vector3 GetCurrentDir_Physics()
          {
              return Vector3.Normalize(Vector3.Transform(Vector3.UnitY, PhysicsObj.Position.Frame.Orientation));
          }
  
          public float GetAngle_Physics2(WorldObject target)
          {
              return PhysicsObj.Position.heading_diff(target.PhysicsObj.Position);
          }
  
          /// <summary>
          /// Returns the 2D angle between current direction
          /// and rotation from an input position
          /// </summary>
          public float GetAngle(Position position)
          {
              var currentDir = Location.GetCurrentDir();
              var targetDir = position.GetCurrentDir();
  
              // get the 2D angle between these vectors
              return GetAngle(currentDir, targetDir);
          }
  
          /// <summary>
          /// Returns the 2D angle of the input vector
          /// </summary>
          public static float GetAngle(Vector3 dir)
          {
              var rads = Math.Atan2(dir.Y, dir.X);
              if (double.IsNaN(rads)) return 0.0f;
  
              var angle = rads * 57.2958f;
              return (float)angle;
          }
  
          /// <summary>
          /// Returns the 2D angle between 2 vectors
          /// </summary>
          public static float GetAngle(Vector3 a, Vector3 b)
          {
              var cosTheta = a.Dot2D(b);
              var rads = Math.Acos(cosTheta);
              if (double.IsNaN(rads)) return 0.0f;
  
              var angle = rads * (180.0f / Math.PI);
              return (float)angle;
          }
  
          /// <summary>
          /// Returns a normalized 2D vector from self to target
          /// </summary>
          public Vector3 GetDirection(Vector3 self, Vector3 target)
          {
              var target2D = new Vector3(self.X, self.Y, 0);
              var self2D = new Vector3(target.X, target.Y, 0);
  
              return Vector3.Normalize(target - self);
          }
  
          /// <summary>
          /// Sends a TurnToObject command to the client
          /// </summary>
          public void TurnToObject(WorldObject target, bool stopCompletely = true)
          {
              var turnToMotion = new Motion(this, target, MovementType.TurnToObject);
  
              if (!stopCompletely)
                  turnToMotion.MoveToParameters.MovementParameters &= ~MovementParams.StopCompletely;
  
              EnqueueBroadcastMotion(turnToMotion);
          }
  
          /// <summary>
          /// Starts rotating a creature from its current direction
          /// so that it eventually is facing the target position
          /// </summary>
          /// <returns>The amount of time in seconds for the rotation to complete</returns>
          public virtual float Rotate(WorldObject target)
          {
              if (target == null || target.Location == null)
                  return 0.0f;
  
              // send network message to start turning creature
              TurnToObject(target);
  
              var angle = GetAngle(target);
              //Console.WriteLine("Angle: " + angle);
  
              // estimate time to rotate to target
              var rotateDelay = GetRotateDelay(angle);
              //Console.WriteLine("RotateTime: " + rotateTime);
  
              // update server object rotation on completion
              // TODO: proper incremental rotation
              var actionChain = new ActionChain();
              actionChain.AddDelaySeconds(rotateDelay);
              actionChain.AddAction(this, () =>
              {
                  if (target == null || target.Location == null)
                      return;
  
                  //var matchIndoors = Location.Indoors == target.Location.Indoors;
  
                  //var globalLoc = matchIndoors ? Location.ToGlobal() : Location.Pos;
                  //var targetLoc = matchIndoors ? target.Location.ToGlobal() : target.Location.Pos;
                  var globalLoc = Location.ToGlobal();
                  var targetLoc = target.Location.ToGlobal();
  
                  var targetDir = GetDirection(globalLoc, targetLoc);
  
                  Location.Rotate(targetDir);
              });
              actionChain.EnqueueChain();
  
              return rotateDelay;
          }
  
          /// <summary>
          /// Returns the amount of time for this creature to rotate by the # of degrees
          /// from the input angle, using the omega speed from its MotionTable
          /// </summary>
          public virtual float GetRotateDelay(float angle)
          {
              var turnSpeed = MotionTable.GetTurnSpeed(MotionTableId);
              if (turnSpeed == 0.0f) return 0.0f;
  
              var rotateTime = Math.PI / turnSpeed / 180.0f * angle;
              return (float)rotateTime;
          }
  
          /// <summary>
          /// Returns the amount of time for this creature to rotate
          /// towards its target, based on the omega speed from its MotionTable
          /// </summary>
          public float GetRotateDelay(WorldObject target)
          {
              var angle = GetAngle(target);
              return GetRotateDelay(angle);
          }
  
          /// <summary>
          /// Starts rotating a creature from its current direction
          /// so that it eventually is facing the rotation from the input position
          /// Used by the emote system, which has the target rotation stored in positions
          /// </summary>
          /// <returns>The amount of time in seconds for the rotation to complete</returns>
          public float TurnTo(Position position)
          {
              var frame = new AFrame(position.Pos, position.Rotation);
              var heading = frame.get_heading();
  
              // send network message to start turning creature
              var turnToMotion = new Motion(this, position, heading);
              EnqueueBroadcastMotion(turnToMotion);
  
              var angle = GetAngle(position);
              //Console.WriteLine("Angle: " + angle);
  
              // estimate time to rotate to target
              var rotateDelay = GetRotateDelay(angle);
              //Console.WriteLine("RotateTime: " + rotateTime);
  
              // update server object rotation on completion
              // TODO: proper incremental rotation
              var actionChain = new ActionChain();
              actionChain.AddDelaySeconds(rotateDelay);
              actionChain.AddAction(this, () =>
              {
                  var targetDir = position.GetCurrentDir();
                  Location.Rotate(targetDir);
                  PhysicsObj.Position.Frame.Orientation = Location.Rotation;
              });
              actionChain.EnqueueChain();
  
              return rotateDelay;
          }
  
          /// <summary>
          /// Returns the amount of time for this creature to rotate
          /// towards the rotation from the input position, based on the omega speed from its MotionTable
          /// Used by the emote system, which has the target rotation stored in positions
          /// </summary>
          /// <param name="position">Only the rotation information from this position is used here</param>
          public float GetRotateDelay(Position position)
          {
              var angle = GetAngle(position);
              return GetRotateDelay(angle);
          }
  
          /// <summary>
          /// This is called by the monster AI system for ranged attacks
          /// It is mostly a duplicate of Rotate(), and should be refactored eventually...
          /// It sets CurrentMotionState and AttackTarget here
          /// </summary>
          public float TurnTo(WorldObject target, bool debug = false)
          {
              if (DebugMove)
                  Console.WriteLine($"{Name}.TurnTo({target.Name})");
  
              if (this is Player) return 0.0f;
  
              var turnToMotion = new Motion(this, target, MovementType.TurnToObject);
              EnqueueBroadcastMotion(turnToMotion);
  
              CurrentMotionState = turnToMotion;
  
              AttackTarget = target;
              var rotateDelay = EstimateTurnTo();
              if (debug)
                  Console.WriteLine("TurnTime = " + rotateDelay);
              var actionChain = new ActionChain();
              actionChain.AddDelaySeconds(rotateDelay);
              actionChain.AddAction(this, () =>
              {
                  // fix me: in progress turn
                  //var targetDir = GetDirection(Location.ToGlobal(), target.Location.ToGlobal());
                  //Location.Rotate(targetDir);
                  if (debug)
                      Console.WriteLine("Finished turning - " + rotateDelay + "s");
              });
              actionChain.EnqueueChain();
              return rotateDelay;
          }
  
          /// <summary>
          /// Used by the monster AI system to start turning / running towards a target
          /// </summary>
          public virtual void MoveTo(WorldObject target, float runRate = 1.0f)
          {
              if (DebugMove)
                  Console.WriteLine($"{Name}.MoveTo({target.Name}, {runRate}) - CurPos: {Location.ToLOCString()} - DestPos: {AttackTarget.Location.ToLOCString()} - TargetDist: {Vector3.Distance(Location.ToGlobal(), AttackTarget.Location.ToGlobal())}");
  
              var motion = GetMoveToMotion(target, runRate);
  
              CurrentMotionState = motion;
  
              EnqueueBroadcastMotion(motion);
          }
  
          public Motion GetMoveToMotion(WorldObject target, float runRate)
          {
              var motion = new Motion(this, target, MovementType.MoveToObject);
              motion.MoveToParameters.MovementParameters |= MovementParams.CanCharge | MovementParams.FailWalk | MovementParams.UseFinalHeading | MovementParams.Sticky | MovementParams.MoveAway;
              motion.MoveToParameters.WalkRunThreshold = 1.0f;
  
              if (runRate > 0)
                  motion.RunRate = runRate;
              else
                  motion.MoveToParameters.MovementParameters &= ~MovementParams.CanRun;
  
              return motion;
          }
  
          public virtual void BroadcastMoveTo(Player player)
          {
              Motion motion = null;
  
              if (AttackTarget != null)
              {
                  // move to object
                  motion = GetMoveToMotion(AttackTarget, RunRate);
              }
              else
              {
                  // move to position
                  var home = GetPosition(PositionType.Home);
  
                  motion = GetMoveToPosition(home, RunRate, 1.0f);
              }
  
              player.Session.Network.EnqueueSend(new GameMessageUpdateMotion(this, motion));
          }
  
          /// <summary>
          /// Sends a network message for moving a creature to a new position
          /// </summary>
          public void MoveTo(Position position, float runRate = 1.0f, bool setLoc = true, float? walkRunThreshold = null, float? speed = null)
          {
              // build and send MoveToPosition message to client
              var motion = GetMoveToPosition(position, runRate, walkRunThreshold, speed);
              EnqueueBroadcastMotion(motion);
  
              if (!setLoc) return;
  
              // start executing MoveTo iterator on server
              if (!PhysicsObj.IsMovingOrAnimating)
                  PhysicsObj.UpdateTime = Physics.Common.PhysicsTimer.CurrentTime;
  
              var mvp = new MovementParameters(motion.MoveToParameters);
              PhysicsObj.MoveToPosition(new Physics.Common.Position(position), mvp);
  
              AddMoveToTick();
          }
  
          private void AddMoveToTick()
          {
              var actionChain = new ActionChain();
              actionChain.AddDelaySeconds(monsterTickInterval);
              actionChain.AddAction(this, () =>
              {
                  if (!IsDead && PhysicsObj?.MovementManager?.MoveToManager != null && PhysicsObj.IsMovingTo())
                  {
                      PhysicsObj.update_object();
                      UpdatePosition_SyncLocation();
                      SendUpdatePosition();
  
                      if (PhysicsObj?.MovementManager?.MoveToManager?.FailProgressCount < 5)
                      {
                          AddMoveToTick();
                      }
                      else
                      {
                          if (PhysicsObj?.MovementManager?.MoveToManager != null)
                          {
                              PhysicsObj.MovementManager.MoveToManager.CancelMoveTo(WeenieError.ActionCancelled);
                              PhysicsObj.MovementManager.MoveToManager.FailProgressCount = 0;
                          }
                          EnqueueBroadcastMotion(new Motion(CurrentMotionState.Stance, MotionCommand.Ready));
                      }
  
                      //Console.WriteLine($"{Name}.Position: {Location}");
                  }
              });
              actionChain.EnqueueChain();
          }
  
          public Motion GetMoveToPosition(Position position, float runRate = 1.0f, float? walkRunThreshold = null, float? speed = null)
          {
              // TODO: change parameters to accept an optional MoveToParameters
  
              var motion = new Motion(this, position);
              motion.MovementType = MovementType.MoveToPosition;
              //motion.Flag |= MovementParams.CanCharge | MovementParams.FailWalk | MovementParams.UseFinalHeading | MovementParams.MoveAway;
              if (walkRunThreshold != null)
                  motion.MoveToParameters.WalkRunThreshold = walkRunThreshold.Value;
  
gt:             if (speed != null)