using FishNet.Documenting;
using FishNet.Managing;
using FishNet.Object;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine.SceneManagement;
namespace FishNet.Connection
{
    /// 
    /// A container for a connected client used to perform actions on and gather information for the declared client.
    /// 
    public partial class NetworkConnection : IEquatable
    {
        #region Public.
        /// 
        /// Called after this connection has loaded start scenes. Boolean will be true if asServer. Available to this connection and server.
        /// 
        public event Action OnLoadedStartScenes;
        /// 
        /// Called after connection gains ownership of an object, and after the object has been added to Objects. Available to this connection and server.
        /// 
        public event Action OnObjectAdded;
        /// 
        /// Called after connection loses ownership of an object, and after the object has been removed from Objects. Available to this connection and server.
        /// 
        public event Action OnObjectRemoved;
        /// 
        /// NetworkManager managing this class.
        /// 
        public NetworkManager NetworkManager { get; private set; }
        /// 
        /// True if connection has loaded start scenes. Available to this connection and server.
        /// 
        public bool LoadedStartScenes() => (_loadedStartScenesAsServer || _loadedStartScenesAsClient);
        /// 
        /// 
        /// 
        public bool LoadedStartScenes(bool asServer)
        {
            if (asServer)
                return _loadedStartScenesAsServer;
            else
                return _loadedStartScenesAsClient;
        }
        /// 
        /// True if loaded start scenes as server.
        /// 
        private bool _loadedStartScenesAsServer;
        /// 
        /// True if loaded start scenes as client.
        /// 
        private bool _loadedStartScenesAsClient;
        /// 
        /// ObjectIds to use for predicted spawning.
        /// 
        internal Queue PredictedObjectIds = new Queue();
        /// 
        /// True if this connection is authenticated. Only available to server.
        /// 
        public bool Authenticated { get; private set; }
        /// 
        /// True if this connection IsValid and not Disconnecting.
        /// 
        public bool IsActive => (ClientId >= 0 && !Disconnecting);
        /// 
        /// True if this connection is valid. An invalid connection indicates no client is set for this reference.
        /// 
        public bool IsValid => (ClientId >= 0);
        /// 
        /// Unique Id for this connection.
        /// 
        public int ClientId = -1;
        /// 
        /// 
        /// 
        private HashSet _objects = new HashSet();
        /// 
        /// Objects owned by this connection. Available to this connection and server.
        /// 
        public IReadOnlyCollection Objects => _objects;
        /// 
        /// The first object within Objects.
        /// 
        public NetworkObject FirstObject { get; private set; }
        /// 
        /// Scenes this connection is in. Available to this connection and server.
        /// 
        public HashSet Scenes { get; private set; } = new HashSet();
        /// 
        /// True if this connection is being disconnected. Only available to server.
        /// 
        public bool Disconnecting { get; private set; }
        /// 
        /// Tick when Disconnecting was set.
        /// 
        internal uint DisconnectingTick { get; private set; }
        /// 
        /// Custom data associated with this connection which may be modified by the user.
        /// The value of this field are not synchronized over the network.
        /// 
        public object CustomData = null;
        /// 
        /// Local tick when the connection last replicated.
        /// 
        public uint LocalReplicateTick { get; internal set; }
        /// 
        /// Tick of the last packet received from this connection.
        /// This value is only available on the server.
        /// 
        /* This is not used internally. At this time it's just
         * here for the users convienence. */
        public uint LastPacketTick { get; private set; }
        /// 
        /// Sets LastPacketTick value.
        /// 
        /// 
        internal void SetLastPacketTick(uint value)
        {
            //If new largest tick from the client then update client tick data.
            if (value > LastPacketTick)
            {
                _latestTick = value;
                _serverLatestTick = NetworkManager.TimeManager.Tick;
            }
            LastPacketTick = value;
        }
        /// 
        /// Latest tick that did not arrive out of order from this connection.
        /// 
        private uint _latestTick;
        /// 
        /// Tick on the server when latestTick was set.
        /// 
        private uint _serverLatestTick;
        [Obsolete("Use LocalTick instead.")] //Remove on 2023/06/01
        public uint Tick => LocalTick;
        /// 
        /// Current approximate local tick as it is on this connection.
        /// 
        public uint LocalTick
        {
            get
            {
                NetworkManager nm = NetworkManager;
                if (nm != null)
                {
                    uint diff = (nm.TimeManager.Tick - _serverLatestTick);
                    return (diff + _latestTick);
                }
                //Fall through, could not process.
                return 0;
            }
        }
        #endregion
        #region Const.
        /// 
        /// Value used when ClientId has not been set.
        /// 
        public const int UNSET_CLIENTID_VALUE = -1;
        #endregion
        #region Comparers.
        public override bool Equals(object obj)
        {
            if (obj is NetworkConnection nc)
                return (nc.ClientId == this.ClientId);
            else
                return false;
        }
        public bool Equals(NetworkConnection nc)
        {
            if (nc is null)
                return false;
            //If either is -1 Id.
            if (this.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE || nc.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE)
                return false;
            //Same object.
            if (System.Object.ReferenceEquals(this, nc))
                return true;
            return (this.ClientId == nc.ClientId);
        }
        public override int GetHashCode()
        {
            return ClientId;
        }
        public static bool operator ==(NetworkConnection a, NetworkConnection b)
        {
            if (a is null && b is null)
                return true;
            if (a is null && !(b is null))
                return false;
            return (b == null) ? a.Equals(b) : b.Equals(a);
        }
        public static bool operator !=(NetworkConnection a, NetworkConnection b)
        {
            return !(a == b);
        }
        #endregion
        [APIExclude]
        public NetworkConnection() { }
        [APIExclude]
        public NetworkConnection(NetworkManager manager, int clientId, bool asServer)
        {
            Initialize(manager, clientId, asServer);
        }
        public void Dispose()
        {
            foreach (PacketBundle p in _toClientBundles)
                p.Dispose();
            _toClientBundles.Clear();
        }
        /// 
        /// Initializes this for use.
        /// 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void Initialize(NetworkManager nm, int clientId, bool asServer)
        {
            NetworkManager = nm;
            ClientId = clientId;
            //Only the server uses the ping and buffer.
            if (asServer)
            {
                InitializeBuffer();
                InitializePing();
            }
        }
        /// 
        /// Resets this instance.
        /// 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void Reset()
        {
            _latestTick = 0;
            _serverLatestTick = 0;
            LastPacketTick = 0;
            ClientId = -1;
            ClearObjects();
            Authenticated = false;
            NetworkManager = null;
            _loadedStartScenesAsClient = false;
            _loadedStartScenesAsServer = false;
            SetDisconnecting(false);
            Scenes.Clear();
            PredictedObjectIds.Clear();
            ResetPingPong();
            LevelOfDetails.Clear();
            AllowedForcedLodUpdates = 0;
            LastLevelOfDetailUpdate = 0;
            LevelOfDetailInfractions = 0;
        }
        /// 
        /// Sets Disconnecting boolean for this connection.
        /// 
        internal void SetDisconnecting(bool value)
        {
            Disconnecting = value;
            if (Disconnecting)
                DisconnectingTick = NetworkManager.TimeManager.LocalTick;
        }
        /// 
        /// Disconnects this connection.
        /// 
        /// True to disconnect immediately. False to send any pending data first.
        public void Disconnect(bool immediately)
        {
            if (Disconnecting)
            {
                NetworkManager.LogWarning($"ClientId {ClientId} is already disconnecting.");
                return;
            }
            SetDisconnecting(true);
            //If immediately then force disconnect through transport.
            if (immediately)
                NetworkManager.TransportManager.Transport.StopConnection(ClientId, true);
            //Otherwise mark dirty so server will push out any pending information, and then disconnect.
            else
                ServerDirty();
        }
        /// 
        /// Returns if just loaded start scenes and sets them as loaded if not.
        /// 
        /// 
        internal bool SetLoadedStartScenes(bool asServer)
        {
            bool loadedToCheck = (asServer) ? _loadedStartScenesAsServer : _loadedStartScenesAsClient;
            //Result becomes true if not yet loaded start scenes.
            bool result = !loadedToCheck;
            if (asServer)
                _loadedStartScenesAsServer = true;
            else
                _loadedStartScenesAsClient = true;
            OnLoadedStartScenes?.Invoke(this, asServer);
            return result;
        }
        /// 
        /// Sets connection as authenticated.
        /// 
        internal void ConnectionAuthenticated()
        {
            Authenticated = true;
        }
        /// 
        /// Adds to Objects owned by this connection.
        /// 
        /// 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void AddObject(NetworkObject nob)
        {
            _objects.Add(nob);
            //If adding the first object then set new FirstObject.
            if (_objects.Count == 1)
                FirstObject = nob;
            OnObjectAdded?.Invoke(nob);
        }
        /// 
        /// Removes from Objects owned by this connection.
        /// 
        /// 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void RemoveObject(NetworkObject nob)
        {
            _objects.Remove(nob);
            //If removing the first object then set a new one.
            if (nob == FirstObject)
                SetFirstObject();
            OnObjectRemoved?.Invoke(nob);
        }
        /// 
        /// Clears all Objects.
        /// 
        private void ClearObjects()
        {
            _objects.Clear();
            FirstObject = null;
        }
        /// 
        /// Sets FirstObject using the first element in Objects.
        /// 
        private void SetFirstObject()
        {
            if (_objects.Count == 0)
            {
                FirstObject = null;
            }
            else
            {
                foreach (NetworkObject nob in Objects)
                {
                    FirstObject = nob;
                    break;
                }
            }
        }
        /// 
        /// Adds a scene to this connections Scenes.
        /// 
        /// 
        /// 
        internal bool AddToScene(Scene scene)
        {
            return Scenes.Add(scene);
        }
        /// 
        /// Removes a scene to this connections Scenes.
        /// 
        /// 
        /// 
        internal bool RemoveFromScene(Scene scene)
        {
            return Scenes.Remove(scene);
        }
    }
}