using System;
using Server;
using Server.Movement;
using CalcMoves = Server.Movement.Movement;

namespace Server
{
	public class PathFollower
	{
		// Should we use pathfinding? 'false' for not
		private static bool Enabled = Core.Expansion == Expansion.UOR;

		private Mobile m_From;
		private IPoint3D m_Goal;
		private MovementPath m_Path;
		private int m_Index;
		private Point3D m_Next, m_LastGoalLoc;
		private DateTime m_LastPathTime;
		private MoveMethod m_Mover;

		public MoveMethod Mover
		{
			get{ return m_Mover; }
			set{ m_Mover = value; }
		}

		public IPoint3D Goal
		{
			get{ return m_Goal; }
		}

		public PathFollower( Mobile from, IPoint3D goal )
		{
			m_From = from;
			m_Goal = goal;
		}

		public MoveResult Move( Direction d )
		{
			if ( m_Mover == null )
				return ( m_From.Move( d ) ? MoveResult.Success : MoveResult.Blocked );

			return m_Mover( d );
		}

		public Point3D GetGoalLocation()
		{
			if ( m_Goal is Item )
				return ((Item)m_Goal).GetWorldLocation();

			return new Point3D( m_Goal );
		}

		private static TimeSpan RepathDelay = TimeSpan.FromSeconds( 2.0 );

		public void Advance( ref Point3D p, int index )
		{
			if ( m_Path != null && m_Path.Success )
			{
				Direction[] dirs = m_Path.Directions;

				if ( index >= 0 && index < dirs.Length )
				{
					int x = p.X, y = p.Y;

					CalcMoves.Offset( dirs[index], ref x, ref y );

					p.X = x;
					p.Y = y;
				}
			}
		}

		public void ForceRepath()
		{
			m_Path = null;
		}

		public bool CheckPath()
		{
			if ( !Enabled )
				return false;

			bool repath = false;

			Point3D goal = GetGoalLocation();

			if ( m_Path == null )
				repath = true;
			else if ( (!m_Path.Success || goal != m_LastGoalLoc) && (m_LastPathTime + RepathDelay) <= DateTime.UtcNow )
				repath = true;
			else if ( m_Path.Success && Check( m_From.Location, m_LastGoalLoc, 0 ) )
				repath = true;

			if ( !repath )
				return false;

			m_LastPathTime = DateTime.UtcNow;
			m_LastGoalLoc = goal;

			m_Path = new MovementPath( m_From, goal );

			m_Index = 0;
			m_Next = m_From.Location;

			Advance( ref m_Next, m_Index );

			return true;
		}

		public bool Check( Point3D loc, Point3D goal, int range )
		{
			if ( !Utility.InRange( loc, goal, range ) )
				return false;

			if ( range <= 1 && Math.Abs( loc.Z - goal.Z ) >= 16 )
				return false;

			return true;
		}

		public bool Follow( bool run, int range )
		{
			Point3D goal = GetGoalLocation();
			Direction d;

			if ( Check( m_From.Location, goal, range ) )
				return true;

			bool repathed = CheckPath();

			if ( !Enabled || !m_Path.Success )
			{
				d = m_From.GetDirectionTo( goal );

				if ( run )
					d |= Direction.Running;

				m_From.SetDirection( d );
				Move( d );

				return Check( m_From.Location, goal, range );
			}

			d = m_From.GetDirectionTo( m_Next );

			if ( run )
				d |= Direction.Running;

			m_From.SetDirection( d );

			MoveResult res = Move( d );

			if ( res == MoveResult.Blocked )
			{
				if ( repathed )
					return false;

				m_Path = null;
				CheckPath();

				if ( !m_Path.Success )
				{
					d = m_From.GetDirectionTo( goal );

					if ( run )
						d |= Direction.Running;

					m_From.SetDirection( d );
					Move( d );

					return Check( m_From.Location, goal, range );
				}

				d = m_From.GetDirectionTo( m_Next );

				if ( run )
					d |= Direction.Running;

				m_From.SetDirection( d );

				res = Move( d );

				if ( res == MoveResult.Blocked )
					return false;
			}

			if ( m_From.X == m_Next.X && m_From.Y == m_Next.Y )
			{
				if ( m_From.Z == m_Next.Z )
				{
					++m_Index;
					Advance( ref m_Next, m_Index );
				}
				else
				{
					m_Path = null;
				}
			}

			return Check( m_From.Location, goal, range );
		}
	}
}