using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.Remoting.Messaging;
using System.Threading;

using LumiSoft.Net.IO;
using LumiSoft.Net.Log;

namespace LumiSoft.Net.TCP
{    
    /// <summary>
    /// This class implements generic TCP client.
    /// </summary>
    public class TCP_Client : TCP_Session
    {
        private bool                                m_IsDisposed           = false;
        private bool                                m_IsConnected          = false;
        private string                              m_ID                   = "";
        private DateTime                            m_ConnectTime;
        private IPEndPoint                          m_pLocalEP             = null;
        private IPEndPoint                          m_pRemoteEP            = null;
        private bool                                m_IsSecure             = false;
        private SmartStream                         m_pTcpStream           = null;
        private Logger                              m_pLogger              = null;
        private RemoteCertificateValidationCallback m_pCertificateCallback = null;
        private int                                 m_Timeout              = 61000;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public TCP_Client()
        {
        }

        #region method Dispose

        /// <summary>
        /// Cleans up any resources being used. This method is thread-safe.
        /// </summary>
        public override void Dispose()
        {
            lock(this){
                if(m_IsDisposed){
                    return;
                }
                try{
                    Disconnect();
                }
                catch{
                }
                m_IsDisposed = true;
            }
        }

        #endregion

                
        #region method Connect

        /// <summary>
        /// Connects to the specified host. If the hostname resolves to more than one IP address, 
        /// all IP addresses will be tried for connection, until one of them connects.
        /// </summary>
        /// <param name="host">Host name or IP address.</param>
        /// <param name="port">Port to connect.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        public void Connect(string host,int port)
        {
            Connect(host,port,false);
        }

        /// <summary>
        /// Connects to the specified host. If the hostname resolves to more than one IP address, 
        /// all IP addresses will be tried for connection, until one of them connects.
        /// </summary>
        /// <param name="host">Host name or IP address.</param>
        /// <param name="port">Port to connect.</param>
        /// <param name="ssl">Specifies if connects to SSL end point.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        public void Connect(string host,int port,bool ssl)
        {            
            if(m_IsDisposed){
                throw new ObjectDisposedException("TCP_Client");
            }
            if(m_IsConnected){
                throw new InvalidOperationException("TCP client is already connected.");
            }
            if(string.IsNullOrEmpty(host)){
                throw new ArgumentException("Argument 'host' value may not be null or empty.");
            }
            if(port < 1){
                throw new ArgumentException("Argument 'port' value must be >= 1.");
            }

            IPAddress[] ips = System.Net.Dns.GetHostAddresses(host);
            for(int i=0;i<ips.Length;i++){
                try{
                    Connect(null,new IPEndPoint(ips[i],port),ssl);
                    break;
                }
                catch(Exception x){
                    if(this.IsConnected){
                        throw x;
                    }
                    // Connect failed for specified IP address, if there are some more IPs left, try next, otherwise forward exception.
                    else if(i == (ips.Length - 1)){
                        throw x;
                    }
                }
            }
        }

        /// <summary>
        /// Connects to the specified remote end point.
        /// </summary>
        /// <param name="remoteEP">Remote IP end point where to connect.</param>
        /// <param name="ssl">Specifies if connects to SSL end point.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>remoteEP</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        public void Connect(IPEndPoint remoteEP,bool ssl)
        {
            Connect(null,remoteEP,ssl);
        }

        /// <summary>
        /// Connects to the specified remote end point.
        /// </summary>
        /// <param name="localEP">Local IP end point to use. Value null means that system will allocate it.</param>
        /// <param name="remoteEP">Remote IP end point to connect.</param>
        /// <param name="ssl">Specifies if connection switches to SSL affter connect.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>remoteEP</b> is null reference.</exception>
        public void Connect(IPEndPoint localEP,IPEndPoint remoteEP,bool ssl)
        {
            Connect(localEP,remoteEP,ssl,null);
        }

        /// <summary>
        /// Connects to the specified remote end point.
        /// </summary>
        /// <param name="localEP">Local IP end point to use. Value null means that system will allocate it.</param>
        /// <param name="remoteEP">Remote IP end point to connect.</param>
        /// <param name="ssl">Specifies if connection switches to SSL affter connect.</param>
        /// <param name="certCallback">SSL server certificate validation callback. Value null means any certificate is accepted.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>remoteEP</b> is null reference.</exception>
        public void Connect(IPEndPoint localEP,IPEndPoint remoteEP,bool ssl,RemoteCertificateValidationCallback certCallback)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(m_IsConnected){
                throw new InvalidOperationException("TCP client is already connected.");
            }
            if(remoteEP == null){
                throw new ArgumentNullException("remoteEP");
            }

            ManualResetEvent wait = new ManualResetEvent(false);
            using(ConnectAsyncOP op = new ConnectAsyncOP(localEP,remoteEP,ssl,certCallback)){
                op.CompletedAsync += delegate(object s1,EventArgs<ConnectAsyncOP> e1){
                    wait.Set();
                };
                if(!this.ConnectAsync(op)){
                    wait.Set();
                }
                wait.WaitOne();
                wait.Close();

                if(op.Error != null){
                    throw op.Error;
                }
            }
        }

        #endregion

        #region method ConnectAsync

        #region class ConnectAsyncOP

        /// <summary>
        /// This class represents <see cref="TCP_Client.ConnectAsync"/> asynchronous operation.
        /// </summary>
        public class ConnectAsyncOP : IDisposable,IAsyncOP
        {
            private object                              m_pLock         = new object();
            private AsyncOP_State                       m_State         = AsyncOP_State.WaitingForStart;
            private Exception                           m_pException    = null;
            private IPEndPoint                          m_pLocalEP      = null;
            private IPEndPoint                          m_pRemoteEP     = null;
            private bool                                m_SSL           = false;
            private RemoteCertificateValidationCallback m_pCertCallback = null;
            private TCP_Client                          m_pTcpClient    = null;
            private Socket                              m_pSocket       = null;
            private Stream                              m_pStream       = null;
            private bool                                m_RiseCompleted = false;

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="localEP">Local IP end point to use. Value null means that system will allocate it.</param>
            /// <param name="remoteEP">Remote IP end point to connect.</param>
            /// <param name="ssl">Specifies if connection switches to SSL affter connect.</param>
            /// <param name="certCallback">SSL server certificate validation callback. Value null means any certificate is accepted.</param>
            /// <exception cref="ArgumentNullException">Is raised when <b>remoteEP</b> is null reference.</exception>
            public ConnectAsyncOP(IPEndPoint localEP,IPEndPoint remoteEP,bool ssl,RemoteCertificateValidationCallback certCallback)
            {
                if(remoteEP == null){
                    throw new ArgumentNullException("localEP");
                }

                m_pLocalEP      = localEP;
                m_pRemoteEP     = remoteEP;
                m_SSL           = ssl;
                m_pCertCallback = certCallback;
            }

            #region method Dispose

            /// <summary>
            /// Cleans up any resource being used.
            /// </summary>
            public void Dispose()
            {
                if(m_State == AsyncOP_State.Disposed){
                    return;
                }
                SetState(AsyncOP_State.Disposed);

                m_pException    = null;
                m_pLocalEP      = null;
                m_pRemoteEP     = null;
                m_SSL           = false;
                m_pCertCallback = null;
                m_pTcpClient    = null;
                m_pSocket       = null;
                m_pStream       = null;

                this.CompletedAsync = null;
            }

            #endregion


            #region method Start

            /// <summary>
            /// Starts operation processing.
            /// </summary>
            /// <param name="owner">Owner TCP client.</param>
            /// <returns>Returns true if asynchronous operation in progress or false if operation completed synchronously.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>owner</b> is null reference.</exception>
            internal bool Start(TCP_Client owner)
            {
                if(owner == null){
                    throw new ArgumentNullException("owner");
                }

                m_pTcpClient = owner;

                SetState(AsyncOP_State.Active);

                try{
                    // Create socket.
                    if(m_pRemoteEP.AddressFamily == AddressFamily.InterNetwork){
                        m_pSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
                        m_pSocket.ReceiveTimeout = m_pTcpClient.m_Timeout;
                        m_pSocket.SendTimeout = m_pTcpClient.m_Timeout;
                    }
                    else if(m_pRemoteEP.AddressFamily == AddressFamily.InterNetworkV6){
                        m_pSocket = new Socket(AddressFamily.InterNetworkV6,SocketType.Stream,ProtocolType.Tcp);
                        m_pSocket.ReceiveTimeout = m_pTcpClient.m_Timeout;
                        m_pSocket.SendTimeout = m_pTcpClient.m_Timeout;
                    }
                    // Bind socket to the specified end point.
                    if(m_pLocalEP != null){
                        m_pSocket.Bind(m_pLocalEP);
                    }

                    m_pTcpClient.LogAddText("Connecting to " + m_pRemoteEP.ToString() + ".");

                    // Start connecting.
                    m_pSocket.BeginConnect(m_pRemoteEP,this.BeginConnectCompleted,null);
                }
                catch(Exception x){
                    m_pException = x;
                    CleanupSocketRelated();
                    if(m_pTcpClient != null){
                        m_pTcpClient.LogAddException("Exception: " + x.Message,x);
                    }
                    SetState(AsyncOP_State.Completed);

                    return false;
                }

                // Set flag rise CompletedAsync event flag. The event is raised when async op completes.
                // If already completed sync, that flag has no effect.
                lock(m_pLock){
                    m_RiseCompleted = true;

                    return m_State == AsyncOP_State.Active;
                }
            }

            #endregion


            #region method SetState

            /// <summary>
            /// Sets operation state.
            /// </summary>
            /// <param name="state">New state.</param>
            private void SetState(AsyncOP_State state)
            {
                if(m_State == AsyncOP_State.Disposed){
                    return;
                }

                lock(m_pLock){
                    m_State = state;

                    if(m_State == AsyncOP_State.Completed && m_RiseCompleted){
                        OnCompletedAsync();
                    }
                }
            }

            #endregion

            #region method BeginConnectCompleted

            /// <summary>
            /// This method is called when "BeginConnect" has completed.
            /// </summary>
            /// <param name="ar">Asynchronous result.</param>
            private void BeginConnectCompleted(IAsyncResult ar)
            {
                try{
                    m_pSocket.EndConnect(ar);

                    m_pTcpClient.LogAddText("Connected, localEP='" + m_pSocket.LocalEndPoint.ToString() + "'; remoteEP='" + m_pSocket.RemoteEndPoint.ToString() + "'.");

                    // Start SSL handshake.
                    if(m_SSL){
                        m_pTcpClient.LogAddText("Starting SSL handshake.");

                        m_pStream = new SslStream(new NetworkStream(m_pSocket,true),false,this.RemoteCertificateValidationCallback);
                        ((SslStream)m_pStream).BeginAuthenticateAsClient("dummy",this.BeginAuthenticateAsClientCompleted,null);
                    }
                    // We are done.
                    else{
                        m_pStream = new NetworkStream(m_pSocket,true);

                        InternalConnectCompleted();
                    }
                }
                catch(Exception x){
                    m_pException = x;
                    CleanupSocketRelated();
                    if(m_pTcpClient != null){
                        m_pTcpClient.LogAddException("Exception: " + x.Message,x);
                    }
                    SetState(AsyncOP_State.Completed);
                }
            }

            #endregion

            #region method BeginAuthenticateAsClientCompleted

            /// <summary>
            /// This method is called when "BeginAuthenticateAsClient" has completed.
            /// </summary>
            /// <param name="ar">Asynchronous result.</param>
            private void BeginAuthenticateAsClientCompleted(IAsyncResult ar)
            {
                try{
                    ((SslStream)m_pStream).EndAuthenticateAsClient(ar);

                    m_pTcpClient.LogAddText("SSL handshake completed sucessfully.");

                    InternalConnectCompleted();
                }
                catch(Exception x){
                    m_pException = x;
                    CleanupSocketRelated();
                    if(m_pTcpClient != null){
                        m_pTcpClient.LogAddException("Exception: " + x.Message,x);
                    }
                    SetState(AsyncOP_State.Completed);
                }
            }

            #endregion

            #region method RemoteCertificateValidationCallback

            /// <summary>
            /// This method is called when we need to validate remote server certificate.
            /// </summary>
            /// <param name="sender">Sender.</param>
            /// <param name="certificate">Certificate.</param>
            /// <param name="chain">Certificate chain.</param>
            /// <param name="sslPolicyErrors">SSL policy errors.</param>
            /// <returns>Returns true if certificate validated, otherwise false.</returns>
            private bool RemoteCertificateValidationCallback(object sender,X509Certificate certificate,X509Chain chain,SslPolicyErrors sslPolicyErrors)
            {
                // User will handle it.
                if(m_pCertCallback != null){
                    return m_pCertCallback(sender,certificate,chain,sslPolicyErrors);
                }
                else{
                    if(sslPolicyErrors == SslPolicyErrors.None || ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) > 0)){
                        return true;
                    }

                    // Do not allow this client to communicate with unauthenticated servers.
                    return false;
                }
            }

            #endregion

            #region method CleanupSocketRelated

            /// <summary>
            /// Cleans up any socket related resources.
            /// </summary>
            private void CleanupSocketRelated()
            {
                try{                    
                    if(m_pStream != null){
                        m_pStream.Dispose();
                    }
                    if(m_pSocket != null){
                        m_pSocket.Close();
                    }
                }
                catch{
                }
            }

            #endregion

            #region method InternalConnectCompleted

            /// <summary>
            /// Is called when when connecting has finished.
            /// </summary>
            private void InternalConnectCompleted()
            {
                m_pTcpClient.m_IsConnected = true;
                m_pTcpClient.m_ID          = Guid.NewGuid().ToString();
                m_pTcpClient.m_ConnectTime = DateTime.Now;
                m_pTcpClient.m_pLocalEP    = (IPEndPoint)m_pSocket.LocalEndPoint;
                m_pTcpClient.m_pRemoteEP   = (IPEndPoint)m_pSocket.RemoteEndPoint;
                m_pTcpClient.m_pTcpStream  = new SmartStream(m_pStream,true);
                m_pTcpClient.m_pTcpStream.Encoding = Encoding.UTF8;

                m_pTcpClient.OnConnected(this.CompleteConnectCallback);
            }

            #endregion

            #region method CompleteConnectCallback

            /// <summary>
            /// This method is called when this derrived class OnConnected processing has completed.
            /// </summary>
            /// <param name="error">Exception happened or null if no errors.</param>
            private void CompleteConnectCallback(Exception error)
            {
                m_pException = error;
                                
                SetState(AsyncOP_State.Completed);
            }

            #endregion


            #region Properties implementation

            /// <summary>
            /// Gets asynchronous operation state.
            /// </summary>
            public AsyncOP_State State
            {
                get{ return m_State; }
            }

            /// <summary>
            /// Gets error happened during operation. Returns null if no error.
            /// </summary>
            /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this property is accessed.</exception>
            /// <exception cref="InvalidOperationException">Is raised when this property is accessed other than <b>AsyncOP_State.Completed</b> state.</exception>
            public Exception Error
            {
                get{ 
                    if(m_State == AsyncOP_State.Disposed){
                        throw new ObjectDisposedException(this.GetType().Name);
                    }
                    if(m_State != AsyncOP_State.Completed){
                        throw new InvalidOperationException("Property 'Error' is accessible only in 'AsyncOP_State.Completed' state.");
                    }

                    return m_pException; 
                }
            }

            /// <summary>
            /// Gets connected socket.
            /// </summary>
            /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this property is accessed.</exception>
            /// <exception cref="InvalidOperationException">Is raised when this property is accessed other than <b>AsyncOP_State.Completed</b> state.</exception>
            public Socket Socket
            {
                get{ 
                    if(m_State == AsyncOP_State.Disposed){
                        throw new ObjectDisposedException(this.GetType().Name);
                    }
                    if(m_State != AsyncOP_State.Completed){
                        throw new InvalidOperationException("Property 'Socket' is accessible only in 'AsyncOP_State.Completed' state.");
                    }
                    if(m_pException != null){
                        throw m_pException;
                    }

                    return m_pSocket; 
                }
            }

            /// <summary>
            /// Gets connected TCP stream.
            /// </summary>
            /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this property is accessed.</exception>
            /// <exception cref="InvalidOperationException">Is raised when this property is accessed other than <b>AsyncOP_State.Completed</b> state.</exception>
            public Stream Stream
            {
                get{ 
                    if(m_State == AsyncOP_State.Disposed){
                        throw new ObjectDisposedException(this.GetType().Name);
                    }
                    if(m_State != AsyncOP_State.Completed){
                        throw new InvalidOperationException("Property 'Stream' is accessible only in 'AsyncOP_State.Completed' state.");
                    }
                    if(m_pException != null){
                        throw m_pException;
                    }

                    return m_pStream; 
                }
            }

            #endregion

            #region Events implementation

            /// <summary>
            /// Is called when asynchronous operation has completed.
            /// </summary>
            public event EventHandler<EventArgs<ConnectAsyncOP>> CompletedAsync = null;

            #region method OnCompletedAsync

            /// <summary>
            /// Raises <b>CompletedAsync</b> event.
            /// </summary>
            private void OnCompletedAsync()
            {
                if(this.CompletedAsync != null){
                    this.CompletedAsync(this,new EventArgs<ConnectAsyncOP>(this));
                }
            }

            #endregion

            #endregion
        }

        #endregion

        /// <summary>
        /// Starts connecting to remote end point.
        /// </summary>
        /// <param name="op">Asynchronous operation.</param>
        /// <returns>Returns true if aynchronous operation is pending (The <see cref="ConnectAsyncOP.CompletedAsync"/> event is raised upon completion of the operation).
        /// Returns false if operation completed synchronously.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>op</b> is null reference.</exception>
        public bool ConnectAsync(ConnectAsyncOP op)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(op == null){
                throw new ArgumentNullException("op");
            }
            if(op.State != AsyncOP_State.WaitingForStart){
                throw new ArgumentException("Invalid argument 'op' state, 'op' must be in 'AsyncOP_State.WaitingForStart' state.","op");
            }

            return op.Start(this);
        }

        #endregion

        #region method Disconnect

        /// <summary>
        /// Disconnects connection.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public override void Disconnect()
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException("TCP_Client");
            }
            if(!m_IsConnected){
                throw new InvalidOperationException("TCP client is not connected.");
            }
            m_IsConnected = false;

            m_pLocalEP = null;
            m_pRemoteEP = null;
            m_pTcpStream.Dispose();
            m_IsSecure = false;
            m_pTcpStream = null;

            LogAddText("Disconnected.");
        }

        #endregion

        #region method BeginDisconnect

        /// <summary>
        /// Internal helper method for asynchronous Disconnect method.
        /// </summary>
        private delegate void DisconnectDelegate();

        /// <summary>
        /// Starts disconnecting connection.
        /// </summary>
        /// <param name="callback">Callback to call when the asynchronous operation is complete.</param>
        /// <param name="state">User data.</param>
        /// <returns>An IAsyncResult that references the asynchronous disconnect.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public IAsyncResult BeginDisconnect(AsyncCallback callback,object state)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(!m_IsConnected){
                throw new InvalidOperationException("TCP client is not connected.");
            }

            DisconnectDelegate asyncMethod = new DisconnectDelegate(this.Disconnect);
            AsyncResultState asyncState = new AsyncResultState(this,asyncMethod,callback,state);
            asyncState.SetAsyncResult(asyncMethod.BeginInvoke(new AsyncCallback(asyncState.CompletedCallback),null));

            return asyncState;
        }

        #endregion

        #region method EndDisconnect

        /// <summary>
        /// Ends a pending asynchronous disconnect request.
        /// </summary>
        /// <param name="asyncResult">An IAsyncResult that stores state information and any user defined data for this asynchronous operation.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>asyncResult</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when argument <b>asyncResult</b> was not returned by a call to the <b>BeginDisconnect</b> method.</exception>
        /// <exception cref="InvalidOperationException">Is raised when <b>EndDisconnect</b> was previously called for the asynchronous connection.</exception>
        public void EndDisconnect(IAsyncResult asyncResult)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(asyncResult == null){
                throw new ArgumentNullException("asyncResult");
            }
            
            AsyncResultState castedAsyncResult = asyncResult as AsyncResultState;
            if(castedAsyncResult == null || castedAsyncResult.AsyncObject != this){
                throw new ArgumentException("Argument asyncResult was not returned by a call to the BeginDisconnect method.");
            }
            if(castedAsyncResult.IsEndCalled){
                throw new InvalidOperationException("EndDisconnect was previously called for the asynchronous connection.");
            }
             
            castedAsyncResult.IsEndCalled = true;
            if(castedAsyncResult.AsyncDelegate is DisconnectDelegate){
                ((DisconnectDelegate)castedAsyncResult.AsyncDelegate).EndInvoke(castedAsyncResult.AsyncResult);
            }
            else{
                throw new ArgumentException("Argument asyncResult was not returned by a call to the BeginDisconnect method.");
            }
        }

        #endregion


        #region method SwitchToSecure

        /// <summary>
        /// Switches session to secure connection.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected or is already secure.</exception>
        protected void SwitchToSecure()
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException("TCP_Client");
            }
            if(!m_IsConnected){
                throw new InvalidOperationException("TCP client is not connected.");
            }
            if(m_IsSecure){
                throw new InvalidOperationException("TCP client is already secure.");
            }

            LogAddText("Switching to SSL.");

            // FIX ME: if ssl switching fails, it closes source stream or otherwise if ssl successful, source stream leaks.

            SslStream sslStream = new SslStream(m_pTcpStream.SourceStream,true,this.RemoteCertificateValidationCallback);
            sslStream.AuthenticateAsClient("dummy");

            // Close old stream, but leave source stream open.
            m_pTcpStream.IsOwner = false;
            m_pTcpStream.Dispose();

            m_IsSecure = true;
            m_pTcpStream = new SmartStream(sslStream,true);
        }

        #region method RemoteCertificateValidationCallback

        private bool RemoteCertificateValidationCallback(object sender,X509Certificate certificate,X509Chain chain,SslPolicyErrors sslPolicyErrors)
        {
            // User will handle it.
            if(m_pCertificateCallback != null){
                return m_pCertificateCallback(sender,certificate,chain,sslPolicyErrors);
            }
            else{
                if(sslPolicyErrors == SslPolicyErrors.None || ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) > 0)){
                    return true;
                }

                // Do not allow this client to communicate with unauthenticated servers.
                return false;
            }
        }

        #endregion

        #endregion

        #region method SwitchToSecureAsync

        #region class SwitchToSecureAsyncOP

        /// <summary>
        /// This class represents <see cref="TCP_Client.SwitchToSecureAsync"/> asynchronous operation.
        /// </summary>
        protected class SwitchToSecureAsyncOP : IDisposable,IAsyncOP
        {
            private object                              m_pLock         = new object();
            private bool                                m_RiseCompleted = false;
            private AsyncOP_State                       m_State         = AsyncOP_State.WaitingForStart;
            private Exception                           m_pException    = null;
            private RemoteCertificateValidationCallback m_pCertCallback = null;
            private TCP_Client                          m_pTcpClient    = null;
            private SslStream                           m_pSslStream    = null;

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="certCallback">SSL server certificate validation callback. Value null means any certificate is accepted.</param>
            public SwitchToSecureAsyncOP(RemoteCertificateValidationCallback certCallback)
            {
                m_pCertCallback = certCallback;
            }

            #region method Dispose

            /// <summary>
            /// Cleans up any resource being used.
            /// </summary>
            public void Dispose()
            {
                if(m_State == AsyncOP_State.Disposed){
                    return;
                }
                SetState(AsyncOP_State.Disposed);
                
                m_pException    = null;
                m_pCertCallback = null;
                m_pSslStream    = null;

                this.CompletedAsync = null;
            }

            #endregion


            #region method Start

            /// <summary>
            /// Starts operation processing.
            /// </summary>
            /// <param name="owner">Owner TCP client.</param>
            /// <returns>Returns true if asynchronous operation in progress or false if operation completed synchronously.</returns>
            /// <exception cref="ArgumentNullException">Is raised when <b>owner</b> is null reference.</exception>
            internal bool Start(TCP_Client owner)
            {
                if(owner == null){
                    throw new ArgumentNullException("owner");
                }

                m_pTcpClient = owner;

                SetState(AsyncOP_State.Active);

                try{
                    m_pSslStream = new SslStream(m_pTcpClient.m_pTcpStream.SourceStream,false,this.RemoteCertificateValidationCallback);
                    m_pSslStream.BeginAuthenticateAsClient("dummy",this.BeginAuthenticateAsClientCompleted,null);                  
                }
                catch(Exception x){
                    m_pException = x;
                    SetState(AsyncOP_State.Completed);
                }

                // Set flag rise CompletedAsync event flag. The event is raised when async op completes.
                // If already completed sync, that flag has no effect.
                lock(m_pLock){
                    m_RiseCompleted = true;

                    return m_State == AsyncOP_State.Active;
                }
            }

            #endregion


            #region method SetState

            /// <summary>
            /// Sets operation state.
            /// </summary>
            /// <param name="state">New state.</param>
            private void SetState(AsyncOP_State state)
            {
                if(m_State == AsyncOP_State.Disposed){
                    return;
                }

                lock(m_pLock){
                    m_State = state;

                    if(m_State == AsyncOP_State.Completed && m_RiseCompleted){
                        OnCompletedAsync();
                    }
                }
            }

            #endregion

            #region method RemoteCertificateValidationCallback

            /// <summary>
            /// This method is called when we need to validate remote server certificate.
            /// </summary>
            /// <param name="sender">Sender.</param>
            /// <param name="certificate">Certificate.</param>
            /// <param name="chain">Certificate chain.</param>
            /// <param name="sslPolicyErrors">SSL policy errors.</param>
            /// <returns>Returns true if certificate validated, otherwise false.</returns>
            private bool RemoteCertificateValidationCallback(object sender,X509Certificate certificate,X509Chain chain,SslPolicyErrors sslPolicyErrors)
            {
                // User will handle it.
                if(m_pCertCallback != null){
                    return m_pCertCallback(sender,certificate,chain,sslPolicyErrors);
                }
                else{
                    if(sslPolicyErrors == SslPolicyErrors.None || ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) > 0)){
                        return true;
                    }

                    // Do not allow this client to communicate with unauthenticated servers.
                    return false;
                }
            }

            #endregion

            #region method BeginAuthenticateAsClientCompleted

            /// <summary>
            /// This method is called when "BeginAuthenticateAsClient" has completed.
            /// </summary>
            /// <param name="ar">Asynchronous result.</param>
            private void BeginAuthenticateAsClientCompleted(IAsyncResult ar)
            {
                try{
                    m_pSslStream.EndAuthenticateAsClient(ar);

                    // Close old stream, but leave source stream open.
                    m_pTcpClient.m_pTcpStream.IsOwner = false;
                    m_pTcpClient.m_pTcpStream.Dispose();

                    m_pTcpClient.m_IsSecure = true;
                    m_pTcpClient.m_pTcpStream = new SmartStream(m_pSslStream,true);
                }
                catch(Exception x){
                    m_pException = x;                    
                }

                SetState(AsyncOP_State.Completed);
            }

            #endregion


            #region Properties implementation

            /// <summary>
            /// Gets asynchronous operation state.
            /// </summary>
            public AsyncOP_State State
            {
                get{ return m_State; }
            }

            /// <summary>
            /// Gets error happened during operation. Returns null if no error.
            /// </summary>
            /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this property is accessed.</exception>
            /// <exception cref="InvalidOperationException">Is raised when this property is accessed other than <b>AsyncOP_State.Completed</b> state.</exception>
            public Exception Error
            {
                get{ 
                    if(m_State == AsyncOP_State.Disposed){
                        throw new ObjectDisposedException(this.GetType().Name);
                    }
                    if(m_State != AsyncOP_State.Completed){
                        throw new InvalidOperationException("Property 'Error' is accessible only in 'AsyncOP_State.Completed' state.");
                    }

                    return m_pException; 
                }
            }

            #endregion

            #region Events implementation

            /// <summary>
            /// Is called when asynchronous operation has completed.
            /// </summary>
            public event EventHandler<EventArgs<SwitchToSecureAsyncOP>> CompletedAsync = null;

            #region method OnCompletedAsync

            /// <summary>
            /// Raises <b>CompletedAsync</b> event.
            /// </summary>
            private void OnCompletedAsync()
            {
                if(this.CompletedAsync != null){
                    this.CompletedAsync(this,new EventArgs<SwitchToSecureAsyncOP>(this));
                }
            }

            #endregion

            #endregion
        }

        #endregion

        /// <summary>
        /// Starts switching connection to secure.
        /// </summary>
        /// <param name="op">Asynchronous operation.</param>
        /// <returns>Returns true if aynchronous operation is pending (The <see cref="SwitchToSecureAsyncOP.CompletedAsync"/> event is raised upon completion of the operation).
        /// Returns false if operation completed synchronously.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected or connection is already secure.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>op</b> is null reference.</exception>
        protected bool SwitchToSecureAsync(SwitchToSecureAsyncOP op)
        {
            if(this.IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(!this.IsConnected){
                throw new InvalidOperationException("You must connect first.");
            }
            if(this.IsSecureConnection){
                throw new InvalidOperationException("Connection is already secure.");
            }
            if(op == null){
                throw new ArgumentNullException("op");
            }
            if(op.State != AsyncOP_State.WaitingForStart){
                throw new ArgumentException("Invalid argument 'op' state, 'op' must be in 'AsyncOP_State.WaitingForStart' state.","op");
            }

            return op.Start(this);
        }

        #endregion

        
        #region virtual method OnConnected

        /// <summary>
        /// This method is called after TCP client has sucessfully connected.
        /// </summary>
        protected virtual void OnConnected()
        {
        }

        /// <summary>
        /// Represents callback to be called when to complete connect operation.
        /// </summary>
        /// <param name="error">Exception happened or null if no errors.</param>
        protected delegate void CompleteConnectCallback(Exception error);

        /// <summary>
        /// This method is called when TCP client has sucessfully connected.
        /// </summary>
        /// <param name="callback">Callback to be called to complete connect operation.</param>
        protected virtual void OnConnected(CompleteConnectCallback callback)
        {
            try{
                OnConnected();

                callback(null);
            }
            catch(Exception x){
                callback(x);
            }
        }

        #endregion


        #region method ReadLine

        /// <summary>
        /// Reads and logs specified line from connected host.
        /// </summary>
        /// <returns>Returns readed line.</returns>
        protected string ReadLine()
        {
            SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException);
            this.TcpStream.ReadLine(args,false);
            if(args.Error != null){
                throw args.Error;
            }
            string line = args.LineUtf8;
            if(args.BytesInBuffer > 0){
                LogAddRead(args.BytesInBuffer,line);
            }
            else{
                LogAddText("Remote host closed connection.");
            }

            return line;
        }

        #endregion

        #region method WriteLine

        /// <summary>
        /// Sends and logs specified line to connected host.
        /// </summary>
        /// <param name="line">Line to send.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>line</b> is null reference.</exception>
        protected void WriteLine(string line)
        {
            if(line == null){
                throw new ArgumentNullException("line");
            }

            int countWritten = this.TcpStream.WriteLine(line);
            LogAddWrite(countWritten,line);
        }

        #endregion


        #region mehtod LogAddRead

        /// <summary>
        /// Logs read operation.
        /// </summary>
        /// <param name="size">Number of bytes readed.</param>
        /// <param name="text">Log text.</param>
        internal protected void LogAddRead(long size,string text)
        {
            try{
                if(m_pLogger != null){
                    m_pLogger.AddRead(
                        this.ID,
                        this.AuthenticatedUserIdentity,
                        size,
                        text,                        
                        this.LocalEndPoint,
                        this.RemoteEndPoint
                    );
                }
            }
            catch{
                // We skip all logging errors, normally there shouldn't be any.
            }
        }

        #endregion

        #region method LogAddWrite

        /// <summary>
        /// Logs write operation.
        /// </summary>
        /// <param name="size">Number of bytes written.</param>
        /// <param name="text">Log text.</param>
        internal protected void LogAddWrite(long size,string text)
        {
            try{
                if(m_pLogger != null){
                    m_pLogger.AddWrite(
                        this.ID,
                        this.AuthenticatedUserIdentity,
                        size,
                        text,                        
                        this.LocalEndPoint,
                        this.RemoteEndPoint
                    );
                }
            }
            catch{
                // We skip all logging errors, normally there shouldn't be any.
            }
        }

        #endregion

        #region method LogAddText

        /// <summary>
        /// Logs free text entry.
        /// </summary>
        /// <param name="text">Log text.</param>
        internal protected void LogAddText(string text)
        {
            try{
                if(m_pLogger != null){
                    m_pLogger.AddText(
                        this.IsConnected ? this.ID : "",
                        this.IsConnected ? this.AuthenticatedUserIdentity : null,
                        text,                        
                        this.IsConnected ? this.LocalEndPoint : null,
                        this.IsConnected ? this.RemoteEndPoint : null
                    );
                }
            }
            catch{
                // We skip all logging errors, normally there shouldn't be any.
            }
        }

        #endregion

        #region method LogAddException

        /// <summary>
        /// Logs exception.
        /// </summary>
        /// <param name="text">Log text.</param>
        /// <param name="x">Exception happened.</param>
        internal protected void LogAddException(string text,Exception x)
        {
            try{
                if(m_pLogger != null){
                    m_pLogger.AddException(
                        this.IsConnected ? this.ID : "",
                        this.IsConnected ? this.AuthenticatedUserIdentity : null,
                        text,                        
                        this.IsConnected ? this.LocalEndPoint : null,
                        this.IsConnected ? this.RemoteEndPoint : null,
                        x
                    );
                }
            }
            catch{
                // We skip all logging errors, normally there shouldn't be any.
            }
        }

        #endregion


        #region Properties Implementation

        /// <summary>
        /// Gets if this object is disposed.
        /// </summary>
        public bool IsDisposed
        {
            get{ return m_IsDisposed; }
        }

        /// <summary>
        /// Gets or sets TCP client logger. Value null means no logging.
        /// </summary>
        public Logger Logger
        {
            get{ return m_pLogger; }

            set{ m_pLogger = value; }
        }


        /// <summary>
        /// Gets if TCP client is connected.
        /// </summary>
        public override bool IsConnected
        {
            get{ return m_IsConnected; }
        }

        /// <summary>
        /// Gets session ID.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public override string ID
        {
            get{                
                if(m_IsDisposed){
                    throw new ObjectDisposedException("TCP_Client");
                }
                if(!m_IsConnected){
                    throw new InvalidOperationException("TCP client is not connected.");
                }

                return m_ID; 
            }
        }

        /// <summary>
        /// Gets the time when session was connected.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public override DateTime ConnectTime
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException("TCP_Client");
                }
                if(!m_IsConnected){
                    throw new InvalidOperationException("TCP client is not connected.");
                }

                return m_ConnectTime; 
            }
        }

        /// <summary>
        /// Gets the last time when data was sent or received.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public override DateTime LastActivity
        {
            get{ 
                if(m_IsDisposed){
                    throw new ObjectDisposedException("TCP_Client");
                }
                if(!m_IsConnected){
                    throw new InvalidOperationException("TCP client is not connected.");
                }

                return m_pTcpStream.LastActivity; 
            }
        }

        /// <summary>
        /// Gets session local IP end point.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public override IPEndPoint LocalEndPoint
        {
            get{ 
                if(m_IsDisposed){
                    throw new ObjectDisposedException("TCP_Client");
                }
                if(!m_IsConnected){
                    throw new InvalidOperationException("TCP client is not connected.");
                }

                return m_pLocalEP; 
            }
        }

        /// <summary>
        /// Gets session remote IP end point.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public override IPEndPoint RemoteEndPoint
        {
            get{ 
                if(m_IsDisposed){
                    throw new ObjectDisposedException("TCP_Client");
                }
                if(!m_IsConnected){
                    throw new InvalidOperationException("TCP client is not connected.");
                }

                return m_pRemoteEP; 
            }
        }
        
        /// <summary>
        /// Gets if this session TCP connection is secure connection.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public override bool IsSecureConnection
        {
            get{ 
                if(m_IsDisposed){
                    throw new ObjectDisposedException("TCP_Client");
                }
                if(!m_IsConnected){
                    throw new InvalidOperationException("TCP client is not connected.");
                }

                return m_IsSecure; 
            }
        }

        /// <summary>
        /// Gets TCP stream which must be used to send/receive data through this session.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected.</exception>
        public override SmartStream TcpStream
        {
            get{ 
                if(m_IsDisposed){
                    throw new ObjectDisposedException("TCP_Client");
                }
                if(!m_IsConnected){
                    throw new InvalidOperationException("TCP client is not connected.");
                }

                return m_pTcpStream; 
            }
        }

        /// <summary>
        /// Gets or stes remote callback which is called when remote server certificate needs to be validated.
        /// Value null means not sepcified.
        /// </summary>
        public RemoteCertificateValidationCallback ValidateCertificateCallback
        {
            get{ return m_pCertificateCallback; }

            set{ m_pCertificateCallback = value; }
        }

        /// <summary>
        /// Gets or sets default TCP read/write timeout.
        /// </summary>
        /// <remarks>This timeout applies only synchronous TCP read/write operations.</remarks>
        public int Timeout
        {
            get{ return m_Timeout; }

            set{ m_Timeout = value; }
        }

        #endregion


        // OBSOLETE

        #region method BeginConnect
        
        /// <summary>
        /// Internal helper method for asynchronous Connect method.
        /// </summary>
        /// <param name="host">Host name or IP address.</param>
        /// <param name="port">Port to connect.</param>
        /// <param name="ssl">Specifies if connects to SSL end point.</param>
        private delegate void BeginConnectHostDelegate(string host,int port,bool ssl);

        /// <summary>
        /// Internal helper method for asynchronous Connect method.
        /// </summary>
        /// <param name="localEP">Local IP end point to use for connect.</param>
        /// <param name="remoteEP">Remote IP end point where to connect.</param>
        /// <param name="ssl">Specifies if connects to SSL end point.</param>
        private delegate void BeginConnectEPDelegate(IPEndPoint localEP,IPEndPoint remoteEP,bool ssl);

        /// <summary>
        /// Starts connection to the specified host.
        /// </summary>
        /// <param name="host">Host name or IP address.</param>
        /// <param name="port">Port to connect.</param>
        /// <param name="callback">Callback to call when the connect operation is complete.</param>
        /// <param name="state">User data.</param>
        /// <returns>An IAsyncResult that references the asynchronous connection.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        [Obsolete("Use method ConnectAsync instead.")]
        public IAsyncResult BeginConnect(string host,int port,AsyncCallback callback,object state)
        {            
            return BeginConnect(host,port,false,callback,state);
        }
                
        /// <summary>
        /// Starts connection to the specified host.
        /// </summary>
        /// <param name="host">Host name or IP address.</param>
        /// <param name="port">Port to connect.</param>
        /// <param name="ssl">Specifies if connects to SSL end point.</param>
        /// <param name="callback">Callback to call when the connect operation is complete.</param>
        /// <param name="state">User data.</param>
        /// <returns>An IAsyncResult that references the asynchronous connection.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        [Obsolete("Use method ConnectAsync instead.")]
        public IAsyncResult BeginConnect(string host,int port,bool ssl,AsyncCallback callback,object state)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(m_IsConnected){
                throw new InvalidOperationException("TCP client is already connected.");
            }
            if(string.IsNullOrEmpty(host)){
                throw new ArgumentException("Argument 'host' value may not be null or empty.");
            }
            if(port < 1){
                throw new ArgumentException("Argument 'port' value must be >= 1.");
            }

            BeginConnectHostDelegate asyncMethod = new BeginConnectHostDelegate(this.Connect);
            AsyncResultState asyncState = new AsyncResultState(this,asyncMethod,callback,state);
            asyncState.SetAsyncResult(asyncMethod.BeginInvoke(host,port,ssl,new AsyncCallback(asyncState.CompletedCallback),null));

            return asyncState;
        }

        /// <summary>
        /// Starts connection to the specified remote end point.
        /// </summary>
        /// <param name="remoteEP">Remote IP end point where to connect.</param>
        /// <param name="ssl">Specifies if connects to SSL end point.</param>
        /// <param name="callback">Callback to call when the connect operation is complete.</param>
        /// <param name="state">User data.</param>
        /// <returns>An IAsyncResult that references the asynchronous connection.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>remoteEP</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        [Obsolete("Use method ConnectAsync instead.")]
        public IAsyncResult BeginConnect(IPEndPoint remoteEP,bool ssl,AsyncCallback callback,object state)
        {
            return BeginConnect(null,remoteEP,ssl,callback,state);
        }

        /// <summary>
        /// Starts connection to the specified remote end point.
        /// </summary>
        /// <param name="localEP">Local IP end point to use for connect.</param>
        /// <param name="remoteEP">Remote IP end point where to connect.</param>
        /// <param name="ssl">Specifies if connects to SSL end point.</param>
        /// <param name="callback">Callback to call when the connect operation is complete.</param>
        /// <param name="state">User data.</param>
        /// <returns>An IAsyncResult that references the asynchronous connection.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when TCP client is already connected.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>remoteEP</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        [Obsolete("Use method ConnectAsync instead.")]
        public IAsyncResult BeginConnect(IPEndPoint localEP,IPEndPoint remoteEP,bool ssl,AsyncCallback callback,object state)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(m_IsConnected){
                throw new InvalidOperationException("TCP client is already connected.");
            }
            if(remoteEP == null){
                throw new ArgumentNullException("remoteEP");
            }
            
            BeginConnectEPDelegate asyncMethod = new BeginConnectEPDelegate(this.Connect);
            AsyncResultState asyncState = new AsyncResultState(this,asyncMethod,callback,state);
            asyncState.SetAsyncResult(asyncMethod.BeginInvoke(localEP,remoteEP,ssl,new AsyncCallback(asyncState.CompletedCallback),null));

            return asyncState;
        }

        #endregion

        #region method EndConnect

        /// <summary>
        /// Ends a pending asynchronous connection request.
        /// </summary>
        /// <param name="asyncResult">An IAsyncResult that stores state information and any user defined data for this asynchronous operation.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>asyncResult</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when argument <b>asyncResult</b> was not returned by a call to the <b>BeginConnect</b> method.</exception>
        /// <exception cref="InvalidOperationException">Is raised when <b>EndConnect</b> was previously called for the asynchronous connection.</exception>
        [Obsolete("Use method ConnectAsync instead.")]
        public void EndConnect(IAsyncResult asyncResult)
        {   
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(asyncResult == null){
                throw new ArgumentNullException("asyncResult");
            }
                        
            AsyncResultState castedAsyncResult = asyncResult as AsyncResultState;
            if(castedAsyncResult == null || castedAsyncResult.AsyncObject != this){                
                throw new ArgumentException("Argument asyncResult was not returned by a call to the BeginConnect method.");
            }
            if(castedAsyncResult.IsEndCalled){
                throw new InvalidOperationException("EndConnect was previously called for the asynchronous operation.");
            }
             
            castedAsyncResult.IsEndCalled = true;
            if(castedAsyncResult.AsyncDelegate is BeginConnectHostDelegate){
                ((BeginConnectHostDelegate)castedAsyncResult.AsyncDelegate).EndInvoke(castedAsyncResult.AsyncResult);
            }
            else if(castedAsyncResult.AsyncDelegate is BeginConnectEPDelegate){
                ((BeginConnectEPDelegate)castedAsyncResult.AsyncDelegate).EndInvoke(castedAsyncResult.AsyncResult);
            }
            else{
                throw new ArgumentException("Argument asyncResult was not returned by a call to the BeginConnect method.");
            }
        }

        #endregion

        #region method OnError

        /// <summary>
        /// This must be called when unexpected error happens. When inheriting <b>TCP_Client</b> class, be sure that you call <b>OnError</b>
        /// method for each unexpected error.
        /// </summary>
        /// <param name="x">Exception happened.</param>
        [Obsolete("Don't use this method.")]
        protected void OnError(Exception x)
        {
            try{
                if(m_pLogger != null){
                    //m_pLogger.AddException(x);
                }
            }
            catch{
            }
        }

        #endregion

    }
}
