using System;
using System.Net;
using System.Text;

namespace NetCoreServer
{
    /// <summary>
    /// WebSocket server
    /// </summary>
    /// <remarks> WebSocket server is used to communicate with clients using WebSocket protocol. Thread-safe.</remarks>
    public class WsServer : HttpServer, IWebSocket
    {
        internal readonly WebSocket WebSocket;

        /// <summary>
        /// Initialize WebSocket server with a given IP address and port number
        /// </summary>
        /// <param name="address">IP address</param>
        /// <param name="port">Port number</param>
        public WsServer(IPAddress address, int port) : base(address, port) { WebSocket = new WebSocket(this); }
        /// <summary>
        /// Initialize WebSocket server with a given IP address and port number
        /// </summary>
        /// <param name="address">IP address</param>
        /// <param name="port">Port number</param>
        public WsServer(string address, int port) : base(address, port) { WebSocket = new WebSocket(this); }
        /// <summary>
        /// Initialize WebSocket server with a given DNS endpoint
        /// </summary>
        /// <param name="endpoint">DNS endpoint</param>
        public WsServer(DnsEndPoint endpoint) : base(endpoint) { WebSocket = new WebSocket(this); }
        /// <summary>
        /// Initialize WebSocket server with a given IP endpoint
        /// </summary>
        /// <param name="endpoint">IP endpoint</param>
        public WsServer(IPEndPoint endpoint) : base(endpoint) { WebSocket = new WebSocket(this); }

        #region Session management

        public virtual bool CloseAll() => CloseAll(0, Span<byte>.Empty);
        public virtual bool CloseAll(int status) => CloseAll(status, Span<byte>.Empty);
        public virtual bool CloseAll(int status, string text) => CloseAll(status, Encoding.UTF8.GetBytes(text));
        public virtual bool CloseAll(int status, ReadOnlySpan<char> text) => CloseAll(status, Encoding.UTF8.GetBytes(text.ToArray()));
        public virtual bool CloseAll(int status, byte[] buffer) => CloseAll(status, buffer.AsSpan());
        public virtual bool CloseAll(int status, byte[] buffer, long offset, long size) => CloseAll(status, buffer.AsSpan((int)offset, (int)size));
        public virtual bool CloseAll(int status, ReadOnlySpan<byte> buffer)
        {
            lock (WebSocket.WsSendLock)
            {
                WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_CLOSE, false, buffer, status);
                if (!Multicast(WebSocket.WsSendBuffer.AsSpan()))
                    return false;

                return base.DisconnectAll();
            }
        }

        #endregion

        #region Multicasting

        public override bool Multicast(ReadOnlySpan<byte> buffer)
        {
            if (!IsStarted)
                return false;

            if (buffer.IsEmpty)
                return true;

            // Multicast data to all WebSocket sessions
            foreach (var session in Sessions.Values)
            {
                if (session is WsSession wsSession)
                {
                    if (wsSession.WebSocket.WsHandshaked)
                        wsSession.SendAsync(buffer);
                }
            }

            return true;
        }

        #endregion

        #region WebSocket multicast text methods

        public bool MulticastText(string text) => MulticastText(Encoding.UTF8.GetBytes(text));
        public bool MulticastText(ReadOnlySpan<char> text) => MulticastText(Encoding.UTF8.GetBytes(text.ToArray()));
        public bool MulticastText(byte[] buffer) => MulticastText(buffer.AsSpan());
        public bool MulticastText(byte[] buffer, long offset, long size) => MulticastText(buffer.AsSpan((int)offset, (int)size));
        public bool MulticastText(ReadOnlySpan<byte> buffer)
        {
            lock (WebSocket.WsSendLock)
            {
                WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_TEXT, false, buffer);
                return Multicast(WebSocket.WsSendBuffer.AsSpan());
            }
        }


        #endregion

        #region WebSocket multicast binary methods

        public bool MulticastBinary(string text) => MulticastBinary(Encoding.UTF8.GetBytes(text));
        public bool MulticastBinary(ReadOnlySpan<char> text) => MulticastBinary(Encoding.UTF8.GetBytes(text.ToArray()));
        public bool MulticastBinary(byte[] buffer) => MulticastBinary(buffer.AsSpan());
        public bool MulticastBinary(byte[] buffer, long offset, long size) => MulticastBinary(buffer.AsSpan((int)offset, (int)size));
        public bool MulticastBinary(ReadOnlySpan<byte> buffer)
        {
            lock (WebSocket.WsSendLock)
            {
                WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_BINARY, false, buffer);
                return Multicast(WebSocket.WsSendBuffer.AsSpan());
            }
        }


        #endregion

        #region WebSocket multicast ping methods

        public bool MulticastPing(string text) => MulticastPing(Encoding.UTF8.GetBytes(text));
        public bool MulticastPing(ReadOnlySpan<char> text) => MulticastPing(Encoding.UTF8.GetBytes(text.ToArray()));
        public bool MulticastPing(byte[] buffer) => MulticastPing(buffer.AsSpan());
        public bool MulticastPing(byte[] buffer, long offset, long size) => MulticastPing(buffer.AsSpan((int)offset, (int)size));
        public bool MulticastPing(ReadOnlySpan<byte> buffer)
        {
            lock (WebSocket.WsSendLock)
            {
                WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PING, false, buffer);
                return Multicast(WebSocket.WsSendBuffer.AsSpan());
            }
        }

        #endregion

        protected override TcpSession CreateSession() { return new WsSession(this); }
    }
}