using UnityEngine;
using Pathfinding.Pooling;

namespace Pathfinding {
	/// <summary>
	/// Traces a path created with the Pathfinding.FloodPath.
	///
	/// See Pathfinding.FloodPath for examples on how to use this path type
	///
	/// [Open online documentation to see images]
	/// </summary>
	public class FloodPathTracer : ABPath {
		/// <summary>Reference to the FloodPath which searched the path originally</summary>
		protected FloodPath flood;

		protected override bool hasEndPoint => false;

		/// <summary>
		/// Default constructor.
		/// Do not use this. Instead use the static Construct method which can handle path pooling.
		/// </summary>
		public FloodPathTracer () {}

		public static FloodPathTracer Construct (Vector3 start, FloodPath flood, OnPathDelegate callback = null) {
			var p = PathPool.GetPath<FloodPathTracer>();

			p.Setup(start, flood, callback);
			return p;
		}

		protected void Setup (Vector3 start, FloodPath flood, OnPathDelegate callback) {
			this.flood = flood;
			this.traversalConstraint = flood.traversalConstraint;

			if (flood == null || flood.PipelineState < PathState.Returning) {
				throw new System.ArgumentException("You must supply a calculated FloodPath to the 'flood' argument");
			}

			base.Setup(start, flood.originalStartPoint, callback);
		}

		/// <summary>
		/// Refreshes the start and end points of the path.
		/// Useful if the agent has moved significantly since the path was requested.
		///
		/// If the path has not yet been processed by the pathfinding system, the start and end points will be updated immediately.
		/// If the path is being calculated or has been calculated, this function will do nothing.
		///
		/// A common way is to call this function once per frame until the path has been calculated,
		/// with the most up-to-date positions of the agent and the target.
		///
		/// Note: For a FloodPathTracer, this method only updates the end point, since the start point is determined by the flood path passed to the constructor.
		/// </summary>
		public override void RefreshPathEndpoints (Vector3 start, Vector3 end) {
			lock (this) {
				if (PipelineState == PathState.PathQueue || PipelineState == PathState.Created) {
					originalEndPoint = endPoint = end;
				}
			}
		}

		protected override void Reset () {
			base.Reset();
			flood = null;
		}

		/// <summary>
		/// Initializes the path.
		/// Traces the path from the start node.
		/// </summary>
		protected override void Prepare (ref SearchContext ctx) {
			var pathHandler = ctx.pathHandler;
			if (!this.flood.IsValid(pathHandler.nodeStorage)) {
				FailWithError("The flood path is invalid because nodes have been destroyed since it was calculated. Please recalculate the flood path.");
				return;
			}

			var nn = this.nearestNodeConstraint;
			var origConstraint = nn; // If the path originally used a ITraversalProvider or filter function, we need to check that as well
			nn.filter = (GraphNode node) => flood.HasPathTo(node) && origConstraint.Suitable(node);
			FindStartAndEndNodes(ref ctx, ref nn);

			for (uint i = 0; i < pathHandler.numTemporaryNodes; i++) {
				var nodeIndex = pathHandler.temporaryNodeStartIndex + i;
				ref var tempNode = ref pathHandler.GetTemporaryNode(nodeIndex);
				if (tempNode.type == TemporaryNodeType.Start) {
					var node = pathHandler.GetNode(tempNode.associatedNode);

					// It is guaranteed that we find a valid node, since we added a filter to the NearestNodeConstraint above
					bool found = false;
					for (uint k = 0; k < node.PathNodeVariants; k++) {
						if (flood.GetParent(node.NodeIndex + k) != 0) {
							found = true;
							OnFoundEndNode(ref ctx, node.NodeIndex + k, 0, 0);
							break;
						}
					}
					if (!found) {
						FailWithError("The flood path did not contain any information about the end node. This should not happen.");
					}
					return;
				}
			}

			FailWithError("Could not find a valid start node");
		}

		protected override void CalculateStep (ref SearchContext ctx, long targetTick) {
			if (CompleteState != PathCompleteState.Complete) throw new System.Exception("Something went wrong. At this point the path should be completed");
		}

		/// <summary>
		/// Traces the calculated path from the start node to the end.
		/// This will build an array (<see cref="path)"/> of the nodes this path will pass through and also set the <see cref="vectorPath"/> array to the <see cref="path"/> arrays positions.
		/// This implementation will use the <see cref="flood"/> (FloodPath) to trace the path from precalculated data.
		/// </summary>
		protected override void Trace (ref SearchContext ctx, uint fromPathNodeIndex) {
			uint pathNodeIndex = fromPathNodeIndex;
			int count = 0;
			GraphNode lastNode = null;
			var pathHandler = ctx.pathHandler;

			while (pathNodeIndex != 0) {
				if ((pathNodeIndex & FloodPath.TemporaryNodeBit) != 0) {
					// Skip over temporary nodes
					pathNodeIndex = flood.GetParent(pathNodeIndex & ~FloodPath.TemporaryNodeBit);
				} else {
					var node = pathHandler.GetNode(pathNodeIndex);
					if (node == null) {
						FailWithError("A node in the path has been destroyed. The FloodPath needs to be recalculated before you can use a FloodPathTracer.");
						return;
					}

					// If a node has multiple variants (like the triangle mesh node), then we may visit
					// the same node multiple times in a sequence (but different variants of it).
					// In the final path we don't want the duplicates.
					if (node != lastNode) {
						if (!traversalConstraint.CanTraverse(node)) {
							FailWithError("A node in the path is no longer walkable. The FloodPath needs to be recalculated before you can use a FloodPathTracer.");
							return;
						}
						path.Add(node);
						lastNode = node;
						vectorPath.Add((Vector3)node.position);
					}
					var next = flood.GetParent(pathNodeIndex);
					if (next == pathNodeIndex) {
						break;
					}
					pathNodeIndex = next;
				}

				count++;
				if (count > 10000) {
					Debug.LogWarning("Infinite loop? >10000 node path. Remove this message if you really have that long paths");
					break;
				}
			}
		}
	}
}
