#region MIT License
/**
 * WebSocket.cs
 *
 * A C# implementation of the WebSocket interface.
 * This code derived from WebSocket.java (http://github.com/adamac/Java-WebSocket-client).
 *
 * The MIT License
 *
 * Copyright (c) 2009 Adam MacBeth
 * Copyright (c) 2010-2012 sta.blockhead
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using WebSocketSharp.Frame;
using WebSocketSharp.Net;
using WebSocketSharp.Net.Sockets;

namespace WebSocketSharp
{

    /// <summary>
    /// Implements the WebSocket interface.
    /// </summary>
    /// <remarks>
    /// The WebSocket class provides methods and properties for two-way communication using the WebSocket protocol (RFC 6455).
    /// </remarks>
    public class WebSocket : IDisposable
    {
        #region Private Const Fields

        private const int _fragmentLen = 1016; // Max value is int.MaxValue - 14.
        private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
        private const string _version = "13";

        #endregion

        #region Private Fields

        private string _base64key;
        private HttpListenerContext _httpContext;
        private WebSocketContext _context;
        private System.Net.IPEndPoint _endPoint;
        private string _extensions;
        private AutoResetEvent _exitMessageLoop;
        private Object _forClose;
        private Object _forSend;
        private bool _isClient;
        private bool _isSecure;
        private string _protocol;
        private string _protocols;
        private NameValueCollection _queryString;
        private volatile WsState _readyState;
        private AutoResetEvent _receivePong;
        private TcpClient _tcpClient;
        private Uri _uri;
        private SynchronizedCollection<WsFrame> _unTransmittedBuffer;
        private WsStream _wsStream;
        private string _origin;

        #endregion

        #region Private Constructor

        private WebSocket()
        {
            _extensions = String.Empty;
            _forClose = new Object();
            _forSend = new Object();
            _protocol = String.Empty;
            _readyState = WsState.CONNECTING;
            _unTransmittedBuffer = new SynchronizedCollection<WsFrame>();
        }

        #endregion

        #region Internal Constructor

        internal WebSocket(HttpListenerWebSocketContext context)
            : this()
        {
            _uri = Ext.ToUri(context.Path);
            _context = context;
            _httpContext = context.BaseContext;
            _wsStream = context.Stream;
            _endPoint = context.ServerEndPoint;
            _isClient = false;
            _isSecure = context.IsSecureConnection;
        }

        internal WebSocket(TcpListenerWebSocketContext context)
            : this()
        {
            _uri = Ext.ToUri(context.Path);
            _context = context;
            _tcpClient = context.Client;
            _wsStream = context.Stream;
            _endPoint = context.ServerEndPoint;
            _isClient = false;
            _isSecure = context.IsSecureConnection;
        }

        #endregion

        #region Public Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="WebSocketSharp.WebSocket"/> class with the specified WebSocket URL and subprotocols.
        /// </summary>
        /// <param name="url">
        /// A <see cref="string"/> that contains the WebSocket URL.
        /// </param>
        /// <param name="protocols">
        /// An array of <see cref="string"/> that contains the WebSocket subprotocols if any.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="url"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="url"/> is not valid WebSocket URL.
        /// </exception>
        public WebSocket(string url, params string[] protocols)
            : this()
        {
            if (url == null)
                throw new ArgumentNullException("url");

            Uri uri;
            string msg;
            if (!tryCreateUri(url, out uri, out msg))
                throw new ArgumentException(msg, "url");

            _uri = uri;
            _protocols = Ext.ToString(protocols, ", ");
            _base64key = createBase64Key();
            _isClient = true;
            _isSecure = uri.Scheme == "wss" ? true : false;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebSocketSharp.WebSocket"/> class with the specified WebSocket URL, OnOpen, OnMessage, OnError, OnClose event handlers and subprotocols.
        /// </summary>
        /// <param name="url">
        /// A <see cref="string"/> that contains the WebSocket URL.
        /// </param>
        /// <param name="onOpen">
        /// An OnOpen event handler.
        /// </param>
        /// <param name="onMessage">
        /// An OnMessage event handler.
        /// </param>
        /// <param name="onError">
        /// An OnError event handler.
        /// </param>
        /// <param name="onClose">
        /// An OnClose event handler.
        /// </param>
        /// <param name="protocols">
        /// An array of <see cref="string"/> that contains the WebSocket subprotocols if any.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="url"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="url"/> is not valid WebSocket URL.
        /// </exception>
        public WebSocket(
          string url,
          EventHandler onOpen,
          EventHandler<MessageEventArgs> onMessage,
          EventHandler<ErrorEventArgs> onError,
          EventHandler<CloseEventArgs> onClose,
          params string[] protocols)
            : this(url, protocols)
        {
            OnOpen = onOpen;
            OnMessage = onMessage;
            OnError = onError;
            OnClose = onClose;

            Connect();
        }

        #endregion

        #region Internal Property

        internal NameValueCollection QueryString
        {
            get
            {
                return _queryString;
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the amount of untransmitted data.
        /// </summary>
        /// <value>
        /// The number of bytes of untransmitted data.
        /// </value>
        public ulong BufferedAmount
        {
            get
            {
                lock (_unTransmittedBuffer.SyncRoot)
                {
                    ulong bufferedAmount = 0;
                    foreach (WsFrame frame in _unTransmittedBuffer)
                        bufferedAmount += frame.PayloadLength;

                    return bufferedAmount;
                }
            }
        }

        /// <summary>
        /// Gets the extensions selected by the server.
        /// </summary>
        /// <value>
        /// A <see cref="string"/> that contains the extensions if any. By default, <c>String.Empty</c>. (Currently this will only ever be the <c>String.Empty</c>.)
        /// </value>
        public string Extensions
        {
            get
            {
                return _extensions;
            }
        }

        /// <summary>
        /// Gets a value indicating whether a connection is alive.
        /// </summary>
        /// <value>
        /// <c>true</c> if the connection is alive; otherwise, <c>false</c>.
        /// </value>
        public bool IsAlive
        {
            get
            {
                if (_readyState != WsState.OPEN)
                    return false;

                return Ping();
            }
        }

        /// <summary>
        /// Gets a value indicating whether a connection is secure.
        /// </summary>
        /// <value>
        /// <c>true</c> if the connection is secure; otherwise, <c>false</c>.
        /// </value>
        public bool IsSecure
        {
            get
            {
                return _isSecure;
            }
        }

        /// <summary>
        /// Gets the subprotocol selected by the server.
        /// </summary>
        /// <value>
        /// A <see cref="string"/> that contains the subprotocol if any. By default, <c>String.Empty</c>.
        /// </value>
        public string Protocol
        {
            get
            {
                return _protocol;
            }
        }

        /// <summary>
        /// Gets the state of the connection.
        /// </summary>
        /// <value>
        /// A <see cref="WebSocketSharp.WsState"/>. By default, <c>WsState.CONNECTING</c>.
        /// </value>
        public WsState ReadyState
        {
            get
            {
                return _readyState;
            }
        }

        /// <summary>
        /// Gets the untransmitted WebSocket frames.
        /// </summary>
        /// <value>
        /// A <c>IList&lt;WsFrame&gt;</c> that contains the untransmitted WebSocket frames.
        /// </value>
        public IList<WsFrame> UnTransmittedBuffer
        {
            get
            {
                return _unTransmittedBuffer;
            }
        }

        /// <summary>
        /// Gets or sets the WebSocket URL.
        /// </summary>
        /// <value>
        /// A <see cref="Uri"/> that contains the WebSocket URL.
        /// </value>
        public Uri Url
        {
            get { return _uri; }
            set
            {
                if (_readyState == WsState.CONNECTING && !_isClient)
                    _uri = value;
            }
        }

        /// <summary>
        /// Gets or sets the WebSocket Origin.
        /// </summary>
        public string Origin
        {
            get { return this._origin; }
            set { this._origin = value; }
        }

        /// <summary>
        /// Gets or sets the extra WebSocket handshake headers.
        /// </summary>
        public IDictionary<string, string> ExtraHeaders { get; set; }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when the WebSocket connection has been established.
        /// </summary>
        public event EventHandler OnOpen;

        /// <summary>
        /// Occurs when the WebSocket receives a data frame.
        /// </summary>
        public event EventHandler<MessageEventArgs> OnMessage;

        /// <summary>
        /// Occurs when the WebSocket gets an error.
        /// </summary>
        public event EventHandler<ErrorEventArgs> OnError;

        /// <summary>
        /// Occurs when the WebSocket receives a Close frame or the Close method is called.
        /// </summary>
        public event EventHandler<CloseEventArgs> OnClose;

        #endregion

        #region Private Methods

        // As Server
        private void acceptHandshake()
        {
            var req = receiveOpeningHandshake();

            string msg;
            if (!isValidRequest(req, out msg))
            {
                onError(msg);
                close(CloseStatusCode.HANDSHAKE_FAILURE, msg);
                return;
            }

            sendResponseHandshake();
            onOpen();
        }

        private bool canSendAsCloseFrame(PayloadData data)
        {
            if (data.Length >= 2)
            {
                var code = Ext.To<ushort>(Ext.SubArray(data.ToBytes(), 0, 2), ByteOrder.BIG);
                if (code == (ushort)CloseStatusCode.NO_STATUS_CODE ||
                    code == (ushort)CloseStatusCode.ABNORMAL ||
                    code == (ushort)CloseStatusCode.HANDSHAKE_FAILURE)
                    return false;
            }

            return true;
        }

        private void close(HttpStatusCode code)
        {
            if (_readyState != WsState.CONNECTING || _isClient)
                return;

            sendResponseHandshake(code);
            closeConnection();
        }

        private void close(PayloadData data)
        {
#if DEBUG
            Console.WriteLine("WS: Info@close: Current thread IsBackground ?: {0}", Thread.CurrentThread.IsBackground);
#endif
            lock (_forClose)
            {
                // Whether the closing handshake has been started already ?
                if (_readyState == WsState.CLOSING ||
                    _readyState == WsState.CLOSED)
                    return;

                // Whether the closing handshake as server is started before the connection has been established ?
                if (_readyState == WsState.CONNECTING && !_isClient)
                {
                    sendResponseHandshake(HttpStatusCode.BadRequest);
                    onClose(new CloseEventArgs(data));

                    return;
                }

                _readyState = WsState.CLOSING;
            }

            // Whether a close status code that must not be set for send is used ?
            if (!canSendAsCloseFrame(data))
            {
                onClose(new CloseEventArgs(data));
                return;
            }

            closeHandshake(data);
#if DEBUG
            Console.WriteLine("WS: Info@close: Exits close method.");
#endif
        }

        private void close(CloseStatusCode code, string reason)
        {
            close((ushort)code, reason);
        }

        private void close(ushort code, string reason)
        {
            var data = new List<byte>(Ext.ToBytes(code, ByteOrder.BIG));
            if (!Ext.IsNullOrEmpty(reason))
            {
                var buffer = Encoding.UTF8.GetBytes(reason);
                data.AddRange(buffer);
            }

            var payloadData = new PayloadData(data.ToArray());
            if (payloadData.Length > 125)
            {
                var msg = "A Close frame must have a payload length of 125 bytes or less.";
                onError(msg);
                return;
            }

            close(payloadData);
        }

        private bool closeConnection()
        {
            _readyState = WsState.CLOSED;

            try
            {
                if (_httpContext != null)
                {
                    _httpContext.Response.Close();
                    _wsStream = null;
                    _httpContext = null;
                }

                if (_wsStream != null)
                {
                    _wsStream.Dispose();
                    _wsStream = null;
                }

                if (_tcpClient != null)
                {
                    _tcpClient.Close();
                    _tcpClient = null;
                }

                return true;
            }
            catch (Exception ex)
            {
                onError(ex.Message);
                return false;
            }
        }

        private void closeHandshake(PayloadData data)
        {
            var args = new CloseEventArgs(data);
            var frame = createFrame(Fin.FINAL, Opcode.CLOSE, data);
            send(frame);
            onClose(args);
        }

        // As Client
        private string createBase64Key()
        {
            var src = new byte[16];
            var rand = new Random();
            rand.NextBytes(src);

            return Convert.ToBase64String(src);
        }

        // As Client
        private void createClientStream()
        {
            var host = _uri.DnsSafeHost;
            var port = _uri.Port > 0
                     ? _uri.Port
                     : _isSecure ? 443 : 80;

            _tcpClient = new TcpClient(host, port);
            _wsStream = WsStream.CreateClientStream(_tcpClient, host, _isSecure);
        }

        private WsFrame createFrame(Fin fin, Opcode opcode, PayloadData payloadData)
        {
            return _isClient
                   ? new WsFrame(fin, opcode, payloadData)
                   : new WsFrame(fin, opcode, Mask.UNMASK, payloadData);
        }

        // As Client
        private RequestHandshake createOpeningHandshake()
        {
            var path = _uri.PathAndQuery;
            var host = _uri.DnsSafeHost;
            var port = ((System.Net.IPEndPoint)_tcpClient.Client.RemoteEndPoint).Port;
            if (port != 80)
                host += ":" + port;

            var req = new RequestHandshake(path);
            req.AddHeader("Host", host);
            req.AddHeader("Sec-WebSocket-Key", _base64key);
            if (!Ext.IsNullOrEmpty(_protocols))
                req.AddHeader("Sec-WebSocket-Protocol", _protocols);
            req.AddHeader("Sec-WebSocket-Version", _version);
            if (!string.IsNullOrEmpty(this._origin))
                req.AddHeader("Origin", this._origin);
            //extra headers
            if (this.ExtraHeaders != null)
                foreach (var i in this.ExtraHeaders)
                    req.AddHeader(i.Key, i.Value);

            return req;
        }

        // As Server
        private ResponseHandshake createResponseHandshake()
        {
            var res = new ResponseHandshake();
            res.AddHeader("Sec-WebSocket-Accept", createResponseKey());

            return res;
        }

        // As Server
        private ResponseHandshake createResponseHandshake(HttpStatusCode code)
        {
            var res = ResponseHandshake.CreateCloseResponse(code);
            res.AddHeader("Sec-WebSocket-Version", _version);

            return res;
        }

        private string createResponseKey()
        {
            SHA1 sha1 = new SHA1CryptoServiceProvider();
            var sb = new StringBuilder(_base64key);
            sb.Append(_guid);
            var src = sha1.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));

            return Convert.ToBase64String(src);
        }

        // As Client
        private void doHandshake()
        {
            var res = sendOpeningHandshake();

            string msg;
            if (!isValidResponse(res, out msg))
            {
                onError(msg);
                close(CloseStatusCode.HANDSHAKE_FAILURE, msg);
                return;
            }

            onOpen();
        }

        private bool isValidCloseStatusCode(ushort code, out string message)
        {
            if (code < 1000)
            {
                message = "Close status codes in the range 0-999 are not used: " + code;
                return false;
            }

            if (code > 4999)
            {
                message = "Out of reserved close status code range: " + code;
                return false;
            }

            message = String.Empty;
            return true;
        }

        private bool isValidFrame(WsFrame frame)
        {
            if (frame == null)
            {
                var msg = "The WebSocket frame can not be read from the network stream.";
                close(CloseStatusCode.ABNORMAL, msg);

                return false;
            }

            return true;
        }

        // As Server
        private bool isValidRequest(RequestHandshake request, out string message)
        {
            if (!request.IsWebSocketRequest)
            {
                message = "Invalid WebSocket request.";
                return false;
            }

            if (_uri.IsAbsoluteUri && !isValidRequestHost(request.Headers["Host"], out message))
                return false;

            if (!request.HeaderExists("Sec-WebSocket-Version", _version))
            {
                message = "Unsupported Sec-WebSocket-Version.";
                return false;
            }

            _base64key = request.Headers["Sec-WebSocket-Key"];

            if (request.HeaderExists("Sec-WebSocket-Protocol"))
                _protocols = request.Headers["Sec-WebSocket-Protocol"];

            if (request.HeaderExists("Sec-WebSocket-Extensions"))
                _extensions = request.Headers["Sec-WebSocket-Extensions"];

            _queryString = request.QueryString;

            message = String.Empty;
            return true;
        }

        // As Server
        private bool isValidRequestHost(string value, out string message)
        {
            var host = _uri.DnsSafeHost;
            var type = Uri.CheckHostName(host);
            var address = _endPoint.Address;
            var port = _endPoint.Port;

            var expectedHost1 = host;
            var expectedHost2 = type == UriHostNameType.Dns
                              ? address.ToString()
                              : System.Net.Dns.GetHostEntry(address).HostName;

            if (port != 80)
            {
                expectedHost1 += ":" + port;
                expectedHost2 += ":" + port;
            }

            if (Ext.NotEqual(expectedHost1, value, false) &&
               Ext.NotEqual(expectedHost2, value, false))
            {
                message = "Invalid Host.";
                return false;
            }

            message = String.Empty;
            return true;
        }

        // As Client
        private bool isValidResponse(ResponseHandshake response, out string message)
        {
            if (!response.IsWebSocketResponse)
            {
                message = "Invalid WebSocket response.";
                return false;
            }

            if (!response.HeaderExists("Sec-WebSocket-Accept", createResponseKey()))
            {
                message = "Invalid Sec-WebSocket-Accept.";
                return false;
            }

            if (response.HeaderExists("Sec-WebSocket-Version") &&
                !response.HeaderExists("Sec-WebSocket-Version", _version))
            {
                message = "Unsupported Sec-WebSocket-Version.";
                return false;
            }

            if (response.HeaderExists("Sec-WebSocket-Protocol"))
                _protocol = response.Headers["Sec-WebSocket-Protocol"];

            if (response.HeaderExists("Sec-WebSocket-Extensions"))
                _extensions = response.Headers["Sec-WebSocket-Extensions"];

            message = String.Empty;
            return true;
        }

        private void onClose(CloseEventArgs eventArgs)
        {
            if (!Thread.CurrentThread.IsBackground)
                if (_exitMessageLoop != null)
                    _exitMessageLoop.WaitOne(5 * 1000, false);

            if (closeConnection())
                eventArgs.WasClean = true;

            Ext.Emit(OnClose, this, eventArgs);
        }

        private void onError(string message)
        {
#if DEBUG
            var callerFrame = new StackFrame(1);
            var caller = callerFrame.GetMethod();
            Console.WriteLine("WS: Error@{0}: {1}", caller.Name, message);
#endif
            Ext.Emit(OnError, this, new ErrorEventArgs(message));
        }

        private void onMessage(MessageEventArgs eventArgs)
        {
            if (eventArgs != null)
                Ext.Emit(OnMessage, this, eventArgs);
        }

        private void onOpen()
        {
            _readyState = WsState.OPEN;
            startMessageLoop();
            Ext.Emit(OnOpen, this, EventArgs.Empty); 

        }

        private bool ping(string message, int millisecondsTimeout)
        {
            var buffer = Encoding.UTF8.GetBytes(message);
            if (buffer.Length > 125)
            {
                var msg = "A Ping frame must have a payload length of 125 bytes or less.";
                onError(msg);
                return false;
            }

            if (!send(Fin.FINAL, Opcode.PING, buffer))
                return false;

            return _receivePong.WaitOne(millisecondsTimeout, false);
        }

        private void pong(PayloadData data)
        {
            var frame = createFrame(Fin.FINAL, Opcode.PONG, data);
            send(frame);
        }

        private void pong(string data)
        {
            var payloadData = new PayloadData(data);
            pong(payloadData);
        }

        private WsFrame readFrame()
        {
            var frame = _wsStream.ReadFrame();
            return isValidFrame(frame) ? frame : null;
        }

        private string[] readHandshake()
        {
            return _wsStream.ReadHandshake();
        }

        private MessageEventArgs receive(WsFrame frame)
        {
            if (!isValidFrame(frame))
                return null;

            if ((frame.Fin == Fin.FINAL && frame.Opcode == Opcode.CONT) ||
                (frame.Fin == Fin.MORE && frame.Opcode == Opcode.CONT))
                return null;

            if (frame.Fin == Fin.MORE)
            {// MORE
                var merged = receiveFragmented(frame);
                return merged != null
                       ? new MessageEventArgs(frame.Opcode, new PayloadData(merged))
                       : null;
            }

            if (frame.Opcode == Opcode.CLOSE)
            {// FINAL & CLOSE
#if DEBUG
                Console.WriteLine("WS: Info@receive: Starts closing handshake.");
#endif
                close(frame.PayloadData);
                return null;
            }

            if (frame.Opcode == Opcode.PING)
            {// FINAL & PING
#if DEBUG
                Console.WriteLine("WS: Info@receive: Returns Pong.");
#endif
                pong(frame.PayloadData);
                return null;
            }

            if (frame.Opcode == Opcode.PONG)
            {// FINAL & PONG
#if DEBUG
                Console.WriteLine("WS: Info@receive: Receives Pong.");
#endif
                _receivePong.Set();
                return null;
            }

            // FINAL & (TEXT | BINARY)
            return new MessageEventArgs(frame.Opcode, frame.PayloadData);
        }

        private byte[] receiveFragmented(WsFrame firstFrame)
        {
            var buffer = new List<byte>(firstFrame.PayloadData.ToBytes());

            while (true)
            {
                var frame = readFrame();
                if (frame == null)
                    return null;

                if (frame.Fin == Fin.MORE)
                {
                    if (frame.Opcode == Opcode.CONT)
                    {// MORE & CONT
                        buffer.AddRange(frame.PayloadData.ToBytes());
                        continue;
                    }

#if DEBUG
                    Console.WriteLine("WS: Info@receiveFragmented: Starts closing handshake.");
#endif
                    close(CloseStatusCode.INCORRECT_DATA, String.Empty);
                    return null;
                }

                if (frame.Opcode == Opcode.CONT)
                {// FINAL & CONT
                    buffer.AddRange(frame.PayloadData.ToBytes());
                    break;
                }

                if (frame.Opcode == Opcode.CLOSE)
                {// FINAL & CLOSE
#if DEBUG
                    Console.WriteLine("WS: Info@receiveFragmented: Starts closing handshake.");
#endif
                    close(frame.PayloadData);
                    return null;
                }

                if (frame.Opcode == Opcode.PING)
                {// FINAL & PING
#if DEBUG
                    Console.WriteLine("WS: Info@receiveFragmented: Returns Pong.");
#endif
                    pong(frame.PayloadData);
                    continue;
                }

                if (frame.Opcode == Opcode.PONG)
                {// FINAL & PONG
#if DEBUG
                    Console.WriteLine("WS: Info@receiveFragmented: Receives Pong.");
#endif
                    _receivePong.Set();
                    continue;
                }

                // FINAL & (TEXT | BINARY)
#if DEBUG
                Console.WriteLine("WS: Info@receiveFragmented: Starts closing handshake.");
#endif
                close(CloseStatusCode.INCORRECT_DATA, String.Empty);
                return null;
            }

            return buffer.ToArray();
        }

        // As Server
        private RequestHandshake receiveOpeningHandshake()
        {
            var req = RequestHandshake.Parse(_context);
#if DEBUG
            Console.WriteLine("WS: Info@receiveOpeningHandshake: Opening handshake from client:\n");
            Console.WriteLine(req.ToString());
#endif
            return req;
        }

        // As Client
        private ResponseHandshake receiveResponseHandshake()
        {
            var res = ResponseHandshake.Parse(readHandshake());
#if DEBUG
            Console.WriteLine("WS: Info@receiveResponseHandshake: Response handshake from server:\n");
            Console.WriteLine(res.ToString());
#endif
            return res;
        }

        private bool send(WsFrame frame)
        {
            if (_readyState == WsState.CONNECTING ||
                _readyState == WsState.CLOSED)
            {
                var msg = "The WebSocket connection isn't established or has been closed.";
                onError(msg);
                return false;
            }

            try
            {
                if (_unTransmittedBuffer.Count == 0)
                {
                    if (_wsStream != null)
                    {
                        _wsStream.WriteFrame(frame);
                        return true;
                    }
                }

                if (_unTransmittedBuffer.Count > 0)
                {
                    _unTransmittedBuffer.Add(frame);
                    var msg = "Current data can not be sent because there is untransmitted data.";
                    onError(msg);
                }

                return false;
            }
            catch (Exception ex)
            {
                _unTransmittedBuffer.Add(frame);
                onError(ex.Message);
                return false;
            }
        }

        private void send(Opcode opcode, byte[] data)
        {
            using (MemoryStream ms = new MemoryStream(data))
            {
                send(opcode, ms);
            }
        }

        private void send(Opcode opcode, Stream stream)
        {
            lock (_forSend)
            {
                try
                {
                    if (_readyState != WsState.OPEN)
                    {
                        var msg = "The WebSocket connection isn't established or has been closed.";
                        onError(msg);
                        return;
                    }

                    var length = stream.Length;
                    if (length <= _fragmentLen)
                        send(Fin.FINAL, opcode, Ext.ReadBytes(stream, (int)length));
                    else
                        sendFragmented(opcode, stream);
                }
                catch (Exception ex)
                {
                    onError(ex.Message);
                }
            }
        }

        private bool send(Fin fin, Opcode opcode, byte[] data)
        {
            var frame = createFrame(fin, opcode, new PayloadData(data));
            return send(frame);
        }

        private void sendAsync(Opcode opcode, byte[] data, Action completed)
        {
            sendAsync(opcode, new MemoryStream(data), completed);
        }

        private void sendAsync(Opcode opcode, Stream stream, Action completed)
        {
            Action<Opcode, Stream> action = send;

            AsyncCallback callback = (ar) =>
            {
                try
                {
                    action.EndInvoke(ar);
                    if (completed != null)
                        completed();
                }
                catch (Exception ex)
                {
                    onError(ex.Message);
                }
                finally
                {
                    stream.Close();
                }
            };

            action.BeginInvoke(opcode, stream, callback, null);
        }

        private long sendFragmented(Opcode opcode, Stream stream)
        {
            var length = stream.Length;
            var quo = length / _fragmentLen;
            var rem = length % _fragmentLen;
            var count = rem == 0 ? quo - 2 : quo - 1;

            // First
            var buffer = new byte[_fragmentLen];
            long readLen = stream.Read(buffer, 0, _fragmentLen);
            send(Fin.MORE, opcode, buffer);

            // Mid
            Ext.Times(count, () =>
            {
                readLen += stream.Read(buffer, 0, _fragmentLen);
                send(Fin.MORE, Opcode.CONT, buffer);
            });

            // Final
            if (rem != 0)
                buffer = new byte[rem];
            readLen += stream.Read(buffer, 0, buffer.Length);
            send(Fin.FINAL, Opcode.CONT, buffer);

            return readLen;
        }

        // As Client
        private ResponseHandshake sendOpeningHandshake()
        {
            var req = createOpeningHandshake();
            sendOpeningHandshake(req);

            return receiveResponseHandshake();
        }

        // As Client
        private void sendOpeningHandshake(RequestHandshake request)
        {
#if DEBUG
            Console.WriteLine("WS: Info@sendOpeningHandshake: Opening handshake from client:\n");
            Console.WriteLine(request.ToString());
#endif
            writeHandshake(request);
        }

        // As Server
        private void sendResponseHandshake()
        {
            var res = createResponseHandshake();
            sendResponseHandshake(res);
        }

        // As Server
        private void sendResponseHandshake(HttpStatusCode code)
        {
            var res = createResponseHandshake(code);
            sendResponseHandshake(res);
        }
        /// <summary>
        /// @Author:      HTL
        /// @Email:       Huangyuan413026@163.com
        /// @DateTime:    2015-06-03 19:54:49
        /// @Description: 获取当前堆栈的上级调用方法列表,直到最终调用者,只会返回调用的各方法,而不会返回具体的出错行数，可参考：微软真是个十足的混蛋啊！让我们跟踪Exception到行把！（不明真相群众请入） 
        /// </summary>
        /// <returns></returns>
        static string GetStackTraceModelName()
        {
            //当前堆栈信息
            System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace();
            System.Diagnostics.StackFrame[] sfs = st.GetFrames();
            //过虑的方法名称,以下方法将不会出现在返回的方法调用列表中
            string _filterdName = "ResponseWrite,ResponseWriteError,";
            string _fullName = string.Empty, _methodName = string.Empty;
            for (int i = 1; i < sfs.Length; ++i)
            {
                //非用户代码,系统方法及后面的都是系统调用，不获取用户代码调用结束
                if (System.Diagnostics.StackFrame.OFFSET_UNKNOWN == sfs[i].GetILOffset()) break;
                _methodName = sfs[i].GetMethod().Name;//方法名称
                //sfs[i].GetFileLineNumber();//没有PDB文件的情况下将始终返回0
                if (_filterdName.Contains(_methodName)) continue;
                _fullName = _methodName + "()->" + _fullName;
            }
            st = null;
            sfs = null;
            _filterdName = _methodName = null;
            return _fullName.TrimEnd('-','>');
        }
        // As Server
        private void sendResponseHandshake(ResponseHandshake response)
        {
#if DEBUG
            Console.WriteLine("WS: Info@sendResponseHandshake: Response handshake from server:\n");
            Console.WriteLine(response.ToString());
#endif
            writeHandshake(response);
        }

        private int count = 0;
        // 0 可读,  1 读取中 , 2 断开
        private volatile int lockI = 0;
        private Thread refreshThread;
        private void startMessageLoop()
        {
            
            _exitMessageLoop = new AutoResetEvent(false);
            _receivePong = new AutoResetEvent(false);

            Action<WsFrame> completed = frame =>
            { 
                try
                {
                    onMessage(receive(frame));
                    if (_readyState == WsState.OPEN)
                    {
                        lockI = 0;
                        count++;
                        // Console.WriteLine("stack in " + count + " " + GetStackTraceModelName());
                    }
                    else
                    {
                        lockI = 2;
                        _exitMessageLoop.Set();
                        // Console.WriteLine("stack over depth " + count); 
                        count = 0;
                    }
                }
                catch (WsReceivedTooBigMessageException ex)
                {
                    close(CloseStatusCode.TOO_BIG, ex.Message);
                    count = 0;
                    lockI = 2;
                }
                catch (Exception ex)
                {
                    count = 0;
                    //HACK:close with 1006 when onMessage exception?
                    close(CloseStatusCode.ABNORMAL
                        , string.Format("An exception has occured: {0}", ex.Message));
                    lockI = 2;
                }
            }; 
            // 0 可读,  1 读取中 , 2 断开
            refreshThread = new Thread(o =>
            {
                while (lockI != 2 && _readyState == WsState.OPEN)
                {
                    if (lockI == 1)
                    {
                        Thread.Sleep(new TimeSpan(0, 0, 0, 0, 1));
                    }
                    else if (lockI == 0)
                    {
                        lockI = 1;
                        _wsStream.ReadFrameAsync(completed);
                    }
                }  
            });
            refreshThread.IsBackground = true;
            refreshThread.Name = "StartMessageLoop thread";
            refreshThread.Start();
        }

        private bool tryCreateUri(string uriString, out Uri result, out string message)
        {
            return Ext.TryCreateWebSocketUri(uriString, out result, out message);
        }

        private void writeHandshake(Handshake handshake)
        {
            _wsStream.WriteHandshake(handshake);
        }

        #endregion

        #region Internal Method

        // As Server
        internal void Close(HttpStatusCode code)
        {
            close(code);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Closes the connection and releases all associated resources after sends a Close control frame.
        /// </summary>
        public void Close()
        {
            var data = new PayloadData(new byte[] { });
            close(data);
        }

        /// <summary>
        /// Closes the connection and releases all associated resources after sends a Close control frame.
        /// </summary>
        /// <param name="code">
        /// A <see cref="WebSocketSharp.Frame.CloseStatusCode"/> that contains a status code indicating a reason for closure.
        /// </param>
        public void Close(CloseStatusCode code)
        {
            Close(code, String.Empty);
        }

        /// <summary>
        /// Closes the connection and releases all associated resources after sends a Close control frame.
        /// </summary>
        /// <param name="code">
        /// A <see cref="ushort"/> that contains a status code indicating a reason for closure.
        /// </param>
        public void Close(ushort code)
        {
            Close(code, String.Empty);
        }

        /// <summary>
        /// Closes the connection and releases all associated resources after sends a Close control frame.
        /// </summary>
        /// <param name="code">
        /// A <see cref="WebSocketSharp.Frame.CloseStatusCode"/> that contains a status code indicating a reason for closure.
        /// </param>
        /// <param name="reason">
        /// A <see cref="string"/> that contains a reason for closure.
        /// </param>
        public void Close(CloseStatusCode code, string reason)
        {
            Close((ushort)code, reason);
        }

        /// <summary>
        /// Closes the connection and releases all associated resources after sends a Close control frame.
        /// </summary>
        /// <param name="code">
        /// A <see cref="ushort"/> that contains a status code indicating a reason for closure.
        /// </param>
        /// <param name="reason">
        /// A <see cref="string"/> that contains a reason for closure.
        /// </param>
        public void Close(ushort code, string reason)
        {
            string msg;
            if (!isValidCloseStatusCode(code, out msg))
            {
                onError(msg);
                return;
            }

            close(code, reason);
        }

        /// <summary>
        /// Establishes a connection.
        /// </summary>
        public void Connect()
        {
            if (_readyState == WsState.OPEN)
            {
                Console.WriteLine("WS: Info@Connect: The WebSocket connection has been established already.");
                return;
            }

            try
            {
                // As client
                if (_isClient)
                {
                    createClientStream();
                    doHandshake();
                    return;
                }

                // As server
                acceptHandshake();
            }
            catch (Exception ex)
            {
                onError(ex.Message);
                close(CloseStatusCode.HANDSHAKE_FAILURE, "An exception has occured.");
            }
        }

        /// <summary>
        /// Closes the connection and releases all associated resources after sends a Close control frame.
        /// </summary>
        /// <remarks>
        /// Call <see cref="Dispose"/> when you are finished using the <see cref="WebSocketSharp.WebSocket"/>. The
        /// <see cref="Dispose"/> method leaves the <see cref="WebSocketSharp.WebSocket"/> in an unusable state. After
        /// calling <see cref="Dispose"/>, you must release all references to the <see cref="WebSocketSharp.WebSocket"/> so
        /// the garbage collector can reclaim the memory that the <see cref="WebSocketSharp.WebSocket"/> was occupying.
        /// </remarks>
        public void Dispose()
        {
            Close(CloseStatusCode.AWAY);
        }

        /// <summary>
        /// Sends a Ping frame using the connection.
        /// </summary>
        /// <returns>
        /// <c>true</c> if the WebSocket receives a Pong frame in a time; otherwise, <c>false</c>.
        /// </returns>
        public bool Ping()
        {
            return Ping(String.Empty);
        }

        /// <summary>
        /// Sends a Ping frame with a message using the connection.
        /// </summary>
        /// <param name="message">
        /// A <see cref="string"/> that contains the message to be sent.
        /// </param>
        /// <returns>
        /// <c>true</c> if the WebSocket receives a Pong frame in a time; otherwise, <c>false</c>.
        /// </returns>
        public bool Ping(string message)
        {
            if (message == null)
                message = String.Empty;

            return _isClient
                   ? ping(message, 5 * 1000)
                   : ping(message, 1 * 1000);
        }

        /// <summary>
        /// Sends a text data using the connection.
        /// </summary>
        /// <param name="data">
        /// A <see cref="string"/> that contains the text data to be sent.
        /// </param>
        public void Send(string data)
        {
            if (data == null)
            {
                onError("'data' must not be null.");
                return;
            }

            var buffer = Encoding.UTF8.GetBytes(data);
            send(Opcode.TEXT, buffer);
        }

        /// <summary>
        /// Sends a binary data using the connection.
        /// </summary>
        /// <param name="data">
        /// An array of <see cref="byte"/> that contains the binary data to be sent.
        /// </param>
        public void Send(byte[] data)
        {
            if (data == null)
            {
                onError("'data' must not be null.");
                return;
            }

            send(Opcode.BINARY, data);
        }

        /// <summary>
        /// Sends a binary data using the connection.
        /// </summary>
        /// <param name="file">
        /// A <see cref="FileInfo"/> that contains the binary data to be sent.
        /// </param>
        public void Send(FileInfo file)
        {
            if (file == null)
            {
                onError("'file' must not be null.");
                return;
            }

            using (FileStream fs = file.OpenRead())
            {
                send(Opcode.BINARY, fs);
            }
        }

        /// <summary>
        /// Sends a text data asynchronously using the connection.
        /// </summary>
        /// <param name="data">
        /// A <see cref="string"/> that contains the text data to be sent.
        /// </param>
        /// <param name="completed">
        /// An <see cref="Action"/> delegate that contains the method(s) that is called when an asynchronous operation completes.
        /// </param>
        public void SendAsync(string data, Action completed)
        {
            if (data == null)
            {
                onError("'data' must not be null.");
                return;
            }

            var buffer = Encoding.UTF8.GetBytes(data);
            sendAsync(Opcode.TEXT, buffer, completed);
        }

        /// <summary>
        /// Sends a binary data asynchronously using the connection.
        /// </summary>
        /// <param name="data">
        /// An array of <see cref="byte"/> that contains the binary data to be sent.
        /// </param>
        /// <param name="completed">
        /// An <see cref="Action"/> delegate that contains the method(s) that is called when an asynchronous operation completes.
        /// </param>
        public void SendAsync(byte[] data, Action completed)
        {
            if (data == null)
            {
                onError("'data' must not be null.");
                return;
            }

            sendAsync(Opcode.BINARY, data, completed);
        }

        /// <summary>
        /// Sends a binary data asynchronously using the connection.
        /// </summary>
        /// <param name="file">
        /// A <see cref="FileInfo"/> that contains the binary data to be sent.
        /// </param>
        /// <param name="completed">
        /// An <see cref="Action"/> delegate that contains the method(s) that is called when an asynchronous operation completes.
        /// </param>
        public void SendAsync(FileInfo file, Action completed)
        {
            if (file == null)
            {
                onError("'file' must not be null.");
                return;
            }

            sendAsync(Opcode.BINARY, file.OpenRead(), completed);
        }

        #endregion
    }
}