using UnityEngine;
using Pathfinding.Serialization;

namespace Pathfinding {
	/// <summary>
	/// Node used for the PointGraph.
	/// This is just a simple point with a list of connections (and associated costs) to other nodes.
	/// It does not have any concept of a surface like many other node types.
	///
	/// See: PointGraph
	/// </summary>
	public class PointNode : GraphNode {
		/// <summary>
		/// All connections from this node.
		/// See: <see cref="Connect"/>
		/// See: <see cref="Disconnect"/>
		/// See: <see cref="GetConnections"/>
		///
		/// Note: If you modify this array or the contents of it you must call <see cref="SetConnectivityDirty"/>.
		///
		/// Note: If you modify this array or the contents of it you must call <see cref="PointGraph.RegisterConnectionLength"/> with the length of the new connections.
		///
		/// This may be null if the node has no connections to other nodes.
		/// </summary>
		public Connection[] connections;

		/// <summary>
		/// GameObject this node was created from (if any).
		/// Warning: When loading a graph from a saved file or from cache, this field will be null.
		///
		/// <code>
		/// var node = AstarPath.active.GetNearest(transform.position).node;
		/// var pointNode = node as PointNode;
		///
		/// if (pointNode != null) {
		///     Debug.Log("That node was created from the GameObject named " + pointNode.gameObject.name);
		/// } else {
		///     Debug.Log("That node is not a PointNode");
		/// }
		/// </code>
		/// </summary>
		public GameObject gameObject;

		[System.Obsolete("Set node.position instead")]
		public void SetPosition (Int3 value) {
			position = value;
		}

		public PointNode() { }
		public PointNode (AstarPath astar) {
			astar.InitializeNode(this);
		}

		/// <summary>
		/// Closest point on the surface of this node to the point p.
		///
		/// For a point node this is always the node's <see cref="position"/> sicne it has no surface.
		/// </summary>
		public override Vector3 ClosestPointOnNode (Vector3 p) {
			return (Vector3)this.position;
		}

		/// <summary>
		/// Checks if point is inside the node when seen from above.
		///
		/// Since point nodes have no surface area, this method always returns false.
		/// </summary>
		public override bool ContainsPoint (Vector3 point) {
			return false;
		}

		/// <summary>
		/// Checks if point is inside the node in graph space.
		///
		/// Since point nodes have no surface area, this method always returns false.
		/// </summary>
		public override bool ContainsPointInGraphSpace (Int3 point) {
			return false;
		}

		public override void GetConnections<T>(NodeActionWithData<T> action, ref T data, int connectionFilter) {
			if (connections == null) return;
			for (int i = 0; i < connections.Length; i++) if ((connections[i].shapeEdgeInfo & connectionFilter) != 0) action(connections[i].node, ref data);
		}

		public override void ClearConnections (bool alsoReverse) {
			if (alsoReverse && connections != null) {
				for (int i = 0; i < connections.Length; i++) {
					connections[i].node.RemovePartialConnection(this);
				}
			}

			connections = null;
			AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
		}

		public override bool ContainsOutgoingConnection (GraphNode node) {
			if (connections == null) return false;
			for (int i = 0; i < connections.Length; i++) if (connections[i].node == node && connections[i].isOutgoing) return true;
			return false;
		}

		public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) {
			if (node == null) throw new System.ArgumentNullException();

			if (connections != null) {
				for (int i = 0; i < connections.Length; i++) {
					if (connections[i].node == node) {
						connections[i].cost = cost;
						connections[i].shapeEdgeInfo = Connection.PackShapeEdgeInfo(isOutgoing, isIncoming);
						return;
					}
				}
			}

			int connLength = connections != null ? connections.Length : 0;

			var newconns = new Connection[connLength+1];
			for (int i = 0; i < connLength; i++) {
				newconns[i] = connections[i];
			}

			newconns[connLength] = new Connection(node, cost, isOutgoing, isIncoming);

			connections = newconns;
			AstarPath.active.hierarchicalGraph.AddDirtyNode(this);

			// Make sure the graph knows that there exists a connection with this length
			if (this.Graph is PointGraph pg) pg.RegisterConnectionLength((node.position - position).sqrMagnitudeLong);
		}

		public override void RemovePartialConnection (GraphNode node) {
			if (connections == null) return;

			for (int i = 0; i < connections.Length; i++) {
				if (connections[i].node == node) {
					int connLength = connections.Length;

					var newconns = new Connection[connLength-1];
					for (int j = 0; j < i; j++) {
						newconns[j] = connections[j];
					}
					for (int j = i+1; j < connLength; j++) {
						newconns[j-1] = connections[j];
					}

					connections = newconns;
					AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
					return;
				}
			}
		}

		public override void Open (ref Path.SearchContext ctx, uint pathNodeIndex, uint gScore) {
			var ourTraversalCostFactor = ctx.traversalCosts.GetTraversalCostMultiplier(this);
			ctx.OpenCandidateConnectionsToEndNode(position, pathNodeIndex, pathNodeIndex, gScore, ourTraversalCostFactor);

			if (connections == null) return;

			for (int i = 0; i < connections.Length; i++) {
				GraphNode other = connections[i].node;

				if (connections[i].isOutgoing && ctx.traversalConstraint.CanTraverse(this, other)) {
					var connectionCost = ctx.traversalCosts.GetConnectionCost(this, other);
					if (other is PointNode) {
						// Half the traversal is done on this node and half on the other node.
						connectionCost += (uint)System.Math.Round(connections[i].cost * 0.5f * (ourTraversalCostFactor + ctx.traversalCosts.GetTraversalCostMultiplier(other)));
						ctx.OpenCandidateConnection(pathNodeIndex, other.NodeIndex, gScore + connectionCost, 0, other.position);
					} else {
						// When connecting to a non-point node, use a special function to open the connection.
						// The typical case for this is that we are at the end of something similar to an off-mesh link and we are connecting to a navmesh node.
						// In that case, this node's position is in the interior of the navmesh node. We let the navmesh node decide how
						// that should be handled.
						// The built-in off mesh links are handled using the LinkNode class, and it behaves in a similar way.
						other.OpenAtPoint(ref ctx, pathNodeIndex, position, gScore + connectionCost);
					}
				}
			}
		}

		public override void OpenAtPoint (ref Path.SearchContext ctx, uint pathNodeIndex, Int3 pos, uint gScore) {
			// TODO: Ideally we should only allow connections to the temporary end node directly from the temporary start node
			// iff they lie on the same connection edge. Otherwise we need to pass through the center of this node.
			//
			//   N1---E----N2
			//   |   /
			//   | /
			//   S
			//   |
			//   N3
			//
			var ourTraversalCostFactor = ctx.traversalCosts.GetTraversalCostMultiplier(this);
			ctx.OpenCandidateConnectionsToEndNode(pos, pathNodeIndex, pathNodeIndex, gScore, ourTraversalCostFactor);

			var connectionCost = (uint)System.Math.Round((pos - this.position).magnitude * ourTraversalCostFactor);
			ctx.OpenCandidateConnection(pathNodeIndex, NodeIndex, gScore + connectionCost, 0, position);
		}

		public override int GetGizmoHashCode () {
			var hash = base.GetGizmoHashCode();

			if (connections != null) {
				for (int i = 0; i < connections.Length; i++) {
					hash ^= 17 * connections[i].GetHashCode();
				}
			}
			return hash;
		}

		public override void SerializeNode (GraphSerializationContext ctx) {
			base.SerializeNode(ctx);
			ctx.SerializeInt3(position);
		}

		public override void DeserializeNode (GraphSerializationContext ctx) {
			base.DeserializeNode(ctx);
			position = ctx.DeserializeInt3();
		}

		public override void SerializeReferences (GraphSerializationContext ctx) {
			ctx.SerializeConnections(connections, true);
		}

		public override void DeserializeReferences (GraphSerializationContext ctx) {
			connections = ctx.DeserializeConnections(ctx.meta.version >= AstarSerializer.V4_3_85);
		}
	}
}
