using FishNet.Connection;
using FishNet.Managing.Logging;
using FishNet.Managing.Transporting;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Transporting.Multipass;
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Managing.Server
{
    public sealed partial class ServerManager : MonoBehaviour
    {
        #region Public.
        /// 
        /// Called when a client is removed from the server using Kick. This is invoked before the client is disconnected.
        /// NetworkConnection when available, clientId, and KickReason are provided.
        /// 
        public event Action OnClientKick;
        #endregion
        /// 
        /// Returns true if only one server is started.
        /// 
        /// 
        public bool OneServerStarted()
        {
            int startedCount = 0;
            TransportManager tm = NetworkManager.TransportManager;
            //If using multipass check all transports.
            if (tm.Transport is Multipass mp)
            {
                foreach (Transport t in mp.Transports)
                {
                    //Another transport is started, no need to load start scenes again.
                    if (t.GetConnectionState(true) == LocalConnectionState.Started)
                        startedCount++;
                }
            }
            //Not using multipass.
            else
            {
                if (tm.Transport.GetConnectionState(true) == LocalConnectionState.Started)
                    startedCount = 1;
            }
            return (startedCount == 1);
        }
        /// 
        /// Returns true if any server socket is in the started state.
        /// 
        /// When set the transport on this index will be ignored. This value is only used with Multipass.
        /// 
        public bool AnyServerStarted(int? excludedIndex = null)
        {
            TransportManager tm = NetworkManager.TransportManager;
            //If using multipass check all transports.
            if (tm.Transport is Multipass mp)
            {
                //Get transport which had state changed.
                Transport excludedTransport = (excludedIndex == null) ? null : mp.GetTransport(excludedIndex.Value);
                foreach (Transport t in mp.Transports)
                {
                    /* Skip t if is the transport that had it's state changed.
                     * We are looking for other transports already in started. */
                    if (t == excludedTransport)
                        continue;
                    //Another transport is started, no need to load start scenes again.
                    if (t.GetConnectionState(true) == LocalConnectionState.Started)
                        return true;
                }
            }
            //Not using multipass.
            else
            {
                return (tm.Transport.GetConnectionState(true) == LocalConnectionState.Started);
            }
            //Fall through, none started.
            return false;
        }
        /// 
        /// Spawns an object over the network. Can only be called on the server.
        /// 
        /// GameObject instance to spawn.
        /// Connection to give ownership to.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Spawn(GameObject go, NetworkConnection ownerConnection = null)
        {
            if (go == null)
            {
                NetworkManager.LogWarning($"GameObject cannot be spawned because it is null.");
                return;
            }
            NetworkObject nob = go.GetComponent();
            Spawn(nob, ownerConnection);
        }
        /// 
        /// Spawns an object over the network. Can only be called on the server.
        /// 
        /// MetworkObject instance to spawn.
        /// Connection to give ownership to.
        public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null)
        {
            Objects.Spawn(nob, ownerConnection);
        }
        /// 
        /// Despawns an object over the network. Can only be called on the server.
        /// 
        /// GameObject instance to despawn.
        /// Overrides the default DisableOnDespawn value for this single despawn. Scene objects will never be destroyed.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Despawn(GameObject go, DespawnType? despawnType = null)
        {
            if (go == null)
            {
                NetworkManager.LogWarning($"GameObject cannot be despawned because it is null.");
                return;
            }
            NetworkObject nob = go.GetComponent();
            Despawn(nob, despawnType);
        }
        /// 
        /// Despawns an object over the network. Can only be called on the server.
        /// 
        /// NetworkObject instance to despawn.
        /// Despawn override type.
        public void Despawn(NetworkObject networkObject, DespawnType? despawnType = null)
        {
            DespawnType resolvedDespawnType = (despawnType == null)
                ? networkObject.GetDefaultDespawnType()
                : despawnType.Value;
            Objects.Despawn(networkObject, resolvedDespawnType, true);
        }
        /// 
        /// Kicks a connection immediately while invoking OnClientKick.
        /// 
        /// Client to kick.
        /// Reason client is being kicked.
        /// How to print logging as.
        /// Optional message to be debug logged.
        public void Kick(NetworkConnection conn, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
        {
            if (!conn.IsValid)
                return;
            OnClientKick?.Invoke(conn, conn.ClientId, kickReason);
            if (conn.IsActive)
                conn.Disconnect(true);
            if (!string.IsNullOrEmpty(log))
                NetworkManager.Log(loggingType, log);
        }
        /// 
        /// Kicks a connection immediately while invoking OnClientKick.
        /// 
        /// ClientId to kick.
        /// Reason client is being kicked.
        /// How to print logging as.
        /// Optional message to be debug logged.
        public void Kick(int clientId, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
        {
            OnClientKick?.Invoke(null, clientId, kickReason);
            NetworkManager.TransportManager.Transport.StopConnection(clientId, true);
            if (!string.IsNullOrEmpty(log))
                NetworkManager.Log(loggingType, log);
        }
        /// 
        /// Kicks a connection immediately while invoking OnClientKick.
        /// 
        /// Client to kick.
        /// Reader to clear before kicking.
        /// Reason client is being kicked.
        /// How to print logging as.
        /// Optional message to be debug logged.
        public void Kick(NetworkConnection conn, Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
        {
            reader.Clear();
            Kick(conn, kickReason, loggingType, log);
        }
    }
}