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

namespace Llaps.Server
{
    [RequireComponent(typeof(Common.ConnectionConfig))]
    public class ServerConnection : MonoBehaviour
    {
        #region Public Members
        /// <summary>
        /// Should we start the server on behavior start?
        /// If this is false StartServerConnection() must be called to start the server connection.
        /// </summary>
        [Tooltip("Should we start the server on behavior start")]
        public bool StartServerOnStartup = true;

        /// <summary>
        /// Should we poll connections and handle network events on update
        /// or physics update?
        /// </summary>
        [Tooltip("Should we poll connections on update or fixed updates")]
        public bool UseFixedUpdate = true;

        /// <summary>
        /// Which port should the server accept connections on?
        /// The range should be valid for both UNIX and Windows operating systems.
        /// Ports less than 1024 are normal reserver by the system.
        /// </summary>
        [Range(1025, 65535)]
        [Tooltip("The port on which the server should accept connections")]
        public int ServerPort = 5598;

        /// <summary>
        /// The maximum number of client connections the server connection will
        /// allow to connect.
        /// I've limited this number to 255, but the Unity documentation doesn't
        /// actually specify a maximum value...
        /// </summary>
        [Range(1, 255)]
        [Tooltip("The maximum number of client connections allowed to connect")]
        public int MaximumConnections = 100;

        /// <summary>
        /// Requested Quality of Service Channels
        /// </summary>
        [Tooltip("All requested quality of service channels")]
        public QosType[] Channels = new QosType[] { QosType.Reliable };
        #endregion

        #region Private Constants
        /// <summary>
        /// The size of the buffer used on polling
        /// </summary>
        private const int c_bufferSize = 1024;
        #endregion

        #region Private Members
        /// <summary>
        /// The connection id of the host
        /// </summary>
        private int m_hostId = LLAPS.c_invalidId;

        /// <summary>
        /// All registered QOS channels
        /// </summary>
        private Dictionary<QosType, byte> m_qosChannels = new Dictionary<QosType, byte>();

        /// <summary>
        /// Is the server setup and running
        /// </summary>
        private bool m_isRunning = false;
        #endregion

        #region Public Methods
        /// <summary>
        /// Setup the Unity LLAPI ready for client connections
        /// </summary>
        /// <returns>True on success, false otherwise</returns>
        public bool StartServerConnection()
        {
            if (m_isRunning)
            {
                Debug.LogError(LLAPS.FormatError(typeof(ServerConnection), "The server is already running, please call StopServerConnection before calling Start again."));
                return false;
            }

            NetworkTransport.Init();

            var configBehaviour = this.GetComponent<Common.ConnectionConfig>();
            var configuration = configBehaviour.GetConnectionConfiguration();

            m_qosChannels.Clear();
            foreach (var channel in this.Channels)
            {
                m_qosChannels[channel] = configuration.AddChannel(channel);
            }

            var topology = new HostTopology(configuration, this.MaximumConnections);
            m_hostId = NetworkTransport.AddHost(topology, this.ServerPort, null);

            bool success = m_hostId != LLAPS.c_invalidId;
            LLAPS.BroadcastMessageAll("LLAPS_ServerStarted", success);
            m_isRunning = true;

            return success;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Called when this behavior is first started.
        /// This is only ever called once.
        /// </summary>
        private void Start()
        {
            if (this.StartServerOnStartup)
            {
                var result = this.StartServerConnection();
                Debug.Assert(result, LLAPS.FormatError(typeof(ServerConnection), "Failed to start server connection."));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void Update()
        {
            if (!this.UseFixedUpdate)
            {
                PollConnections();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void FixedUpdate()
        {
            if (this.UseFixedUpdate)
            {
                PollConnections();
            }
        }

        /// <summary>
        /// Receive network events and process them
        /// </summary>
        private void PollConnections()
        {
            byte error;
            int hostId, connectionId, channelId, dataSize;
            var recBuffer = new byte[c_bufferSize];

            var dataEvent = NetworkTransport.Receive(out hostId, out connectionId, out channelId, recBuffer, c_bufferSize, out dataSize, out error);

            switch (dataEvent)
            {
                case NetworkEventType.ConnectEvent:
                    {
                    }
                    break;

                case NetworkEventType.DataEvent:
                    {
                    }
                    break;

                case NetworkEventType.DisconnectEvent:
                    {
                    }
                    break;
                default:
                    break;
            }
        }
        #endregion
    }
}
