using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Transporting;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace FishNet.Discovery
{
	/// 
	/// A component that advertises a server or searches for servers.
	/// 
	public sealed class NetworkDiscovery : MonoBehaviour
	{
		/// 
		/// A string that differentiates your application/game from others.
		/// Must not be null, empty, or blank.
		/// 
		[SerializeField]
		[Tooltip("A string that differentiates your application/game from others. Must not be null, empty, or blank.")]
		private string secret;
		/// 
		/// The port number used by this  component.
		/// Must be different from the one used by the .
		/// 
		[SerializeField]
		[Tooltip("The port number used by this NetworkDiscovery component. Must be different from the one used by the Transport.")]
		private ushort port;
		/// 
		/// How often does this  component advertises a server or searches for servers.
		/// 
		[SerializeField]
		[Tooltip("How often does this NetworkDiscovery component advertises a server or searches for servers.")]
		private float discoveryInterval;
		/// 
		/// Whether this  component will automatically start/stop? Setting this to true is recommended.
		/// 
		[SerializeField]
		[Tooltip("Whether this NetworkDiscovery component will automatically start/stop? Setting this to true is recommended.")]
		private bool automatic;
		/// 
		/// The  used to advertise the server.
		/// 
		private UdpClient _serverUdpClient;
		/// 
		/// The  used to search for servers.
		/// 
		private UdpClient _clientUdpClient;
		/// 
		/// Whether this  component is currently advertising a server or not.
		/// 
		public bool IsAdvertising => _serverUdpClient != null;
		/// 
		/// Whether this  component is currently searching for servers or not.
		/// 
		public bool IsSearching => _clientUdpClient != null;
		/// 
		/// An  that is invoked by this  component whenever a server is found.
		/// 
		public event Action ServerFoundCallback;
		private void Start()
		{
			if (automatic)
			{
				InstanceFinder.ServerManager.OnServerConnectionState += ServerConnectionStateChangedHandler;
				InstanceFinder.ClientManager.OnClientConnectionState += ClientConnectionStateChangedHandler;
				StartSearchingForServers();
			}
		}
		private void OnDisable()
		{
			InstanceFinder.ServerManager.OnServerConnectionState -= ServerConnectionStateChangedHandler;
			InstanceFinder.ClientManager.OnClientConnectionState -= ClientConnectionStateChangedHandler;
			StopAdvertisingServer();
			StopSearchingForServers();
		}
		private void OnDestroy()
		{
			InstanceFinder.ServerManager.OnServerConnectionState -= ServerConnectionStateChangedHandler;
			InstanceFinder.ClientManager.OnClientConnectionState -= ClientConnectionStateChangedHandler;
			StopAdvertisingServer();
			StopSearchingForServers();
		}
		private void OnApplicationQuit()
		{
			InstanceFinder.ServerManager.OnServerConnectionState -= ServerConnectionStateChangedHandler;
			InstanceFinder.ClientManager.OnClientConnectionState -= ClientConnectionStateChangedHandler;
			StopAdvertisingServer();
			StopSearchingForServers();
		}
		#region Connection State Handlers
		private void ServerConnectionStateChangedHandler(ServerConnectionStateArgs args)
		{
			if (args.ConnectionState == LocalConnectionState.Starting)
			{
				StopSearchingForServers();
			}
			else if (args.ConnectionState == LocalConnectionState.Started)
			{
				StartAdvertisingServer();
			}
			else if (args.ConnectionState == LocalConnectionState.Stopping)
			{
				//StopAdvertisingServer();
			}
			else if (args.ConnectionState == LocalConnectionState.Stopped)
			{
				StartSearchingForServers();
			}
		}
		private void ClientConnectionStateChangedHandler(ClientConnectionStateArgs args)
		{
			if (args.ConnectionState == LocalConnectionState.Starting)
			{
				StopSearchingForServers();
			}
			else if (args.ConnectionState == LocalConnectionState.Stopped)
			{
				StartSearchingForServers();
			}
		}
		#endregion
		#region Server
		/// 
		/// Makes this  component start advertising a server.
		/// 
		public void StartAdvertisingServer()
		{
			//Debug.Log("NetworkDiscovery is advertizing on port " + port);
			if (!InstanceFinder.IsServer)
			{
				if (NetworkManager.StaticCanLog(LoggingType.Warning)) Debug.LogWarning("Unable to start advertising server. Server is inactive.", this);
				return;
			}
			if (_serverUdpClient != null)
			{
				if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Server is already being advertised.", this);
				return;
			}
			if (port == InstanceFinder.TransportManager.Transport.GetPort())
			{
				if (NetworkManager.StaticCanLog(LoggingType.Warning)) Debug.LogWarning("Unable to start advertising server on the same port as the transport.", this);
				return;
			}
			_serverUdpClient = new UdpClient(port)
			{
				EnableBroadcast = true,
				MulticastLoopback = false,
			};
			//Debug.Log("UDP = " + _serverUdpClient.ToString());
			Task.Run(AdvertiseServerAsync);
			if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Started advertising server.", this);
		}
		/// 
		/// Makes this  component immediately stop advertising the server it is currently advertising.
		/// 
		public void StopAdvertisingServer()
		{
			if (_serverUdpClient == null) return;
			_serverUdpClient.Close();
			_serverUdpClient = null;
			if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Stopped advertising server.", this);
		}
		private async void AdvertiseServerAsync()
		{
			while (_serverUdpClient != null)
			{
				
				await Task.Delay(TimeSpan.FromSeconds(discoveryInterval));
				
				UdpReceiveResult result = await _serverUdpClient.ReceiveAsync();
				Debug.Log("Awaiting for server");
				string receivedSecret = Encoding.UTF8.GetString(result.Buffer);
				Debug.Log(receivedSecret);
				if (receivedSecret == secret)
				{
					byte[] okBytes = BitConverter.GetBytes(true);
					await _serverUdpClient.SendAsync(okBytes, okBytes.Length, result.RemoteEndPoint);
				}
			}
			Debug.Log("_serverUdpClient == null");
		}
		#endregion
		#region Client
		/// 
		/// Makes this  component start searching for servers.
		/// 
		public void StartSearchingForServers()
		{
			// if (InstanceFinder.IsServer)
			// {
			// 	if (NetworkManager.StaticCanLog(LoggingType.Warning)) Debug.LogWarning("Unable to start searching for servers. Server is active.", this);
			//
			// 	return;
			// }
			Debug.Log("NetworkDiscovery is searching");
			if (InstanceFinder.IsClient)
			{
				if (NetworkManager.StaticCanLog(LoggingType.Warning)) Debug.LogWarning("Unable to start searching for servers. Client is active.", this);
				return;
			}
			if (_clientUdpClient != null)
			{
				if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Already searching for servers.", this);
				return;
			}
			_clientUdpClient = new UdpClient()
			{
				EnableBroadcast = true,
				MulticastLoopback = false,
			};
			Task.Run(SearchForServersAsync);
			if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Started searching for servers.", this);
		}
		/// 
		/// Makes this  component immediately stop searching for servers.
		/// 
		public void StopSearchingForServers()
		{
			if (_clientUdpClient == null) return;
			_clientUdpClient.Close();
			_clientUdpClient = null;
			if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Stopped searching for servers.", this);
		}
		private async void SearchForServersAsync()
		{
			byte[] secretBytes = Encoding.UTF8.GetBytes(secret);
			IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, port);
			while (_clientUdpClient != null)
			{
				await Task.Delay(TimeSpan.FromSeconds(discoveryInterval));
				await _clientUdpClient.SendAsync(secretBytes, secretBytes.Length, endPoint);
				UdpReceiveResult result = await _clientUdpClient.ReceiveAsync();
				if (BitConverter.ToBoolean(result.Buffer, 0))
				{
					ServerFoundCallback?.Invoke(result.RemoteEndPoint);
					StopSearchingForServers();
				}
			}
		}
		#endregion
	}
}