//------------------------------------------------------------------------------
// <copyright file="TCPListener.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------

namespace System.Net.Sockets {
    using System;
    using System.Net;
    using System.Security.Permissions;
    using System.Threading.Tasks;

    /// <devdoc>
    /// <para>The <see cref='System.Net.Sockets.TcpListener'/> class provide TCP services at a higher level of abstraction than the <see cref='System.Net.Sockets.Socket'/>
    /// class. <see cref='System.Net.Sockets.TcpListener'/> is used to create a host process that
    /// listens for connections from TCP clients.</para>
    /// </devdoc>
    public class TcpListener {

        IPEndPoint m_ServerSocketEP;
        Socket m_ServerSocket;
        bool m_Active;
        bool m_ExclusiveAddressUse;



        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the TcpListener class with the specified local
        ///       end point.
        ///    </para>
        /// </devdoc>
        public TcpListener(IPEndPoint localEP) {
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "TcpListener", localEP);
            if (localEP == null) {
                throw new ArgumentNullException("localEP");
            }
            m_ServerSocketEP = localEP;
            m_ServerSocket   = new Socket(m_ServerSocketEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            if(Logging.On)Logging.Exit(Logging.Sockets, this, "TcpListener", null);
        }

        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the TcpListener class that listens to the
        ///       specified IP address and port.
        ///    </para>
        /// </devdoc>
        public TcpListener(IPAddress localaddr, int port) {
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "TcpListener", localaddr);
            if (localaddr == null) {
                throw new ArgumentNullException("localaddr");
            }
            if (!ValidationHelper.ValidateTcpPort(port)) {
                throw new ArgumentOutOfRangeException("port");
            }
            m_ServerSocketEP = new IPEndPoint(localaddr, port);
            m_ServerSocket   = new Socket(m_ServerSocketEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            if(Logging.On)Logging.Exit(Logging.Sockets, this, "TcpListener", null);
        }

        // implementation picks an address for client
        /// <devdoc>
        ///    <para>
        ///       Initiailizes a new instance of the TcpListener class
        ///       that listens on the specified
        ///       port.
        ///    </para>
        /// </devdoc>
        /// 

        [Obsolete("This method has been deprecated. Please use TcpListener(IPAddress localaddr, int port) instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public TcpListener(int port){

            if (!ValidationHelper.ValidateTcpPort(port)) 
                throw new ArgumentOutOfRangeException("port");
            
            m_ServerSocketEP = new IPEndPoint(IPAddress.Any, port);
            m_ServerSocket = new Socket(m_ServerSocketEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        }

        // This creates a TcpListener that listens on both IPv4 and IPv6 on the given port.
        public static TcpListener Create(int port) {
            if (Logging.On) Logging.Enter(Logging.Sockets, "TcpListener.Create", "Port: " + port);

            if (!ValidationHelper.ValidateTcpPort(port)) {
                throw new ArgumentOutOfRangeException("port");
            }

            TcpListener listener = new TcpListener(IPAddress.IPv6Any, port);
            listener.Server.DualMode = true;

            if (Logging.On) Logging.Exit(Logging.Sockets, "TcpListener.Create", "Port: " + port);
            return listener;
        }

        /// <devdoc>
        ///    <para>
        ///       Used by the class to provide the underlying network socket.
        ///    </para>
        /// </devdoc>
        public Socket Server {
            get {
                return m_ServerSocket;
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Used
        ///       by the class to indicate that the listener's socket has been bound to a port
        ///       and started listening.
        ///    </para>
        /// </devdoc>
        protected bool Active {
            get {
                return m_Active;
            }
        }
                
        /// <devdoc>
        ///    <para>
        ///        Gets the m_Active EndPoint for the local listener socket.
        ///    </para>
        /// </devdoc>
        public EndPoint LocalEndpoint {
            get {
                return m_Active ? m_ServerSocket.LocalEndPoint : m_ServerSocketEP;
            }
        }

        public bool ExclusiveAddressUse {
            get {
                return m_ServerSocket.ExclusiveAddressUse;
            } 
            set{
                if (m_Active) {
                    throw new InvalidOperationException(SR.GetString(SR.net_tcplistener_mustbestopped));
                }
                m_ServerSocket.ExclusiveAddressUse = value; 
                m_ExclusiveAddressUse = value;
            }
        }

        public void AllowNatTraversal(bool allowed) {
            if (m_Active) {
                throw new InvalidOperationException(SR.GetString(SR.net_tcplistener_mustbestopped));
            }

            if (allowed) {
                m_ServerSocket.SetIPProtectionLevel(IPProtectionLevel.Unrestricted);
            }
            else {
                m_ServerSocket.SetIPProtectionLevel(IPProtectionLevel.EdgeRestricted);
            }
        }

        // Start/stop the listener
        /// <devdoc>
        ///    <para>
        ///       Starts listening to network requests.
        ///    </para>
        /// </devdoc>
        public void Start() {
            Start((int)SocketOptionName.MaxConnections);
        }
        
        public void Start(int backlog) {
            if (backlog > (int)SocketOptionName.MaxConnections || backlog < 0) {
                throw new ArgumentOutOfRangeException("backlog");
            }

            if(Logging.On)Logging.Enter(Logging.Sockets, this, "Start", null);
            GlobalLog.Print("TCPListener::Start()");            

            if (m_ServerSocket == null) 
                throw new InvalidOperationException(SR.GetString(SR.net_InvalidSocketHandle));

            //already listening
            if (m_Active) {
                if(Logging.On)Logging.Exit(Logging.Sockets, this, "Start", null);
                return;
            }

            m_ServerSocket.Bind(m_ServerSocketEP);
            try {
                m_ServerSocket.Listen(backlog);
            }
            // When there is an exception unwind previous actions (bind etc) 
            catch (SocketException) {
                Stop();
                throw;
            }
            m_Active = true;
            if(Logging.On)Logging.Exit(Logging.Sockets, this, "Start", null);
        }


        /// <devdoc>
        ///    <para>
        ///       Closes the network connection.
        ///    </para>
        /// </devdoc>
        public void Stop() {
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "Stop", null);
            GlobalLog.Print("TCPListener::Stop()");

            if (m_ServerSocket != null) {
                m_ServerSocket.Close();
                m_ServerSocket = null;
            }
            m_Active = false;
            m_ServerSocket = new Socket(m_ServerSocketEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            
            if (m_ExclusiveAddressUse) {
                m_ServerSocket.ExclusiveAddressUse = true;
            }

            if(Logging.On)Logging.Exit(Logging.Sockets, this, "Stop", null);
        }

        // Determine if there are pending connections
        /// <devdoc>
        ///    <para>
        ///       Determine if there are pending connection requests.
        ///    </para>
        /// </devdoc>
        public bool Pending() {
            if (!m_Active) 
                throw new InvalidOperationException(SR.GetString(SR.net_stopped));
            return m_ServerSocket.Poll(0, SelectMode.SelectRead);
        }

        // Accept the first pending connection
        /// <devdoc>
        ///    <para>
        ///       Accepts a pending connection request.
        ///    </para>
        /// </devdoc>
        public Socket AcceptSocket() {
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "AcceptSocket", null);
            if (!m_Active) 
                throw new InvalidOperationException(SR.GetString(SR.net_stopped));
            Socket socket = m_ServerSocket.Accept();
            if(Logging.On)Logging.Exit(Logging.Sockets, this, "AcceptSocket", socket);
            return socket;
        }

        // UEUE
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public TcpClient AcceptTcpClient() {
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "AcceptTcpClient", null);
            if (!m_Active) 
                throw new InvalidOperationException(SR.GetString(SR.net_stopped));
            
            Socket acceptedSocket = m_ServerSocket.Accept();
            TcpClient returnValue = new TcpClient(acceptedSocket);
            if(Logging.On)Logging.Exit(Logging.Sockets, this, "AcceptTcpClient", returnValue);
            return returnValue;
        }



        //methods
        
        [HostProtection(ExternalThreading=true)]
        public IAsyncResult BeginAcceptSocket(AsyncCallback callback, object state)
        {
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "BeginAcceptSocket", null);
            if (!m_Active) 
                throw new InvalidOperationException(SR.GetString(SR.net_stopped));

            IAsyncResult result = m_ServerSocket.BeginAccept(callback,state);
            if(Logging.On)Logging.Exit(Logging.Sockets, this, "BeginAcceptSocket", null);
            return result;
        }

        public Socket EndAcceptSocket(IAsyncResult asyncResult){
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "EndAcceptSocket", null);
            
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            LazyAsyncResult lazyResult = asyncResult as LazyAsyncResult;
            Socket asyncSocket = lazyResult == null ? null : lazyResult.AsyncObject as Socket;
            if (asyncSocket == null)
            {
                throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
            }

            // This will throw ObjectDisposedException if Stop() has been called.
            Socket socket = asyncSocket.EndAccept(asyncResult);

            if(Logging.On)Logging.Exit(Logging.Sockets, this, "EndAcceptSocket", socket);
            return socket;
        }
        
        [HostProtection(ExternalThreading=true)]
        public IAsyncResult BeginAcceptTcpClient(AsyncCallback callback, object state)
        {
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "BeginAcceptTcpClient", null);
            if (!m_Active) 
                throw new InvalidOperationException(SR.GetString(SR.net_stopped));
            IAsyncResult result = m_ServerSocket.BeginAccept(callback,state);
            if(Logging.On)Logging.Exit(Logging.Sockets, this, "BeginAcceptTcpClient", null);
            return result;
        }

        public TcpClient EndAcceptTcpClient(IAsyncResult asyncResult){
            if(Logging.On)Logging.Enter(Logging.Sockets, this, "EndAcceptTcpClient", null);
            
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            LazyAsyncResult lazyResult = asyncResult as LazyAsyncResult;
            Socket asyncSocket = lazyResult == null ? null : lazyResult.AsyncObject as Socket;
            if (asyncSocket == null)
            {
                throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
            }

            // This will throw ObjectDisposedException if Stop() has been called.
            Socket socket = asyncSocket.EndAccept(asyncResult);

            if(Logging.On)Logging.Exit(Logging.Sockets, this, "EndAcceptTcpClient", socket);
            return new TcpClient(socket);
        }

        //************* Task-based async public methods *************************
        [HostProtection(ExternalThreading = true)]
        public Task<Socket> AcceptSocketAsync()
        {
            return Task<Socket>.Factory.FromAsync(BeginAcceptSocket, EndAcceptSocket, null);
        }

        [HostProtection(ExternalThreading = true)]
        public Task<TcpClient> AcceptTcpClientAsync()
        {
            return Task<TcpClient>.Factory.FromAsync(BeginAcceptTcpClient, EndAcceptTcpClient, null);
        }


    }; // class TcpListener


} // namespace System.Net.Sockets
