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>
    {

        [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;


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


        #endregion Public Variables

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


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

        /// <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.server;
            Reset();

            server.RegisterHandler(MsgType.Connect, OnClientConnect);
            server.RegisterHandler(MsgType.Disconnect, OnClientDisconnect);
            server.RegisterHandler(LoginProtocols.SendingLoginDetails, OnClientLogin);
        }

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

            msg.conn.Send(LoginProtocols.RequestLoginDetails, new LoginProtocols.EmptyMsg());
        }

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

            ClientData client = ConnectedClients.FirstOrDefault(p => p.ID == msg.conn.connectionId);
            ConnectedClients.Remove(client);
            DisconnectedClients.Add(client);

            Debug.Log("Disconnected: " + client.Name);
            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.channelId))
            {
                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.connectionId);
            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);

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

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

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

    [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 character animal - needed for instantiation
		/// </summary>
		public string characterAnimal = "Bear";

        /// <summary>
        /// Clients number of collected items - for colelction level
        /// </summary>
        public int SceneScore;

        /// <summary>
        /// Network connection ID
        /// </summary>
        public int ID { get { return (conn != null) ? conn.connectionId : -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 SendScore()
        {
            conn.Send(LogicProtocols.SendScore, new LogicProtocols.FloatMsg(SceneScore));
        }

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