using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;

namespace Networking.Server
{
    [CreateAssetMenu(menuName = "Major Project/Networking/ClientList", order = 150)]
    public class ClientList : ScriptableObject, IEnumerable<ClientData>
    {

#if UNITY_EDITOR
      
        [SerializeField]
        [Multiline]
        private string DevNotes;
        
#endif

        [SerializeField]
        private Inventory StartInventory;

        #region Public Variables
        /// <summary>
        /// All Clients which are Currently Connected;
        /// </summary>
        public List<ClientData> ConnectedClients;


        /// <summary>
        /// Clients which connected at one point but no longer are
        /// Usefull if a client needs to reconnect
        /// </summary>
        public List<ClientData> DisconnectedClients;


        private List<NetworkConnection> PotentialClients = new List<NetworkConnection>();

        /// <summary>
        /// Called when a client connects or disconnet 
        /// </summary>
        public System.Action<List<ClientData>> OnClientsChange;


        #endregion Public Variables

        #region Private Variables
        public ServerObject server;
        #endregion Private Variables


        /// <summary>
        /// Clears the lists
        /// </summary>
        public void Reset()
        {
            ConnectedClients = new List<ClientData>();
            DisconnectedClients = new List<ClientData>();
            PotentialClients = new List<NetworkConnection>();
        }

        /// <summary>
        /// Resets the client list + registers handlers with the server
        /// </summary>
        /// <param name="serverObject">Server to register handlers with</param>
        public void SetUp(ServerObject serverObject)
        {
            this.server = serverObject;
            Reset();
            server.RegisterHandler(MsgType.Connect, OnClientConnect);
            server.RegisterHandler(MsgType.Disconnect, OnClientDisconnect);
            server.RegisterHandler(LoginProtocols.SendingLoginDetails, OnClientLogin);
        }

        public void OnClientConnect(NetworkMessage msg)
        {

            //Debug.Log("newClient: " + msg.conn.address + ", " + msg.conn.Hash() + ", " + msg.conn.hostId);

            if (ConnectedClients.Any(p => p.ID == msg.channelId))
            {
                Debug.LogError("Client[" + msg.channelId + "] already connected");
                msg.conn.Send(LoginProtocols.LoginFail, new LoginProtocols.EmptyMsg());
                return;
            }

            if (!PotentialClients.Contains(msg.conn))
                PotentialClients.Add(msg.conn);

            msg.conn.Send(LoginProtocols.RequestLoginDetails, new LoginProtocols.EmptyMsg());
            ConnectedClients.ForEach(p => msg.conn.Send(LoginProtocols.OtherClientConnected, new LoginProtocols.LoginMsg(p.Name, p.Color, p.characterAnimal)));
        }

        public void OnClientDisconnect(NetworkMessage msg)
        {
            PotentialClients.RemoveAll(p => p == msg.conn);

            if (!ConnectedClients.Any(p => p.ID == msg.conn.Hash()))
            {
                Debug.LogError("Unknown client disconnect [" + msg.conn.Hash() + "]");
                return;
            }

            ClientData client = ConnectedClients.FirstOrDefault(p => p.ID == msg.conn.Hash());
            client.Lives = 0;
            client.playerCharacter.lives = 0;
            ConnectedClients.Remove(client);
            DisconnectedClients.Add(client);
            
            ConnectedClients.ForEach(p => p.conn.Send(LoginProtocols.OtherClientDisconnected, new LoginProtocols.LoginMsg(client.Name, client.Color, client.characterAnimal)));
            PotentialClients.ForEach(p => p.Send(LoginProtocols.OtherClientDisconnected, new LoginProtocols.LoginMsg(client.Name, client.Color, client.characterAnimal)));
            Debug.Log("Disconnected: " + client.Name);
            //somewhere here we need to remove the disconnected client from the client list and update such things
            OnClientsChange?.Invoke(ConnectedClients);
        }

        public void OnClientLogin(NetworkMessage msg)
        {
            LoginProtocols.LoginMsg loginMsg;
            if (!msg.TryRead(out loginMsg))
            {
                msg.conn.Send(LoginProtocols.LoginFail, new LoginProtocols.EmptyMsg());
                return;
            }

           

            if (ConnectedClients.Any(p => p.ID == msg.conn.Hash()))
            {
                Debug.LogError("Client[" + msg.channelId + "] already connected");
                msg.conn.Send(LoginProtocols.LoginFail, new LoginProtocols.EmptyMsg());
                return;
            }

            ClientData newClient = DisconnectedClients.FirstOrDefault(p => p.ID == msg.conn.Hash());
            if (newClient != default)
            {
                Debug.Log("Reconnection: " + loginMsg.Name + ((loginMsg.Name != newClient.Name) ?  (" (Prev. " + newClient.Name +")") : ""));
                DisconnectedClients.Remove(newClient);
            }
            else
            {
                //Debug.Log("new Connection: " + loginMsg.Name);
                newClient = new ClientData();
                newClient.Inventory = Inventory.Clone(StartInventory);
            }

            newClient.Color = loginMsg.Color;
			newClient.characterAnimal = loginMsg.Animal;
            newClient.Name = loginMsg.Name;
            newClient.conn = msg.conn;

            

            ConnectedClients.Add(newClient);
            ConnectedClients.ForEach(p => p.conn.Send(LoginProtocols.OtherClientConnected, new LoginProtocols.LoginMsg(newClient.Name, newClient.Color, newClient.characterAnimal)));
            PotentialClients.RemoveAll(p => p == newClient.conn);
            PotentialClients.ForEach(p => p.Send(LoginProtocols.OtherClientConnected, new LoginProtocols.LoginMsg(newClient.Name, newClient.Color, newClient.characterAnimal)));

			newClient.conn.Send(LoginProtocols.LoginSuccess, new LoginProtocols.LoginMsg(newClient.Name, newClient.Color, newClient.characterAnimal));
            OnClientsChange.Invoke(ConnectedClients);
            newClient.SendInventory();
            newClient.SendLives();
            newClient.ChangeScene("LobbyWait");
        }

     
        #region IEnumerable Implementation
        public IEnumerator<ClientData> GetEnumerator()
        {
            return ((IEnumerable<ClientData>)ConnectedClients).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<ClientData>)ConnectedClients).GetEnumerator();
        }
        #endregion IEnumerable Implementation

        /// <summary>
        /// Returns client with id
        /// </summary>
        /// <param name="id">id to find client with</param>
        /// <returns>client with provided id else null</returns>
        public ClientData this[int id] {
            get {
                return ConnectedClients.FirstOrDefault(p => p.ID == id);
            }
        }
    }

    [System.Serializable]
    public class ClientData
    {
        /// <summary>
        /// Client Name
        /// </summary>
        public string Name;

        /// <summary>
        /// Color which represents player, picked by player
        /// </summary>
        public Color Color;

        /// <summary>
        /// Clients Current Score
        /// </summary>
        public int Score;

        /// <summary>
        /// Clients Current Lives
        /// </summary>
        public int Lives = 3;

        /// <summary>
        /// Clients character animal - needed for instantiation
        /// </summary>
        public string characterAnimal = "Bear";

        /// <summary>
        /// Network connection ID
        /// </summary>
        public int ID { get { return (conn != null) ? conn.Hash() : -1; }}

        /// <summary>
        /// Connection to Client
        /// </summary>
        public NetworkConnection conn;

        /// <summary>
        /// Current Logic elements held by player
        /// </summary>
        public Inventory Inventory;

        public Character playerCharacter;

        public void SendInventory()
        {
            //Debug.Log("Sending inventory");
            conn.SendByChannel(LogicProtocols.SendInventory, new LogicProtocols.InventoryMsg(Inventory), TransportConfigure.ReliableFragmented);
        }

        public void SendLives()
        {
            conn.Send(LogicProtocols.SendLives, new LogicProtocols.FloatMsg(Lives));
        }

        public void ChangeScene(string scene, bool Additive = false)
        {
            conn.Send(LoginProtocols.SceneChange, new LoginProtocols.SceneMsg(scene,Additive));
        }
    }
}
