#region MIT License
/**
 * WebSocketServerBase.cs
 *
 * The MIT License
 *
 * Copyright (c) 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.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace WebSocketSharp.Server
{
    public abstract class WebSocketServerBase
    {
        #region Fields

        private Thread _acceptClientThread;
        private IPAddress _address;
        private bool _isSecure;
        private bool _isSelfHost;
        private int _port;
        private TcpListener _tcpListener;
        private Uri _uri;

        #endregion

        #region Constructors

        protected WebSocketServerBase()
        {
            _isSelfHost = false;
        }

        protected WebSocketServerBase(string url)
        {
            if (url == null)
                throw new ArgumentNullException("url");

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

            init(uri);
        }

        protected WebSocketServerBase(IPAddress address, int port, string absPath, bool secure)
        {
            if (address == null)
                throw new ArgumentNullException("address");

            if (absPath == null)
                throw new ArgumentNullException("absPath");

            string msg;
            if (!Ext.IsValidAbsolutePath(absPath, out msg))
                throw new ArgumentException(msg, "absPath");

            if ((port == 80 && secure) ||
                (port == 443 && !secure))
            {
                msg = String.Format(
                  "Invalid pair of 'port' and 'secure': {0}, {1}", port, secure);
                throw new ArgumentException(msg);
            }

            _address = address;
            _port = port > 0
                      ? port
                      : secure ? 443 : 80;
            _uri = Ext.ToUri(absPath);
            _isSecure = secure;

            init();
        }

        #endregion

        #region Protected Property

        protected Uri BaseUri
        {
            get
            {
                return _uri;
            }

            set
            {
                _uri = value;
            }
        }

        #endregion

        #region Public Properties

        public IPAddress Address
        {
            get
            {
                return _address;
            }
        }

        public bool IsSecure
        {
            get
            {
                return _isSecure;
            }
        }

        public bool IsSelfHost
        {
            get
            {
                return _isSelfHost;
            }
        }

        public int Port
        {
            get
            {
                return _port;
            }
        }

        #endregion

        #region Events

        public event EventHandler<ErrorEventArgs> OnError;

        #endregion

        #region Private Methods

        private void acceptClient()
        {
            while (true)
            {
                try
                {
                    var client = _tcpListener.AcceptTcpClient();
                    acceptSocketAsync(client);
                }
                catch (SocketException)
                {
                    // TcpListener has been stopped.
                    break;
                }
                catch (Exception ex)
                {
                    onError(ex.Message);
                    break;
                }
            }
        }

        private void acceptSocketAsync(TcpClient client)
        {
            WaitCallback acceptSocketCb = (state) =>
            {
                try
                {
                    AcceptWebSocket(client);
                }
                catch (Exception ex)
                {
                    onError(ex.Message);
                }
            };

            ThreadPool.QueueUserWorkItem(acceptSocketCb);
        }

        private void init()
        {
            _tcpListener = new TcpListener(_address, _port);
            _isSelfHost = true;
        }

        private void init(Uri uri)
        {
            var scheme = uri.Scheme;
            var host = uri.DnsSafeHost;
            var port = uri.Port;
            var addrs = Dns.GetHostAddresses(host);

            _uri = uri;
            _address = addrs[0];
            _isSecure = scheme == "wss" ? true : false;
            _port = port > 0
                        ? port
                        : _isSecure ? 443 : 80;

            init();
        }

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

        private void startAcceptClientThread()
        {
            _acceptClientThread = new Thread(new ThreadStart(acceptClient));
            _acceptClientThread.IsBackground = true;
            _acceptClientThread.Start();
        }

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

            if (!Ext.IsNullOrEmpty(result.Query))
            {
                result = null;
                message = "Must not contain the query component: " + uriString;
                return false;
            }

            return true;
        }

        #endregion

        #region Protected Methods

        protected abstract void AcceptWebSocket(TcpClient client);

        protected virtual void Error(string message)
        {
            onError(message);
        }

        #endregion

        #region Public Methods

        public virtual void Start()
        {
            if (!_isSelfHost)
                return;

            _tcpListener.Start();
            startAcceptClientThread();
        }

        public virtual void Stop()
        {
            if (!_isSelfHost)
                return;

            _tcpListener.Stop();
            _acceptClientThread.Join(5 * 1000);
        }

        #endregion
    }
}