namespace Fusion.Addons.InterestManagement
{
	using System.Collections.Generic;
	using UnityEngine;
	/// 
	/// Defines processing behavior of interest provider.
    /// 
    /// - Default - Interest from this provider is processed additively to existing interest.///
- Override - Interest from this provider overrides all existing interest.///
- Exclude - Interest from this provider is removed from existing interest.///
/// 
	public enum EInterestMode
	{
		Default  = 0,
		Override = 1,
		Exclude  = 2,
	}
	/// 
	/// Base class for interest providers.
	/// Can be used as is and setup interest by linking interest shapes.
	/// 
	[DefaultExecutionOrder(InterestProvider.EXECUTION_ORDER)]
	public class InterestProvider : NetworkBehaviour, IInterestProvider
	{
		// CONSTANTS
		public const int EXECUTION_ORDER = 20000;
		// PUBLIC MEMBERS
		[InlineHelp][Tooltip("This provider will be registered to global interest manager and automatically processed for every player.")]
		public bool IsGlobal;
		[InlineHelp][Tooltip("Defines order in which interest providers are processed. Provider with lower value will be processed first.")]
		public int SortOrder;
		[InlineHelp][Tooltip("Defines processing behavior.\n" +
		"• Default - Interest from this provider is processed additively to existing interest.\n" +
		"• Override - Interest from this provider overrides all existing interest.\n" +
		"• Exclude - Interest from this provider is removed from existing interest.")]
		public EInterestMode InterestMode;
		[InlineHelp][Tooltip("List of interest shapes that are processed to player interest.")]
		public List Shapes = new List();
		[InlineHelp][Tooltip("List of child interest providers which are registered to this provider upon spawn.")]
		public List ChildProviders = new List();
		/// 
		/// Cached Transform component.
		/// 
		public Transform Transform { get { if (ReferenceEquals(_transform, null) == true) { _transform = transform; } return transform; } }
		public static Color ChildProvidersConnectionColor      = new Color(0.0f, 0.0f, 1.0f, 1.0f);
		public static Color RegisteredProvidersConnectionColor = new Color(0.0f, 1.0f, 0.0f, 1.0f);
		// PRIVATE MEMBERS
		private bool                  _isGlobal;
		private int                   _sortOrder;
		private EInterestMode         _interestMode;
		private List _runtimeProviders = new List();
		private bool                  _isRegistered;
		private Transform             _transform;
		private int                   _version;
		private static Stack _runtimeProvidersPool = new Stack();
		// PUBLIC METHODS
		/// 
		/// Mark all active registrations (including global) of this provider as invalid.
		/// Use this method to prevent further processing of this provider.
		/// Called automatically on despawn.
		/// 
		public void Release()
		{
			++_version;
		}
		/// 
		/// Register other interest provider to self, allowing to create hierarchy of interest providers.
		/// Registered providers are processed only if the player is interested in this provider.
		/// 
		/// Reference to interest provider.
		/// How long the interest provider stays registered.
		public bool RegisterProvider(IInterestProvider interestProvider, float duration = 0.0f)
		{
			int version = interestProvider.Version;
			for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
			{
				RuntimeProvider runtimeProvider = _runtimeProviders[i];
				if (ReferenceEquals(runtimeProvider.Provider, interestProvider) == true)
				{
					bool hasChanged = false;
					if (duration > runtimeProvider.Duration)
					{
						runtimeProvider.Duration = duration;
						hasChanged = true;
					}
					if (version != runtimeProvider.Version)
					{
						runtimeProvider.Version = version;
						hasChanged = true;
					}
					return hasChanged;
				}
			}
			if (_runtimeProvidersPool.TryPop(out RuntimeProvider newProvider) == false)
			{
				newProvider = new RuntimeProvider();
			}
			newProvider.Provider = interestProvider;
			newProvider.Duration = duration;
			newProvider.Version  = version;
			_runtimeProviders.Add(newProvider);
			return true;
		}
		/// 
		/// Unregister other interest provider from self.
		/// 
		/// Reference to interest provider.
		/// If true, the interest provider is unregistered recursively from all registered interest providers.
		public bool UnregisterProvider(IInterestProvider interestProvider, bool recursive)
		{
			bool unregistered = false;
			if (recursive == true)
			{
				for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
				{
					unregistered |= _runtimeProviders[i].Provider.UnregisterProvider(interestProvider, true);
				}
			}
			for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
			{
				RuntimeProvider runtimeProvider = _runtimeProviders[i];
				if (ReferenceEquals(runtimeProvider.Provider, interestProvider) == true)
				{
					_runtimeProviders.RemoveAt(i);
					runtimeProvider.Clear();
					_runtimeProvidersPool.Push(runtimeProvider);
					unregistered = true;
					break;
				}
			}
			return unregistered;
		}
		/// 
		/// Add all valid registered interest providers to the set of unique providers.
		/// 
		/// Set of interest providers that will be filled.
		/// If true, registered interest providers are processed recursively.
		public void GetProviders(InterestProviderSet interestProviders, bool recursive)
		{
			for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
			{
				RuntimeProvider   runtimeProvider  = _runtimeProviders[i];
				IInterestProvider interestProvider = runtimeProvider.Provider;
				if (interestProvider.Version == runtimeProvider.Version)
				{
					interestProviders.Add(interestProvider);
					if (recursive == true)
					{
						interestProvider.GetProviders(interestProviders, true);
					}
				}
			}
		}
		// InterestProvider INTERFACE
		/// 
		/// Returns whether this provider should be processed for given player.
		/// 
		/// Player interest view with player information used for filtering.
		public virtual bool IsPlayerInterested(PlayerInterestView playerView)
		{
			return true;
		}
		/// 
		/// Add interest providers that are relevant to the player.
		/// This method can be used for adding providers selectively based on gameplay conditions.
		/// 
		/// Player interest view used for filtering.
		/// Interest providers set that will be filled.
		protected virtual void GetProvidersForPlayer(PlayerInterestView playerView, InterestProviderSet interestProviders) {}
		/// 
		/// Add interest cells that are relevant to the player.
		/// This method can be used for adding shape cells selectively based on gameplay conditions, using InterestShape.GetCells().
		/// 
		/// Player interest view used for filtering.
		/// Interest cells that will be set.
		protected virtual void GetCellsForPlayer(PlayerInterestView playerView, HashSet cells) {}
		/// 
		/// Called on the end of Spawned(), before registering child providers to self and before self-registring to GlobalInterestManager (only global interest providers).
		/// 
		protected virtual void OnSpawned() {}
		/// 
		/// Called on the start of Despawn(), after self-unregistering from GlobalInterestManager and after unregistering all registered providers.
		/// 
		protected virtual void OnDespawned(NetworkRunner runner, bool hasState) {}
		/// 
		/// Called on the end of FixedUpdateNetwork(), after updating internal providers list.
		/// 
		protected virtual void OnFixedUpdateNetwork() {}
		/// 
		/// Called on the end of Render(), after updating internal providers list.
		/// 
		protected virtual void OnRender() {}
		/// 
		/// Called when an object with InterestProvider or PlayerInterestManager component is selected.
		/// 
		/// Provides player information, is valid only if the selected object has PlayerInterestManager component.
		/// True if the selected object has this component, false otherwise.
		protected virtual void OnDrawGizmosForPlayer(PlayerInterestView playerView, bool isSelected) {}
		// NetworkBehaviour INTERFACE
		public override sealed void Spawned()
		{
			_isGlobal     = IsGlobal;
			_sortOrder    = SortOrder;
			_interestMode = InterestMode;
			_isRegistered = false;
			OnSpawned();
			Register();
		}
		public override sealed void Despawned(NetworkRunner runner, bool hasState)
		{
			Unregister();
			OnDespawned(runner, hasState);
			IsGlobal     = _isGlobal;
			SortOrder    = _sortOrder;
			InterestMode = _interestMode;
			_isRegistered   = default;
			_isGlobal       = default;
			_sortOrder      = default;
			_interestMode   = default;
			_runtimeProviders.Clear();
			Release();
		}
		public override sealed void FixedUpdateNetwork()
		{
			if (Runner.IsForward == true)
			{
				UpdateRuntimeProviders(Runner.DeltaTime);
			}
			OnFixedUpdateNetwork();
		}
		public override sealed void Render()
		{
			if (Object.IsInSimulation == false)
			{
				UpdateRuntimeProviders(Time.unscaledDeltaTime);
			}
			OnRender();
		}
		// IInterestProvider INTERFACE
		int           IInterestProvider.Version      => _version;
		int           IInterestProvider.SortOrder    => SortOrder;
		EInterestMode IInterestProvider.InterestMode => InterestMode;
		void IInterestProvider.GetProvidersForPlayer(PlayerInterestView playerView, InterestProviderSet interestProviders, bool recursive)
		{
			for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
			{
				RuntimeProvider   runtimeProvider  = _runtimeProviders[i];
				IInterestProvider interestProvider = runtimeProvider.Provider;
				if (interestProvider.Version == runtimeProvider.Version && interestProvider.IsPlayerInterested(playerView) == true)
				{
					interestProviders.Add(interestProvider);
					if (recursive == true)
					{
						interestProvider.GetProvidersForPlayer(playerView, interestProviders, true);
					}
				}
			}
			GetProvidersForPlayer(playerView, interestProviders);
		}
		void IInterestProvider.GetCellsForPlayer(PlayerInterestView playerView, HashSet cells)
		{
			GetCells(playerView, cells);
			GetCellsForPlayer(playerView, cells);
		}
		void IInterestProvider.DrawGizmosForPlayer(PlayerInterestView playerView)
		{
			DrawGizmos(playerView, false);
			OnDrawGizmosForPlayer(playerView, false);
		}
		// PRIVATE METHODS
		private void Register()
		{
			for (int i = 0, count = ChildProviders.Count; i < count; ++i)
			{
				InterestProvider interestProvider = ChildProviders[i];
				if (interestProvider != null)
				{
					RegisterProvider(interestProvider);
				}
			}
			if (IsGlobal == true)
			{
				_isRegistered = Runner.GetGlobalInterestManager().RegisterProvider(this);
			}
		}
		private void Unregister()
		{
			if (_isRegistered == true)
			{
				Runner.GetGlobalInterestManager().UnregisterProvider(this, false);
				_isRegistered = false;
			}
			for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
			{
				RuntimeProvider registeredProvider = _runtimeProviders[i];
				registeredProvider.Clear();
				_runtimeProvidersPool.Push(registeredProvider);
			}
			_runtimeProviders.Clear();
		}
		private void UpdateRuntimeProviders(float deltaTime)
		{
			for (int i = _runtimeProviders.Count - 1; i >= 0; --i)
			{
				RuntimeProvider   runtimeProvider  = _runtimeProviders[i];
				IInterestProvider interestProvider = runtimeProvider.Provider;
				bool isValid = interestProvider.Version == runtimeProvider.Version;
				if (isValid == true)
				{
					if (runtimeProvider.Duration > 0.0f)
					{
						runtimeProvider.Duration -= deltaTime;
						if (runtimeProvider.Duration <= 0.0f)
						{
							isValid = false;
						}
					}
				}
				if (isValid == false)
				{
					_runtimeProviders.RemoveAt(i);
					runtimeProvider.Clear();
					_runtimeProvidersPool.Push(runtimeProvider);
				}
			}
		}
		private void GetCells(PlayerInterestView playerView, HashSet cells)
		{
			if (InterestMode == EInterestMode.Override)
			{
				cells.Clear();
			}
			for (int i = 0, count = Shapes.Count; i < count; ++i)
			{
				InterestShape shape = Shapes[i];
				if (shape != null)
				{
					shape.GetCells(playerView, cells, InterestMode);
				}
			}
		}
		private void DrawGizmos(PlayerInterestView playerView, bool isSelected)
		{
			for (int i = 0, count = Shapes.Count; i < count; ++i)
			{
				InterestShape shape = Shapes[i];
				if (shape != null)
				{
					shape.DrawGizmo(playerView);
				}
			}
			if (isSelected == true)
			{
				if (Object != null)
				{
					for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
					{
						RuntimeProvider   runtimeProvider  = _runtimeProviders[i];
						IInterestProvider interestProvider = runtimeProvider.Provider;
						if (interestProvider != null && interestProvider.Transform != null && interestProvider.Version == runtimeProvider.Version)
						{
							InterestUtility.DrawLine(Transform.position, interestProvider.Transform.position, RegisteredProvidersConnectionColor);
						}
					}
				}
				else
				{
					if (ChildProviders != null && ChildProviders.Count > 0)
					{
						for (int i = 0, count = ChildProviders.Count; i < count; ++i)
						{
							IInterestProvider interestProvider = ChildProviders[i];
							if (interestProvider != null && interestProvider.Transform != null)
							{
								InterestUtility.DrawLine(Transform.position, interestProvider.Transform.position, ChildProvidersConnectionColor);
							}
						}
					}
				}
			}
		}
#if UNITY_EDITOR
		[UnityEditor.DrawGizmo(UnityEditor.GizmoType.Active)]
		private static void DrawGizmo(InterestProvider interestProvider, UnityEditor.GizmoType gizmoType)
		{
			if ((gizmoType & UnityEditor.GizmoType.Active) != UnityEditor.GizmoType.Active)
				return;
			PlayerInterestView playerView = interestProvider as PlayerInterestView;
			interestProvider.DrawGizmos(playerView, true);
			interestProvider.OnDrawGizmosForPlayer(playerView, true);
		}
#endif
		// DATA STRUCTURES
		private sealed class RuntimeProvider
		{
			public IInterestProvider Provider;
			public float             Duration;
			public int               Version;
			public void Clear()
			{
				Provider = default;
				Duration = default;
				Version  = default;
			}
		}
	}
}