namespace Fusion.Addons.InterestManagement
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Reflection;
	using UnityEngine;
	/// 
	/// Contains methods to for extending NetworkRunner public API.
	/// 
	public static class NetworkRunnerExtensions
	{
		// PRIVATE MEMBERS
		private static bool                          _isInitialized;
		private static bool                          _isNotAvailable;
		private static FieldInfo                     _simulationFieldInfo;
		private static FieldInfo                     _playersConnectionsFieldInfo;
		private static FieldInfo                     _areaOfInterestCellsFieldInfo;
		private static List               _interestCellPlayers = new List();
		private static List               _interestCellObjects = new List();
		private static HashSet            _uniqueInterestObjects = new HashSet();
		private static Dictionary _playersAsObjects = new Dictionary();
		// PUBLIC METHODS
		/// 
		/// Returns global interest manager singleton. New one (of type GlobalInterestManager) is created on NetworkRunner if it doesn't exist.
		/// 
		public static GlobalInterestManager GetGlobalInterestManager(this NetworkRunner runner)
		{
			TryGetGlobalInterestManager(runner, out GlobalInterestManager globalInterestManager, true);
			return globalInterestManager;
		}
		/// 
		/// Returns global interest manager singleton of type T.
		/// 
		/// Reference to NetworkRunner.
		/// Returned reference to global interest manager.
		/// Create new interest manager of type T on NetworkRunner if it doesn't exist.
		public static bool TryGetGlobalInterestManager(this NetworkRunner runner, out T globalInterestManager, bool createIfNotExists) where T : GlobalInterestManager
		{
			globalInterestManager = default;
			if (ReferenceEquals(runner, null) == true)
				return false;
			if (runner.TryGetComponent(out globalInterestManager) == true)
				return true;
			if (createIfNotExists == false)
				return false;
			if (runner.TryGetComponent(out GlobalInterestManager randomGlobalInterestManager) == true)
			{
				Debug.LogError($"Trying to get/create {typeof(T).Name} on {nameof(NetworkRunner)}({runner.name}), but {randomGlobalInterestManager.GetType().Name} already exists. This is not allowed!", runner.gameObject);
				return false;
			}
			Debug.Log($"Creating new {typeof(T).Name} on {nameof(NetworkRunner)}({runner.name}).", runner.gameObject);
			globalInterestManager = runner.gameObject.AddComponent();
			return true;
		}
		/// 
		/// Returns player interest manager for given player.
		/// 
		public static bool TryGetPlayerInterestManager(this NetworkRunner runner, PlayerRef player, out PlayerInterestManager playerInterestManager)
		{
			if (runner.TryGetGlobalInterestManager(out GlobalInterestManager globalInterestManager, true) == true)
				return globalInterestManager.TryGetPlayerManager(player, out playerInterestManager);
			playerInterestManager = default;
			return false;
		}
		/// 
		/// Returns player interest view for given player.
		/// 
		public static bool TryGetPlayerInterestView(this NetworkRunner runner, PlayerRef player, out PlayerInterestView playerInterestView)
		{
			if (runner.TryGetGlobalInterestManager(out GlobalInterestManager globalInterestManager, true) == true)
				return globalInterestManager.TryGetPlayerView(player, out playerInterestView);
			playerInterestView = default;
			return false;
		}
		/// 
		/// Returns reference to internal Fusion hashset with interest cells.
		/// DO NOT USE, THIS IS FOR INTERNAL PURPOSES ONLY!
		/// 
		public static HashSet GetAreaOfInterestCells(NetworkRunner runner, PlayerRef player)
		{
			if (Initialize(runner, player) == false)
				return default;
			object simulation = _simulationFieldInfo.GetValue(runner);
			if (ReferenceEquals(simulation, null) == true)
				return default;
			IDictionary playerConnections = _playersConnectionsFieldInfo.GetValue(simulation) as IDictionary;
			if (ReferenceEquals(playerConnections, null) == true)
				return default;
			if (_playersAsObjects.TryGetValue(player, out object playerAsObject) == false)
			{
				playerAsObject = player;
				_playersAsObjects[player] = playerAsObject;
			}
			if (playerConnections.Contains(playerAsObject) == false)
				return default;
			object simulationConnection = playerConnections[playerAsObject];
			if (ReferenceEquals(simulationConnection, null) == true)
				return default;
			return _areaOfInterestCellsFieldInfo.GetValue(simulationConnection) as HashSet;
		}
		/// 
		/// Returns all objects in the player interest.
		/// DO NOT USE, THIS IS FOR INTERNAL PURPOSES ONLY!
		/// 
		public static bool GetObjectsInPlayerInterest(NetworkRunner runner, PlayerRef player, List objects, HashSet playerInterestCells = null)
		{
			objects.Clear();
			if (Initialize(runner, player) == false)
				return false;
			if (playerInterestCells == null)
			{
				playerInterestCells = GetAreaOfInterestCells(runner, player);
			}
			if (playerInterestCells == null)
				return false;
			_uniqueInterestObjects.Clear();
			Simulation simulation = (Simulation)_simulationFieldInfo.GetValue(runner);
			foreach (int cell in playerInterestCells)
			{
				simulation.GetObjectsAndPlayersInAreaOfInterestCell(cell, _interestCellPlayers, _interestCellObjects);
				foreach (NetworkId interestCellObject in _interestCellObjects)
				{
					if (_uniqueInterestObjects.Add(interestCellObject) == true)
					{
						NetworkObject networkObject = runner.FindObject(interestCellObject);
						if (ReferenceEquals(networkObject, null) == false)
						{
							objects.Add(networkObject);
						}
					}
				}
			}
			return true;
		}
		// PRIVATE METHODS
		private static bool Initialize(NetworkRunner runner, PlayerRef player)
		{
			if (_isInitialized == true)
				return true;
			if (_isNotAvailable == true)
				return false;
			if (_simulationFieldInfo == null)
			{
				try
				{
					_simulationFieldInfo = typeof(NetworkRunner).GetField("_simulation", BindingFlags.Instance | BindingFlags.NonPublic);
				}
				catch (Exception exception)
				{
					Debug.LogException(exception);
				}
				if (_simulationFieldInfo == null)
				{
					_isNotAvailable = true;
					return false;
				}
			}
			object simulation = _simulationFieldInfo.GetValue(runner);
			if (ReferenceEquals(simulation, null) == true)
				return false;
			if (_playersConnectionsFieldInfo == null)
			{
				try
				{
					_playersConnectionsFieldInfo = typeof(Simulation).GetField("_playersConnections", BindingFlags.NonPublic | BindingFlags.Instance);
				}
				catch (Exception exception)
				{
					Debug.LogException(exception);
				}
				if (_playersConnectionsFieldInfo == null)
				{
					_isNotAvailable = true;
					return false;
				}
			}
			IDictionary playerConnections = _playersConnectionsFieldInfo.GetValue(simulation) as IDictionary;
			if (ReferenceEquals(playerConnections, null) == true)
				return false;
			if (_playersAsObjects.TryGetValue(player, out object playerAsObject) == false)
			{
				playerAsObject = player;
				_playersAsObjects[player] = playerAsObject;
			}
			if (playerConnections.Contains(playerAsObject) == false)
				return false;
			object simulationConnection = playerConnections[playerAsObject];
			if (ReferenceEquals(simulationConnection, null) == true)
				return false;
			if (_areaOfInterestCellsFieldInfo == null)
			{
				try
				{
					_areaOfInterestCellsFieldInfo = simulationConnection.GetType().GetField("AreaOfInterestCells", BindingFlags.Instance | BindingFlags.Public);
				}
				catch (Exception exception)
				{
					Debug.LogException(exception);
				}
				if (_areaOfInterestCellsFieldInfo == null)
				{
					_isNotAvailable = true;
					return false;
				}
			}
			_isInitialized = true;
			return true;
		}
	}
}