#define PREALLOCATE_NODES
using System.Collections.Generic;
using Pathfinding.Serialization;
using Pathfinding.Util;
using UnityEngine;

namespace Pathfinding {
	/// <summary>Node used for the GridGraph</summary>
	public class GridNode : GridNodeBase {
		public GridNode() {
		}
		public GridNode (AstarPath astar) {
			astar.InitializeNode(this);
		}

#if !ASTAR_NO_GRID_GRAPH
		private static GridGraph[] _gridGraphs = new GridGraph[0];
		public static GridGraph GetGridGraph (uint graphIndex) { return _gridGraphs[(int)graphIndex]; }

		public static void SetGridGraph (int graphIndex, GridGraph graph) {
			if (_gridGraphs.Length <= graphIndex) {
				var gg = new GridGraph[graphIndex+1];
				for (int i = 0; i < _gridGraphs.Length; i++) gg[i] = _gridGraphs[i];
				_gridGraphs = gg;
			}

			_gridGraphs[graphIndex] = graph;
		}

		public static void ClearGridGraph (int graphIndex, GridGraph graph) {
			if (graphIndex < _gridGraphs.Length && _gridGraphs[graphIndex] == graph) {
				_gridGraphs[graphIndex] = null;
			}
		}

		/// <summary>Internal use only</summary>
		internal ushort InternalGridFlags {
			get { return gridFlags; }
			set { gridFlags = value; }
		}

		const int GridFlagsConnectionOffset = 0;
		const int GridFlagsConnectionBit0 = 1 << GridFlagsConnectionOffset;
		const int GridFlagsConnectionMask = 0xFF << GridFlagsConnectionOffset;
		const int GridFlagsAxisAlignedConnectionMask = 0xF << GridFlagsConnectionOffset;

		const int GridFlagsEdgeNodeOffset = 10;
		const int GridFlagsEdgeNodeMask = 1 << GridFlagsEdgeNodeOffset;

		public override bool HasConnectionsToAllEightNeighbours {
			get {
				return (InternalGridFlags & GridFlagsConnectionMask) == GridFlagsConnectionMask;
			}
		}

		public override bool HasConnectionsToAllAxisAlignedNeighbours {
			get {
				return (InternalGridFlags & GridFlagsAxisAlignedConnectionMask) == GridFlagsAxisAlignedConnectionMask;
			}
		}

		/// <summary>
		/// True if the node has a connection in the specified direction.
		/// The dir parameter corresponds to directions in the grid as:
		/// <code>
		///         Z
		///         |
		///         |
		///
		///      6  2  5
		///       \ | /
		/// --  3 - X - 1  ----- X
		///       / | \
		///      7  0  4
		///
		///         |
		///         |
		/// </code>
		///
		/// See: <see cref="SetConnectionInternal"/>
		/// See: <see cref="GridGraph.neighbourXOffsets"/>
		/// See: <see cref="GridGraph.neighbourZOffsets"/>
		/// See: <see cref="GridGraph.neighbourOffsets"/>
		/// See: <see cref="GridGraph.GetNeighbourDirections"/>
		/// </summary>
		public override bool HasConnectionInDirection (int dir) {
			return (gridFlags >> dir & GridFlagsConnectionBit0) != 0;
		}

		/// <summary>
		/// Enables or disables a connection in a specified direction on the graph.
		///
		/// Note: This only changes the connection from this node to the other node. You may also want to call the same method on the other node with the opposite direction.
		///
		/// See: <see cref="HasConnectionInDirection"/>
		/// See: <see cref="OppositeConnectionDirection"/>
		/// </summary>
		public void SetConnection (int dir, bool value) {
			SetConnectionInternal(dir, value);
			var grid = GetGridGraph(GraphIndex);
			grid.nodeDataRef.connections[NodeInGridIndex] = (ulong)GetAllConnectionInternal();
		}

		/// <summary>
		/// Enables or disables a connection in a specified direction on the graph.
		/// See: <see cref="HasConnectionInDirection"/>
		///
		/// Warning: Using this method can make the graph data inconsistent. It's recommended to use other ways to update the graph, instead, for example <see cref="SetConnection"/>.
		/// </summary>
		public void SetConnectionInternal (int dir, bool value) {
			// Set bit number #dir to 1 or 0 depending on #value
			unchecked { gridFlags = (ushort)(gridFlags & ~((ushort)1 << GridFlagsConnectionOffset << dir) | (value ? (ushort)1 : (ushort)0) << GridFlagsConnectionOffset << dir); }
			AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
		}

		/// <summary>
		/// Sets the state of all grid connections.
		///
		/// See: SetConnectionInternal
		///
		/// Warning: Using this method can make the graph data inconsistent. It's recommended to use other ways to update the graph, instead.
		/// </summary>
		/// <param name="connections">a bitmask of the connections (bit 0 is the first connection, bit 1 the second connection, etc.).</param>
		public void SetAllConnectionInternal (int connections) {
			unchecked { gridFlags = (ushort)((gridFlags & ~GridFlagsConnectionMask) | (connections << GridFlagsConnectionOffset)); }
			AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
		}

		/// <summary>Bitpacked int containing all 8 grid connections</summary>
		public int GetAllConnectionInternal () {
			return (gridFlags & GridFlagsConnectionMask) >> GridFlagsConnectionOffset;
		}

		public override bool HasAnyGridConnections => GetAllConnectionInternal() != 0;

		/// <summary>
		/// Disables all grid connections from this node.
		/// Note: Other nodes might still be able to get to this node.
		/// Therefore it is recommended to also disable the relevant connections on adjacent nodes.
		///
		/// Warning: Using this method can make the graph data inconsistent. It's recommended to use other ways to update the graph, instead.
		/// </summary>
		public override void ResetConnectionsInternal () {
			unchecked {
				gridFlags = (ushort)(gridFlags & ~GridFlagsConnectionMask);
			}
			AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
		}

		/// <summary>
		/// Work in progress for a feature that required info about which nodes were at the border of the graph.
		/// Note: This property is not functional at the moment.
		/// </summary>
		public bool EdgeNode {
			get {
				return (gridFlags & GridFlagsEdgeNodeMask) != 0;
			}
			set {
				unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsEdgeNodeMask | (value ? GridFlagsEdgeNodeMask : 0)); }
			}
		}

		public override GridNodeBase GetNeighbourAlongDirection (int direction) {
			if (HasConnectionInDirection(direction)) {
				GridGraph gg = GetGridGraph(GraphIndex);
				return gg.nodes[NodeInGridIndex+gg.neighbourOffsets[direction]];
			}
			return null;
		}

		public override void ClearConnections (bool alsoReverse) {
			if (alsoReverse) {
				// Note: This assumes that all connections are bidirectional
				// which should hold for all grid graphs unless some custom code has been added
				for (int i = 0; i < 8; i++) {
					var other = GetNeighbourAlongDirection(i) as GridNode;
					if (other != null) {
						// Remove reverse connection. See doc for GridGraph.neighbourOffsets to see which indices are used for what.
						other.SetConnectionInternal(OppositeConnectionDirection(i), false);
					}
				}
			}

			ResetConnectionsInternal();

#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
			base.ClearConnections(alsoReverse);
#endif
		}

		public override void GetConnections<T>(NodeActionWithData<T> action, ref T data, int connectionFilter) {
			if ((connectionFilter & (Connection.IncomingConnection | Connection.OutgoingConnection)) == 0) return;

			GridGraph gg = GetGridGraph(GraphIndex);

			var neighbourOffsets = gg.neighbourOffsets;
			var nodes = gg.nodes;

			for (int i = 0; i < 8; i++) {
				if ((gridFlags >> i & GridFlagsConnectionBit0) != 0) {
					var other = nodes[NodeInGridIndex + neighbourOffsets[i]];
					if (other != null) action(other, ref data);
				}
			}

#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
			base.GetConnections(action, ref data, connectionFilter);
#endif
		}

		public override bool GetPortal (GraphNode other, out Vector3 left, out Vector3 right) {
			if (other.GraphIndex != GraphIndex) {
				left = right = Vector3.zero;
				return false;
			}

			GridGraph gg = GetGridGraph(GraphIndex);
			var cellOffset = (other as GridNodeBase).CoordinatesInGrid - CoordinatesInGrid;
			var dir = OffsetToConnectionDirection(cellOffset.x, cellOffset.y);
			if (dir == -1 || !HasConnectionInDirection(dir)) {
				left = right = Vector3.zero;
				return false;
			}

			UnityEngine.Assertions.Assert.AreEqual(other, gg.nodes[NodeInGridIndex + gg.neighbourOffsets[dir]]);

			if (dir < 4) {
				Vector3 middle = ((Vector3)(position + other.position))*0.5f;
				var cross = gg.transform.TransformVector(new Vector3(cellOffset.y, 0, -cellOffset.x)*0.5f);
				left = middle - cross;
				right = middle + cross;
			} else {
				bool rClear = false;
				bool lClear = false;
				if (HasConnectionInDirection(dir-4)) {
					var n2 = gg.nodes[NodeInGridIndex + gg.neighbourOffsets[dir-4]];
					if (n2.Walkable && n2.HasConnectionInDirection((dir-4+1)%4)) {
						rClear = true;
					}
				}

				if (HasConnectionInDirection((dir-4+1)%4)) {
					var n2 = gg.nodes[NodeInGridIndex + gg.neighbourOffsets[(dir-4+1)%4]];
					if (n2.Walkable && n2.HasConnectionInDirection(dir-4)) {
						lClear = true;
					}
				}

				Vector3 middle = ((Vector3)(position + other.position))*0.5f;
				var cross = gg.transform.TransformVector(new Vector3(cellOffset.y, 0, -cellOffset.x));
				left = middle - (lClear ? cross : Vector3.zero);
				right = middle + (rClear ? cross : Vector3.zero);
			}
			return true;
		}

		/// <summary>
		/// Filters diagonal connections based on the non-diagonal ones to prevent corner cutting and similar things.
		///
		/// This involves some complicated bitshifting to calculate which diagonal connections
		/// should be active based on the non-diagonal ones.
		/// For example a path should not be able to pass from A to B if the \<see cref="s"/> represent nodes
		/// that we cannot traverse.
		///
		/// <code>
		///    # B
		///    A #
		/// </code>
		///
		/// Additionally if corner cutting is disabled we will also prevent a connection from A to B in this case:
		///
		/// <code>
		///      B
		///    A #
		/// </code>
		///
		/// If neighbours = 4 then only the 4 axis aligned connections will be enabled.
		///
		/// If neighbours = 6 then only the connections which are valid for hexagonal graphs will be enabled.
		/// </summary>
		public static int FilterDiagonalConnections (int conns, NumNeighbours neighbours, bool cutCorners) {
			switch (neighbours) {
			case NumNeighbours.Four:
				// The first 4 bits are the axis aligned connections
				return conns & 0xF;
			// Default case exists only to make the compiler happy, it is never intended to be used.
			default:
			case NumNeighbours.Eight:
				if (cutCorners) {
					int axisConns = conns & 0xF;
					// If at least one axis aligned connection
					// is adjacent to this diagonal, then we can add a connection.
					// Bitshifting is a lot faster than calling node.HasConnectionInDirection.
					// We need to check if connection i and i+1 are enabled
					// but i+1 may overflow 4 and in that case need to be wrapped around
					// (so 3+1 = 4 goes to 0). We do that by checking both connection i+1
					// and i+1-4 at the same time. Either i+1 or i+1-4 will be in the range
					// from 0 to 4 (exclusive)
					int diagConns = (axisConns | (axisConns >> 1 | axisConns << 3)) << 4;

					// Filter out diagonal connections that are invalid
					// This will also filter out some junk bits which may be set to true above bit 8
					diagConns &= conns;
					return axisConns | diagConns;
				} else {
					int axisConns = conns & 0xF;
					// If exactly 2 axis aligned connections are adjacent to a diagonal connection
					// then the diagonal connection is ok to use.
					int diagConns = (axisConns & (axisConns >> 1 | axisConns << 3)) << 4;

					// Filter out diagonal connections that are invalid
					// This will also filter out some junk bits which may be set above bit 8
					diagConns &= conns;
					return axisConns | diagConns;
				}
			case NumNeighbours.Six:
				// Hexagon layout
				return conns & GridGraph.HexagonConnectionMask;
			}
		}

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

			GridGraph gg = GetGridGraph(GraphIndex);
			int[] neighbourOffsets = gg.neighbourOffsets;
			var nodes = gg.nodes;
			var index = NodeInGridIndex;
			// Bitmask of the 8 connections out of this node
			// Each bit represents one connection.
			var conns = gridFlags & GridFlagsConnectionMask;

			// Loop over all connections, first the 4 axis aligned ones and then the 4 diagonal ones.
			var straightCost = gg.neighbourCosts[0];
			for (int dir = 0; dir < 4; dir++) {
				// Check if we have a connection in this direction
				if (((conns >> dir) & GridFlagsConnectionBit0) != 0) {
					var other = nodes[index + neighbourOffsets[dir]];
					if (ctx.traversalConstraint.CanTraverse(this, other)) {
						if (ctx.ShouldConsiderPathNode(other.NodeIndex)) {
							var connectionCost = ctx.traversalCosts.GetConnectionCost(this, other);
							connectionCost += (uint)Mathf.Round((ourTraversalCostFactor + ctx.traversalCosts.GetTraversalCostMultiplier(other)) * (0.5f * straightCost));
							ctx.OpenCandidateConnection(pathNodeIndex, other.NodeIndex, gScore + connectionCost, 0, other.position);
						}
					} else {
						// Mark that connection as not valid
						conns &= ~(GridFlagsConnectionBit0 << dir);
					}
				}
			}

			// dir=4 is the first diagonal connection.
			// At this point we know exactly which orthogonal (not diagonal) connections are actually traversable.
			// So we do some filtering to determine which diagonals should be traversable.
			//
			// We do this dynamically because each path may use different tags or different
			// ITraversalProviders that affect the result.
			//
			// When the grid graph is scanned this exact method is also run to pre-filter connections
			// based on their walkability values.
			// Doing pre-filtering is good because it allows users to use `HasConnectionInDirection`
			// and it will return accurate values even for diagonals (even though it will of course not
			// take into account any additional constraints such as tags or ITraversalProviders).
			if (ctx.traversalConstraint.filterDiagonalGridConnections) {
				conns = FilterDiagonalConnections(conns, gg.neighbours, gg.cutCorners);
			}

			// Loop over the diagonal connections
			var diagonalCost = gg.neighbourCosts[4];
			for (int dir = 4; dir < 8; dir++) {
				// Check if we have a connection in this direction
				if (((conns >> dir) & GridFlagsConnectionBit0) != 0) {
					var other = nodes[index + neighbourOffsets[dir]];
					if (ctx.ShouldConsiderPathNode(other.NodeIndex) && ctx.traversalConstraint.CanTraverse(this, other)) {
						var connectionCost = ctx.traversalCosts.GetConnectionCost(this, other);
						connectionCost += (uint)Mathf.Round((ourTraversalCostFactor + ctx.traversalCosts.GetTraversalCostMultiplier(other)) * (0.5f * diagonalCost));
						ctx.OpenCandidateConnection(pathNodeIndex, other.NodeIndex, gScore + connectionCost, 0, other.position);
					}
				}
			}

			base.Open(ref ctx, pathNodeIndex, gScore);
		}

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

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

		public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) {
			// In case the node was already added as an internal grid connection,
			// we need to remove that connection before we insert it as a custom connection.
			// Using a custom connection is necessary because it has a custom cost.
			if (node is GridNode gn && gn.GraphIndex == GraphIndex) {
				RemoveGridConnection(gn);
			}
			base.AddPartialConnection(node, cost, isOutgoing, isIncoming);
		}

		public override void RemovePartialConnection (GraphNode node) {
			base.RemovePartialConnection(node);
			// If the node is a grid node on the same graph, it might be added as an internal connection and not a custom one.
			if (node is GridNode gn && gn.GraphIndex == GraphIndex) {
				RemoveGridConnection(gn);
			}
		}

		/// <summary>
		/// Removes a connection from the internal grid connections.
		/// See: SetConnectionInternal
		/// </summary>
		protected void RemoveGridConnection (GridNode node) {
			var nodeIndex = NodeInGridIndex;
			var gg = GetGridGraph(GraphIndex);

			for (int i = 0; i < 8; i++) {
				if (nodeIndex + gg.neighbourOffsets[i] == node.NodeInGridIndex && GetNeighbourAlongDirection(i) == node) {
					SetConnectionInternal(i, false);
					break;
				}
			}
		}
#else
		public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) {
			throw new System.NotImplementedException();
		}

		public override void ClearConnections (bool alsoReverse) {
			throw new System.NotImplementedException();
		}

		public override void GetConnections (GraphNodeDelegate del) {
			throw new System.NotImplementedException();
		}

		public override void Open (ref Path.SearchContext ctx, PathNode pathNode, PathHandler handler) {
			throw new System.NotImplementedException();
		}

		public override void AddPartialConnection (GraphNode node) {
			throw new System.NotImplementedException();
		}
#endif
	}
}
