using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using HG;
using Unity.Collections;
using UnityEngine;

namespace RoR2.Navigation;

[CreateAssetMenu(menuName = "RoR2/NodeGraph")]
[PreferBinarySerialization]
public class NodeGraph : ScriptableObject
{
	[Serializable]
	public struct NodeIndex : IEquatable<NodeIndex>
	{
		public int nodeIndex;

		public static readonly NodeIndex invalid = new NodeIndex(-1);

		public NodeIndex(int nodeIndex)
		{
			this.nodeIndex = nodeIndex;
		}

		public static bool operator ==(NodeIndex lhs, NodeIndex rhs)
		{
			return lhs.nodeIndex == rhs.nodeIndex;
		}

		public static bool operator !=(NodeIndex lhs, NodeIndex rhs)
		{
			return lhs.nodeIndex != rhs.nodeIndex;
		}

		public override bool Equals(object other)
		{
			if (other is NodeIndex)
			{
				return ((NodeIndex)other).nodeIndex == nodeIndex;
			}
			return false;
		}

		public override int GetHashCode()
		{
			return nodeIndex;
		}

		public bool Equals(NodeIndex other)
		{
			return nodeIndex == other.nodeIndex;
		}
	}

	[Serializable]
	public struct LinkIndex
	{
		public int linkIndex;

		public static readonly LinkIndex invalid = new LinkIndex
		{
			linkIndex = -1
		};

		public static bool operator ==(LinkIndex lhs, LinkIndex rhs)
		{
			return lhs.linkIndex == rhs.linkIndex;
		}

		public static bool operator !=(LinkIndex lhs, LinkIndex rhs)
		{
			return lhs.linkIndex != rhs.linkIndex;
		}

		public override bool Equals(object other)
		{
			if (other is LinkIndex)
			{
				return ((LinkIndex)other).linkIndex == linkIndex;
			}
			return false;
		}

		public override int GetHashCode()
		{
			return linkIndex;
		}
	}

	[Serializable]
	public struct LinkListIndex
	{
		public int index;

		public uint size;
	}

	[Serializable]
	public struct Node
	{
		public Vector3 position;

		public LinkListIndex linkListIndex;

		public HullMask forbiddenHulls;

		public SerializableBitArray lineOfSightMask;

		public byte gateIndex;

		public NodeFlags flags;
	}

	[Serializable]
	public struct Link
	{
		public NodeIndex nodeIndexA;

		public NodeIndex nodeIndexB;

		public float distanceScore;

		public float maxSlope;

		public float minJumpHeight;

		public int hullMask;

		public int jumpHullMask;

		public byte gateIndex;
	}

	private struct NodePositionGetter : IPosition3Getter<NodeIndex>
	{
		private readonly NodeGraph nodeGraph;

		public NodePositionGetter(NodeGraph nodeGraph)
		{
			this.nodeGraph = nodeGraph;
		}

		public Vector3 GetPosition3(NodeIndex item)
		{
			return nodeGraph.nodes[item.nodeIndex].position;
		}
	}

	public readonly struct PathRequestPosition
	{
		public readonly NodeIndex? nodeIndex;

		public readonly Vector3? position;

		private PathRequestPosition(NodeIndex nodeIndex)
		{
			this.nodeIndex = nodeIndex;
			position = null;
		}

		private PathRequestPosition(Vector3 position)
		{
			nodeIndex = null;
			this.position = position;
		}

		public static implicit operator PathRequestPosition(NodeIndex nodeIndex)
		{
			return new PathRequestPosition(nodeIndex);
		}

		public static implicit operator PathRequestPosition(Vector3 position)
		{
			return new PathRequestPosition(position);
		}
	}

	public class PathRequest
	{
		public Path path;

		public PathRequestPosition startPos;

		public PathRequestPosition endPos;

		public HullClassification hullClassification;

		public float maxSlope;

		public float maxJumpHeight;

		public float maxSpeed;

		public PathRequest()
		{
			Init();
		}

		public void Reset()
		{
			Init();
		}

		private void Init()
		{
			path = null;
			startPos = NodeIndex.invalid;
			endPos = NodeIndex.invalid;
			hullClassification = HullClassification.Human;
			maxSlope = 0f;
			maxJumpHeight = 0f;
			maxSpeed = 0f;
		}
	}

	private static class NodeFilters
	{
		public interface INodeCheckFilterComponent
		{
			bool CheckNode(NodeGraph nodeGraph, ref Node node);
		}

		public struct NodeCheckFilterAnd<TFilterA, TFilterB> : INodeCheckFilterComponent where TFilterA : INodeCheckFilterComponent where TFilterB : INodeCheckFilterComponent
		{
			private TFilterA filterA;

			private TFilterB filterB;

			public NodeCheckFilterAnd(TFilterA filterA, TFilterB filterB)
			{
				this.filterA = filterA;
				this.filterB = filterB;
			}

			public bool CheckNode(NodeGraph nodeGraph, ref Node node)
			{
				if (filterA.CheckNode(nodeGraph, ref node))
				{
					return filterB.CheckNode(nodeGraph, ref node);
				}
				return false;
			}
		}

		public struct NodeSearchFilter<TNodeCheckFilter> : IBlockMapSearchFilter<NodeIndex> where TNodeCheckFilter : INodeCheckFilterComponent
		{
			private readonly NodeGraph nodeGraph;

			private TNodeCheckFilter nodeCheckFilter;

			public NodeSearchFilter(NodeGraph nodeGraph, TNodeCheckFilter nodeCheckFilter)
			{
				this.nodeGraph = nodeGraph;
				this.nodeCheckFilter = nodeCheckFilter;
			}

			public bool CheckItem(NodeIndex item, ref bool shouldFinish)
			{
				return nodeCheckFilter.CheckNode(nodeGraph, ref nodeGraph.nodes[item.nodeIndex]);
			}
		}

		public readonly struct NodeHullFilter : INodeCheckFilterComponent
		{
			private readonly HullMask hullMask;

			public NodeHullFilter(HullMask hullMask)
			{
				this.hullMask = hullMask;
			}

			public NodeHullFilter(HullClassification hullClassification)
			{
				hullMask = (HullMask)(1 << (int)hullClassification);
			}

			public bool CheckNode(NodeGraph nodeGraph, ref Node node)
			{
				return (node.forbiddenHulls & hullMask) == 0;
			}
		}

		public readonly struct NodeFlagsFilter : INodeCheckFilterComponent
		{
			private readonly NodeFlags requiredFlags;

			private readonly NodeFlags forbiddenFlags;

			public NodeFlagsFilter(NodeFlags requiredFlags, NodeFlags forbiddenFlags)
			{
				this.requiredFlags = requiredFlags;
				this.forbiddenFlags = forbiddenFlags;
			}

			public bool CheckNode(NodeGraph nodeGraph, ref Node node)
			{
				NodeFlags flags = node.flags;
				if ((flags & forbiddenFlags) == 0)
				{
					return (flags & requiredFlags) == requiredFlags;
				}
				return false;
			}
		}

		[StructLayout(LayoutKind.Sequential, Size = 1)]
		public readonly struct NodeAvailableFilter : INodeCheckFilterComponent
		{
			public bool CheckNode(NodeGraph nodeGraph, ref Node node)
			{
				if (node.gateIndex != 0)
				{
					return nodeGraph.openGates[node.gateIndex];
				}
				return true;
			}
		}

		public struct NodeMinDistanceFilter : INodeCheckFilterComponent
		{
			private readonly Vector3 position;

			private readonly float minDistanceSqr;

			public NodeMinDistanceFilter(Vector3 position, float minDistance)
			{
				this.position = position;
				minDistanceSqr = minDistance * minDistance;
			}

			public bool CheckNode(NodeGraph nodeGraph, ref Node node)
			{
				return (node.position - position).sqrMagnitude >= minDistanceSqr;
			}
		}

		public struct NodeRaycastFilter : INodeCheckFilterComponent
		{
			private readonly Vector3 raycastOrigin;

			private readonly Vector3 raycastOffset;

			private readonly int maxRaycasts;

			private int raycastsPerformed;

			public NodeRaycastFilter(Vector3 raycastOrigin, Vector3 raycastOffset, int maxRaycasts)
			{
				this.raycastOrigin = raycastOrigin + raycastOffset;
				this.raycastOffset = raycastOffset;
				this.maxRaycasts = maxRaycasts;
				raycastsPerformed = 0;
			}

			public bool CheckNode(NodeGraph nodeGraph, ref Node node)
			{
				if (raycastsPerformed < maxRaycasts)
				{
					raycastsPerformed++;
					if (Physics.Linecast(raycastOrigin, node.position + raycastOffset, out var _, LayerIndex.world.mask, QueryTriggerInteraction.Ignore))
					{
						return false;
					}
				}
				return true;
			}
		}

		public struct NodeOverheadFilter : INodeCheckFilterComponent
		{
			private Vector3 position;

			private bool enabled;

			public NodeOverheadFilter(Vector3 position, bool enabled)
			{
				this.position = position;
				this.enabled = enabled;
			}

			public bool CheckNode(NodeGraph nodeGraph, ref Node node)
			{
				if (enabled && Vector3.Dot((node.position - position).normalized, Vector3.up) > 0.70710677f)
				{
					return false;
				}
				return true;
			}
		}

		public static NodeSearchFilter<TFilter> Create<TFilter>(NodeGraph nodeGraph, TFilter nodeCheckFilter) where TFilter : INodeCheckFilterComponent
		{
			return new NodeSearchFilter<TFilter>(nodeGraph, nodeCheckFilter);
		}

		public static NodeCheckFilterAnd<TFilterA, TFilterB> And<TFilterA, TFilterB>(TFilterA filterA, TFilterB filterB) where TFilterA : INodeCheckFilterComponent where TFilterB : INodeCheckFilterComponent
		{
			return new NodeCheckFilterAnd<TFilterA, TFilterB>(filterA, filterB);
		}

		public static NodeCheckFilterAnd<TFilterA, NodeCheckFilterAnd<TFilterB, TFilterC>> And<TFilterA, TFilterB, TFilterC>(TFilterA filterA, TFilterB filterB, TFilterC filterC) where TFilterA : INodeCheckFilterComponent where TFilterB : INodeCheckFilterComponent where TFilterC : INodeCheckFilterComponent
		{
			return And(filterA, And(filterB, filterC));
		}

		public static NodeCheckFilterAnd<TFilterA, NodeCheckFilterAnd<TFilterB, NodeCheckFilterAnd<TFilterC, TFilterD>>> And<TFilterA, TFilterB, TFilterC, TFilterD>(TFilterA filterA, TFilterB filterB, TFilterC filterC, TFilterD filterD) where TFilterA : INodeCheckFilterComponent where TFilterB : INodeCheckFilterComponent where TFilterC : INodeCheckFilterComponent where TFilterD : INodeCheckFilterComponent
		{
			return And(filterA, And(filterB, filterC, filterD));
		}

		public static NodeCheckFilterAnd<TFilterA, NodeCheckFilterAnd<TFilterB, NodeCheckFilterAnd<TFilterC, NodeCheckFilterAnd<TFilterD, TFilterE>>>> And<TFilterA, TFilterB, TFilterC, TFilterD, TFilterE>(TFilterA filterA, TFilterB filterB, TFilterC filterC, TFilterD filterD, TFilterE filterE) where TFilterA : INodeCheckFilterComponent where TFilterB : INodeCheckFilterComponent where TFilterC : INodeCheckFilterComponent where TFilterD : INodeCheckFilterComponent where TFilterE : INodeCheckFilterComponent
		{
			return And(filterA, And(filterB, filterC, filterD, filterE));
		}
	}

	[SerializeField]
	private Node[] nodes = Array.Empty<Node>();

	[SerializeField]
	private Link[] links = Array.Empty<Link>();

	[SerializeField]
	private List<string> gateNames = new List<string> { "" };

	private bool[] openGates = new bool[256];

	public Vector3 cellSize = new Vector3(15f, 30f, 15f);

	private BlockMap<NodeIndex, NodePositionGetter> blockMap = new BlockMap<NodeIndex, NodePositionGetter>();

	private FixedSizeArrayPool<bool> boolPerNodePool = new FixedSizeArrayPool<bool>(0);

	private FixedSizeArrayPool<float> floatPerNodePool = new FixedSizeArrayPool<float>(0);

	private FixedSizeArrayPool<NodeIndex> nodePerNodePool = new FixedSizeArrayPool<NodeIndex>(0);

	private FixedSizeArrayPool<LinkIndex> linkPerNodePool = new FixedSizeArrayPool<LinkIndex>(0);

	private const float overheadDotLimit = 0.70710677f;

	private void OnNodeCountChanged()
	{
		boolPerNodePool.lengthOfArrays = nodes.Length;
		floatPerNodePool.lengthOfArrays = nodes.Length;
		nodePerNodePool.lengthOfArrays = nodes.Length;
		linkPerNodePool.lengthOfArrays = nodes.Length;
	}

	private void Awake()
	{
		OnNodeCountChanged();
		RebuildBlockMap();
	}

	private void RebuildBlockMap()
	{
		NativeArray<NodeIndex> nativeArray = new NativeArray<NodeIndex>(nodes.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
		for (int i = 0; i < nodes.Length; i++)
		{
			nativeArray[i] = new NodeIndex(i);
		}
		try
		{
			blockMap.SetCellSize(cellSize);
			blockMap.Set(new NativeArrayIListWrapper<NodeIndex>(nativeArray), nativeArray.Length, new NodePositionGetter(this));
		}
		finally
		{
			nativeArray.Dispose();
		}
	}

	public void Clear()
	{
		nodes = Array.Empty<Node>();
		links = Array.Empty<Link>();
		gateNames.Clear();
		gateNames.Add("");
		blockMap.Reset();
		OnNodeCountChanged();
	}

	public void SetNodes(ReadOnlyCollection<MapNode> mapNodes, ReadOnlyCollection<SerializableBitArray> lineOfSightMasks)
	{
		Clear();
		Dictionary<MapNode, NodeIndex> dictionary = new Dictionary<MapNode, NodeIndex>();
		List<Node> list = new List<Node>();
		List<Link> list2 = new List<Link>();
		for (int i = 0; i < mapNodes.Count; i++)
		{
			MapNode key = mapNodes[i];
			dictionary[key] = new NodeIndex(i);
		}
		for (int j = 0; j < mapNodes.Count; j++)
		{
			MapNode mapNode = mapNodes[j];
			NodeIndex nodeIndexA = dictionary[mapNode];
			int count = list2.Count;
			for (int k = 0; k < mapNode.links.Count; k++)
			{
				MapNode.Link link = mapNode.links[k];
				if (!dictionary.ContainsKey(link.nodeB))
				{
					Debug.LogErrorFormat(link.nodeB, "[{0}] Node {1} was not registered.", k, link.nodeB);
				}
				list2.Add(new Link
				{
					nodeIndexA = nodeIndexA,
					nodeIndexB = dictionary[link.nodeB],
					distanceScore = link.distanceScore,
					minJumpHeight = link.minJumpHeight,
					hullMask = link.hullMask,
					jumpHullMask = link.jumpHullMask,
					gateIndex = RegisterGateName(link.gateName)
				});
			}
			HullMask hullMask = mapNode.forbiddenHulls;
			for (HullClassification hullClassification = HullClassification.Human; hullClassification < HullClassification.Count; hullClassification++)
			{
				bool flag = false;
				int num = 1 << (int)hullClassification;
				List<MapNode.Link> list3 = mapNode.links;
				for (int l = 0; l < list3.Count; l++)
				{
					if ((list3[l].hullMask & num) != 0)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					hullMask = (HullMask)((int)hullMask | num);
				}
			}
			list.Add(new Node
			{
				position = mapNode.transform.position,
				linkListIndex = new LinkListIndex
				{
					index = count,
					size = (uint)mapNode.links.Count
				},
				forbiddenHulls = hullMask,
				flags = mapNode.flags,
				lineOfSightMask = new SerializableBitArray(lineOfSightMasks[j]),
				gateIndex = RegisterGateName(mapNode.gateName)
			});
		}
		nodes = list.ToArray();
		links = list2.ToArray();
		OnNodeCountChanged();
		RebuildBlockMap();
	}

	public Vector3 GetQuadraticCoordinates(float t, Vector3 startPos, Vector3 apexPos, Vector3 endPos)
	{
		return Mathf.Pow(1f - t, 2f) * startPos + 2f * t * (1f - t) * apexPos + Mathf.Pow(t, 2f) * endPos;
	}

	public Mesh GenerateLinkDebugMesh(HullMask hullMask)
	{
		using WireMeshBuilder wireMeshBuilder = new WireMeshBuilder();
		Link[] array = links;
		for (int i = 0; i < array.Length; i++)
		{
			Link link = array[i];
			if (((uint)link.hullMask & (uint)hullMask) == 0)
			{
				continue;
			}
			Vector3 position = nodes[link.nodeIndexA.nodeIndex].position;
			Vector3 position2 = nodes[link.nodeIndexB.nodeIndex].position;
			Vector3 vector = (position + position2) * 0.5f;
			bool num = ((uint)link.jumpHullMask & (uint)hullMask) != 0;
			Color color = (num ? Color.cyan : Color.green);
			if (num)
			{
				Vector3 apexPos = vector;
				apexPos.y = position.y + link.minJumpHeight;
				int num2 = 8;
				Vector3 p = position;
				for (int j = 1; j <= num2; j++)
				{
					if (j > num2 / 2)
					{
						color.a = 0.1f;
					}
					Vector3 quadraticCoordinates = GetQuadraticCoordinates((float)j / (float)num2, position, apexPos, position2);
					wireMeshBuilder.AddLine(p, color, quadraticCoordinates, color);
					p = quadraticCoordinates;
				}
			}
			else
			{
				Color c = color;
				c.a = 0.1f;
				wireMeshBuilder.AddLine(position, color, (position + position2) * 0.5f, c);
			}
		}
		return wireMeshBuilder.GenerateMesh();
	}

	public void DebugDrawLinks(HullClassification hull)
	{
		int num = 1 << (int)hull;
		Link[] array = links;
		for (int i = 0; i < array.Length; i++)
		{
			Link link = array[i];
			if ((link.hullMask & num) == 0)
			{
				continue;
			}
			Vector3 position = nodes[link.nodeIndexA.nodeIndex].position;
			Vector3 position2 = nodes[link.nodeIndexB.nodeIndex].position;
			Vector3 vector = (position + position2) * 0.5f;
			bool num2 = (link.jumpHullMask & num) != 0;
			Color color = (num2 ? Color.cyan : Color.green);
			if (num2)
			{
				Vector3 apexPos = vector;
				apexPos.y = position.y + link.minJumpHeight;
				int num3 = 8;
				Vector3 start = position;
				for (int j = 1; j <= num3; j++)
				{
					if (j > num3 / 2)
					{
						color.a = 0.1f;
					}
					Vector3 quadraticCoordinates = GetQuadraticCoordinates((float)j / (float)num3, position, apexPos, position2);
					Debug.DrawLine(start, quadraticCoordinates, color, 10f);
					start = quadraticCoordinates;
				}
			}
			else
			{
				Debug.DrawLine(position, vector, color, 10f, depthTest: false);
				Color color2 = color;
				color2.a = 0.1f;
				Debug.DrawLine(vector, position2, color2, 10f, depthTest: false);
			}
		}
	}

	public void DebugDrawPath(Vector3 startPos, Vector3 endPos)
	{
		Path path = new Path(this);
		ComputePath(new PathRequest
		{
			startPos = startPos,
			endPos = endPos,
			path = path,
			hullClassification = HullClassification.Human
		}).Wait();
		if (path.status == PathStatus.Valid)
		{
			for (int i = 1; i < path.waypointsCount; i++)
			{
				Debug.DrawLine(nodes[path[i - 1].nodeIndex.nodeIndex].position, nodes[path[i].nodeIndex.nodeIndex].position, Color.red, 10f);
			}
		}
	}

	public void DebugHighlightNodesWithNoLinks()
	{
		Node[] array = nodes;
		for (int i = 0; i < array.Length; i++)
		{
			Node node = array[i];
			if (node.linkListIndex.size == 0)
			{
				Debug.DrawRay(node.position, Vector3.up * 100f, Color.cyan, 60f);
			}
		}
	}

	public int GetNodeCount()
	{
		return nodes.Length;
	}

	public List<NodeIndex> GetActiveNodesForHullMask(HullMask hullMask)
	{
		List<NodeIndex> list = new List<NodeIndex>(nodes.Length);
		GetActiveNodesForHullMask(hullMask, list);
		return list;
	}

	public void GetActiveNodesForHullMask(HullMask hullMask, List<NodeIndex> dest)
	{
		dest.Capacity = Math.Max(dest.Capacity, nodes.Length);
		for (int i = 0; i < nodes.Length; i++)
		{
			if ((nodes[i].forbiddenHulls & hullMask) == 0 && (nodes[i].gateIndex == 0 || openGates[nodes[i].gateIndex]))
			{
				dest.Add(new NodeIndex(i));
			}
		}
	}

	public List<NodeIndex> GetActiveNodesForHullMaskWithFlagConditions(HullMask hullMask, NodeFlags requiredFlags, NodeFlags forbiddenFlags)
	{
		List<NodeIndex> list = new List<NodeIndex>(nodes.Length);
		GetActiveNodesForHullMaskWithFlagConditions(hullMask, requiredFlags, forbiddenFlags, list);
		return list;
	}

	public void GetActiveNodesForHullMaskWithFlagConditions(HullMask hullMask, NodeFlags requiredFlags, NodeFlags forbiddenFlags, List<NodeIndex> dest)
	{
		for (int i = 0; i < nodes.Length; i++)
		{
			NodeFlags flags = nodes[i].flags;
			if ((flags & forbiddenFlags) == 0 && (flags & requiredFlags) == requiredFlags && (nodes[i].forbiddenHulls & hullMask) == 0 && (nodes[i].gateIndex == 0 || openGates[nodes[i].gateIndex]))
			{
				dest.Add(new NodeIndex(i));
			}
		}
	}

	public List<NodeIndex> FindNodesInRange(Vector3 position, float minRange, float maxRange, HullMask hullMask)
	{
		List<NodeIndex> list = new List<NodeIndex>();
		FindNodesInRange(position, minRange, maxRange, hullMask, list);
		return list;
	}

	public void FindNodesInRange(Vector3 position, float minRange, float maxRange, HullMask hullMask, List<NodeIndex> dest)
	{
		NodeFilters.NodeSearchFilter<NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeHullFilter, NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeAvailableFilter, NodeFilters.NodeMinDistanceFilter>>> filter = NodeFilters.Create(this, NodeFilters.And(new NodeFilters.NodeHullFilter(hullMask), default(NodeFilters.NodeAvailableFilter), new NodeFilters.NodeMinDistanceFilter(position, minRange)));
		blockMap.GetNearestItemsWhichPassFilter(position, maxRange, ref filter, dest);
	}

	public List<NodeIndex> FindNodesInRangeWithFlagConditions(Vector3 position, float minRange, float maxRange, HullMask hullMask, NodeFlags requiredFlags, NodeFlags forbiddenFlags, bool preventOverhead)
	{
		List<NodeIndex> list = new List<NodeIndex>();
		FindNodesInRangeWithFlagConditions(position, minRange, maxRange, hullMask, requiredFlags, forbiddenFlags, preventOverhead, list);
		return list;
	}

	public void FindNodesInRangeWithFlagConditions(Vector3 position, float minRange, float maxRange, HullMask hullMask, NodeFlags requiredFlags, NodeFlags forbiddenFlags, bool preventOverhead, List<NodeIndex> dest)
	{
		NodeFilters.NodeSearchFilter<NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeFlagsFilter, NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeHullFilter, NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeAvailableFilter, NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeOverheadFilter, NodeFilters.NodeMinDistanceFilter>>>>> filter = NodeFilters.Create(this, NodeFilters.And(new NodeFilters.NodeFlagsFilter(requiredFlags, forbiddenFlags), new NodeFilters.NodeHullFilter(hullMask), default(NodeFilters.NodeAvailableFilter), new NodeFilters.NodeOverheadFilter(position, preventOverhead), new NodeFilters.NodeMinDistanceFilter(position, minRange)));
		blockMap.GetNearestItemsWhichPassFilter(position, maxRange, ref filter, dest);
	}

	public bool GetNodePosition(NodeIndex nodeIndex, out Vector3 position)
	{
		if (nodeIndex != NodeIndex.invalid && nodeIndex.nodeIndex < nodes.Length)
		{
			position = nodes[nodeIndex.nodeIndex].position;
			return true;
		}
		position = Vector3.zero;
		return false;
	}

	public bool GetNodeFlags(NodeIndex nodeIndex, out NodeFlags flags)
	{
		if (nodeIndex != NodeIndex.invalid && nodeIndex.nodeIndex < nodes.Length)
		{
			flags = nodes[nodeIndex.nodeIndex].flags;
			return true;
		}
		flags = NodeFlags.None;
		return false;
	}

	public LinkIndex[] GetActiveNodeLinks(NodeIndex nodeIndex)
	{
		if (nodeIndex != NodeIndex.invalid && nodeIndex.nodeIndex < nodes.Length)
		{
			LinkListIndex linkListIndex = nodes[nodeIndex.nodeIndex].linkListIndex;
			LinkIndex[] array = new LinkIndex[linkListIndex.size];
			int index = linkListIndex.index;
			for (int i = 0; i < linkListIndex.size; i++)
			{
				array[i] = new LinkIndex
				{
					linkIndex = index++
				};
			}
			return array;
		}
		return null;
	}

	public void GetActiveNodeLinks(NodeIndex nodeIndex, List<LinkIndex> results)
	{
		if (nodeIndex != NodeIndex.invalid && nodeIndex.nodeIndex < nodes.Length)
		{
			LinkListIndex linkListIndex = nodes[nodeIndex.nodeIndex].linkListIndex;
			int index = linkListIndex.index;
			for (int i = 0; i < linkListIndex.size; i++)
			{
				results.Add(new LinkIndex
				{
					linkIndex = index++
				});
			}
		}
	}

	public bool TestNodeLineOfSight(NodeIndex nodeIndexA, NodeIndex nodeIndexB)
	{
		if (nodeIndexA != NodeIndex.invalid && nodeIndexA.nodeIndex < nodes.Length && nodeIndexB != NodeIndex.invalid && nodeIndexB.nodeIndex < nodes.Length)
		{
			return nodes[nodeIndexA.nodeIndex].lineOfSightMask[nodeIndexB.nodeIndex];
		}
		return false;
	}

	public bool GetPositionAlongLink(LinkIndex linkIndex, float t, out Vector3 position)
	{
		if (linkIndex != LinkIndex.invalid && linkIndex.linkIndex < links.Length)
		{
			position = Vector3.LerpUnclamped(nodes[links[linkIndex.linkIndex].nodeIndexA.nodeIndex].position, nodes[links[linkIndex.linkIndex].nodeIndexB.nodeIndex].position, t);
			return true;
		}
		position = Vector3.zero;
		return false;
	}

	public bool IsLinkSuitableForHull(LinkIndex linkIndex, HullClassification hullClassification)
	{
		if (linkIndex != LinkIndex.invalid && linkIndex.linkIndex < links.Length)
		{
			if ((links[linkIndex.linkIndex].hullMask & (1 << (int)hullClassification)) != 0)
			{
				if (links[linkIndex.linkIndex].gateIndex != 0)
				{
					return openGates[links[linkIndex.linkIndex].gateIndex];
				}
				return true;
			}
			return false;
		}
		return false;
	}

	public bool IsLinkSuitableForHull(LinkIndex linkIndex, HullMask hullMask)
	{
		if (linkIndex != LinkIndex.invalid && linkIndex.linkIndex < links.Length)
		{
			if (((uint)links[linkIndex.linkIndex].hullMask & (uint)hullMask) != 0)
			{
				if (links[linkIndex.linkIndex].gateIndex != 0)
				{
					return openGates[links[linkIndex.linkIndex].gateIndex];
				}
				return true;
			}
			return false;
		}
		return false;
	}

	public NodeIndex GetLinkStartNode(LinkIndex linkIndex)
	{
		if (linkIndex != LinkIndex.invalid && linkIndex.linkIndex < links.Length)
		{
			return links[linkIndex.linkIndex].nodeIndexA;
		}
		return NodeIndex.invalid;
	}

	public NodeIndex GetLinkEndNode(LinkIndex linkIndex)
	{
		if (linkIndex != LinkIndex.invalid && linkIndex.linkIndex < links.Length)
		{
			return links[linkIndex.linkIndex].nodeIndexB;
		}
		return NodeIndex.invalid;
	}

	public NodeIndex FindClosestNode(Vector3 position, HullClassification hullClassification, float maxDistance = float.PositiveInfinity)
	{
		NodeFilters.NodeSearchFilter<NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeHullFilter, NodeFilters.NodeAvailableFilter>> filter = NodeFilters.Create(this, NodeFilters.And(new NodeFilters.NodeHullFilter(hullClassification), default(NodeFilters.NodeAvailableFilter)));
		if (blockMap.GetNearestItemWhichPassesFilter(position, maxDistance, ref filter, out var dest))
		{
			return dest;
		}
		return NodeIndex.invalid;
	}

	public NodeIndex FindClosestNodeWithRaycast(Vector3 position, HullClassification hullClassification, float maxDistance, int maxRaycasts)
	{
		NodeFilters.NodeSearchFilter<NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeHullFilter, NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeAvailableFilter, NodeFilters.NodeRaycastFilter>>> filter = NodeFilters.Create(this, NodeFilters.And(new NodeFilters.NodeHullFilter(hullClassification), default(NodeFilters.NodeAvailableFilter), new NodeFilters.NodeRaycastFilter(position, new Vector3(0f, 0.2f, 0f), 3)));
		if (blockMap.GetNearestItemWhichPassesFilter(position, maxDistance, ref filter, out var dest))
		{
			return dest;
		}
		return NodeIndex.invalid;
	}

	public NodeIndex FindClosestNodeWithFlagConditions(Vector3 position, HullClassification hullClassification, NodeFlags requiredFlags, NodeFlags forbiddenFlags, bool preventOverhead)
	{
		NodeFilters.NodeSearchFilter<NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeFlagsFilter, NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeHullFilter, NodeFilters.NodeCheckFilterAnd<NodeFilters.NodeAvailableFilter, NodeFilters.NodeOverheadFilter>>>> filter = NodeFilters.Create(this, NodeFilters.And(new NodeFilters.NodeFlagsFilter(requiredFlags, forbiddenFlags), new NodeFilters.NodeHullFilter(hullClassification), default(NodeFilters.NodeAvailableFilter), new NodeFilters.NodeOverheadFilter(position, preventOverhead)));
		if (blockMap.GetNearestItemWhichPassesFilter(position, float.PositiveInfinity, ref filter, out var dest))
		{
			return dest;
		}
		return NodeIndex.invalid;
	}

	private float HeuristicCostEstimate(Vector3 startPos, Vector3 endPos)
	{
		return Vector3.Distance(startPos, endPos);
	}

	private static float DistanceXZ(Vector3 a, Vector3 b)
	{
		a.y = 0f;
		b.y = 0f;
		return Vector3.Distance(a, b);
	}

	private static void ArrayRemoveNodeIndex(NodeIndex[] array, NodeIndex value, int count)
	{
		for (int i = 0; i < count; i++)
		{
			if (array[i] == value)
			{
				array[i] = array[count - 1];
				break;
			}
		}
	}

	private static void ArrayRemoveNodeIndexFromCachedPosition(NodeIndex[] array, int arrayIndex, int count)
	{
		array[arrayIndex] = array[count - 1];
	}

	public PathTask ComputePath(PathRequest pathRequest)
	{
		bool[] array = boolPerNodePool.Request();
		bool[] array2 = boolPerNodePool.Request();
		NodeIndex[] array3 = nodePerNodePool.Request();
		LinkIndex[] array4 = linkPerNodePool.Request();
		ArrayUtils.SetAll(array4, in LinkIndex.invalid);
		float[] array5 = floatPerNodePool.Request();
		float value = float.PositiveInfinity;
		ArrayUtils.SetAll(array5, in value);
		float[] array6 = floatPerNodePool.Request();
		value = float.PositiveInfinity;
		ArrayUtils.SetAll(array6, in value);
		try
		{
			ResolveNodePosition(in pathRequest.startPos, out var dest2, 100f, 2);
			ResolveNodePosition(in pathRequest.endPos, out var dest3, 500f, 0);
			PathTask pathTask = new PathTask(pathRequest.path);
			pathTask.status = PathTask.TaskStatus.Running;
			if (dest2.nodeIndex == NodeIndex.invalid.nodeIndex || dest3.nodeIndex == NodeIndex.invalid.nodeIndex)
			{
				pathRequest.path.Clear();
				pathTask.status = PathTask.TaskStatus.Complete;
				pathTask.wasReachable = false;
				return pathTask;
			}
			int num = 1 << (int)pathRequest.hullClassification;
			array2[dest2.nodeIndex] = true;
			int num2 = 1;
			array3[0] = dest2;
			array5[dest2.nodeIndex] = 0f;
			array6[dest2.nodeIndex] = HeuristicCostEstimate(nodes[dest2.nodeIndex].position, nodes[dest3.nodeIndex].position);
			int arrayIndex = -1;
			while (num2 > 0)
			{
				NodeIndex nodeIndex = NodeIndex.invalid;
				float num3 = float.PositiveInfinity;
				for (int i = 0; i < num2; i++)
				{
					int nodeIndex2 = array3[i].nodeIndex;
					if (array6[nodeIndex2] <= num3)
					{
						num3 = array6[nodeIndex2];
						nodeIndex = new NodeIndex(nodeIndex2);
						arrayIndex = i;
					}
				}
				if (nodeIndex.nodeIndex == dest3.nodeIndex)
				{
					ReconstructPath(pathRequest.path, array4, array4[nodeIndex.nodeIndex], pathRequest);
					pathTask.status = PathTask.TaskStatus.Complete;
					pathTask.wasReachable = true;
					return pathTask;
				}
				array2[nodeIndex.nodeIndex] = false;
				ArrayRemoveNodeIndexFromCachedPosition(array3, arrayIndex, num2);
				num2--;
				array[nodeIndex.nodeIndex] = true;
				LinkListIndex linkListIndex = nodes[nodeIndex.nodeIndex].linkListIndex;
				LinkIndex linkIndex = default(LinkIndex);
				linkIndex.linkIndex = linkListIndex.index;
				LinkIndex linkIndex2 = linkIndex;
				linkIndex = default(LinkIndex);
				linkIndex.linkIndex = linkListIndex.index + (int)linkListIndex.size;
				for (LinkIndex linkIndex3 = linkIndex; linkIndex2.linkIndex < linkIndex3.linkIndex; linkIndex2.linkIndex++)
				{
					Link link = links[linkIndex2.linkIndex];
					NodeIndex nodeIndexB = link.nodeIndexB;
					if (array[nodeIndexB.nodeIndex])
					{
						continue;
					}
					if ((num & link.jumpHullMask) != 0 && links[linkIndex2.linkIndex].minJumpHeight > 0f)
					{
						Vector3 position = nodes[link.nodeIndexA.nodeIndex].position;
						Vector3 position2 = nodes[link.nodeIndexB.nodeIndex].position;
						if (Trajectory.CalculateApex(Trajectory.CalculateInitialYSpeed(Trajectory.CalculateGroundTravelTime(pathRequest.maxSpeed, DistanceXZ(position, position2)), position2.y - position.y)) > pathRequest.maxJumpHeight)
						{
							continue;
						}
					}
					if ((link.hullMask & num) != 0 && (link.gateIndex == 0 || openGates[link.gateIndex]))
					{
						float distanceScore = link.distanceScore;
						float num4 = array5[nodeIndex.nodeIndex] + distanceScore;
						if (!array2[nodeIndexB.nodeIndex])
						{
							array2[nodeIndexB.nodeIndex] = true;
							array3[num2] = nodeIndexB;
							num2++;
						}
						else if (num4 >= array5[nodeIndexB.nodeIndex])
						{
							continue;
						}
						array4[nodeIndexB.nodeIndex] = linkIndex2;
						array5[nodeIndexB.nodeIndex] = num4;
						array6[nodeIndexB.nodeIndex] = num4 + HeuristicCostEstimate(nodes[nodeIndexB.nodeIndex].position, nodes[dest3.nodeIndex].position);
					}
				}
			}
			pathRequest.path.Clear();
			pathTask.status = PathTask.TaskStatus.Complete;
			return pathTask;
		}
		catch (Exception ex)
		{
			Debug.LogError($"ComputePath exception:  {ex}\n\n{ex.StackTrace}");
		}
		finally
		{
			floatPerNodePool.Return(array6, FixedSizeArrayPool<float>.ClearType.NONE);
			floatPerNodePool.Return(array5, FixedSizeArrayPool<float>.ClearType.NONE);
			linkPerNodePool.Return(array4, FixedSizeArrayPool<LinkIndex>.ClearType.NONE);
			nodePerNodePool.Return(array3, FixedSizeArrayPool<NodeIndex>.ClearType.CLEAR);
			boolPerNodePool.Return(array2, FixedSizeArrayPool<bool>.ClearType.CLEAR);
			boolPerNodePool.Return(array, FixedSizeArrayPool<bool>.ClearType.CLEAR);
		}
		return null;
		void ResolveNodePosition(in PathRequestPosition pathRequestPosition, out NodeIndex dest, float maxSearchDistance, int maxRaycasts)
		{
			if (pathRequestPosition.nodeIndex.HasValue)
			{
				dest = pathRequestPosition.nodeIndex.Value;
			}
			else if (pathRequestPosition.position.HasValue)
			{
				dest = FindClosestNodeWithRaycast(pathRequestPosition.position.Value, pathRequest.hullClassification, maxSearchDistance, maxRaycasts);
			}
			else
			{
				dest = NodeIndex.invalid;
			}
		}
	}

	private LinkIndex Resolve(LinkIndex[] cameFrom, LinkIndex current)
	{
		if (current.linkIndex < 0 || current.linkIndex > links.Length)
		{
			Debug.LogFormat("Link {0} is out of range [0,{1})", current.linkIndex, links.Length);
		}
		NodeIndex nodeIndexA = links[current.linkIndex].nodeIndexA;
		return cameFrom[nodeIndexA.nodeIndex];
	}

	private void ReconstructPath(Path path, LinkIndex[] cameFrom, LinkIndex current, PathRequest pathRequest)
	{
		int num = 1 << (int)pathRequest.hullClassification;
		path.Clear();
		if (current != LinkIndex.invalid)
		{
			path.PushWaypointToFront(links[current.linkIndex].nodeIndexB, 0f);
		}
		while (current != LinkIndex.invalid)
		{
			NodeIndex nodeIndexB = links[current.linkIndex].nodeIndexB;
			float minJumpHeight = 0f;
			if ((num & links[current.linkIndex].jumpHullMask) != 0 && links[current.linkIndex].minJumpHeight > 0f)
			{
				Vector3 position = nodes[links[current.linkIndex].nodeIndexA.nodeIndex].position;
				Vector3 position2 = nodes[links[current.linkIndex].nodeIndexB.nodeIndex].position;
				minJumpHeight = Trajectory.CalculateApex(Trajectory.CalculateInitialYSpeed(Trajectory.CalculateGroundTravelTime(pathRequest.maxSpeed, DistanceXZ(position, position2)), position2.y - position.y));
			}
			path.PushWaypointToFront(nodeIndexB, minJumpHeight);
			if (cameFrom[links[current.linkIndex].nodeIndexA.nodeIndex] == LinkIndex.invalid)
			{
				path.PushWaypointToFront(links[current.linkIndex].nodeIndexA, 0f);
			}
			current = cameFrom[links[current.linkIndex].nodeIndexA.nodeIndex];
		}
		path.status = PathStatus.Valid;
	}

	private byte RegisterGateName(string gateName)
	{
		if (string.IsNullOrEmpty(gateName))
		{
			return 0;
		}
		int num = gateNames.IndexOf(gateName);
		if (num == -1)
		{
			num = gateNames.Count;
			if (num >= 256)
			{
				Debug.LogErrorFormat(this, "Nodegraph cannot have more than 255 gate names. Nodegraph={0} gateName={1}", this, gateName);
				num = 0;
			}
			else
			{
				gateNames.Add(gateName);
			}
		}
		return (byte)num;
	}

	public bool IsGateOpen(string gateName)
	{
		int num = gateNames.IndexOf(gateName);
		if (num == -1)
		{
			return false;
		}
		return openGates[num];
	}

	public void SetGateState(string gateName, bool open)
	{
		TrySetGateState(gateName, open);
	}

	public bool TrySetGateState(string gateName, bool open)
	{
		int num = gateNames.IndexOf(gateName);
		if (num == -1)
		{
			return false;
		}
		openGates[num] = open;
		return true;
	}
}
