<commit_before>﻿//******************************************************************************************************
//  ClientBase.cs - Gbtc
//
//  Copyright © 2015, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/MIT
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  06/01/2006 - <NAME>
//       Original version of source code generated.
//  09/06/2006 - <NAME>
//       Added bypass optimizations for high-speed client data access.
//  11/30/2007 - <NAME>
//       Modified the "design time" check in EndInit() method to use LicenseManager.UsageMode property
//       instead of DesignMode property as the former is more accurate than the latter.
//  02/19/2008 - <NAME>
//       Added code to detect and avoid redundant calls to Dispose().
//  09/29/2008 - <NAME>
//       Converted to C#.
//  06/18/2009 - <NAME>
//       Fixed the implementation of Enabled property.
//  07/02/2009 - <NAME>
//       Modified state altering properties to reconnect the client when changed.
//  07/08/2009 - <NAME>
//       Added WaitHandle return value from asynchronous connection.
//  07/15/2009 - <NAME>
//       Modified Connect() to wait for post-connection processing to complete.
//  07/17/2009 - <NAME>l
//       Modified SharedSecret to be persisted as an encrypted value.
//  08/05/2009 - <NAME>
//       Edited Comments.
//  09/14/2009 - <NAME>
//       Added new header and license agreement.
//  11/29/2010 - <NAME>
//       Updated the implementation of Connect() method so it blocks correctly after updates made to 
//       ConnectAsync() method in the derived classes.
//  04/14/2011 - <NAME>
//       Updated to use new serialization methods in GSF.Serialization class.
//  12/02/2011 - <NAME>
//       Updated event data publication to provide "copy" of reusable buffer instead of original
//       buffer since you cannot assume how user will use the buffer (they may cache it).
//  12/29/2011 - <NAME>
//       Updated Status property to show ConnectionString information.
//  04/26/2012 - <NAME>
//       Updated Create() static method to apply settings from the connection string to the created 
//       client instance using reflection.
//  05/22/2015 - <NAME>
//       Added ZeroMQ to the create IClient options.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Reflection;
using System.Text;
using System.Threading;
using GSF.Configuration;
using GSF.Diagnostics;
using GSF.Units;

// ReSharper disable VirtualMemberCallInConstructor
namespace GSF.Communication
{
    /// <summary>
    /// Base class for a client involved in server-client communication.
    /// </summary>
    [ToolboxBitmap(typeof(ClientBase))]
    public abstract class ClientBase : Component, IClient, ISupportInitialize, IPersistSettings
    {
        #region [ Members ]

        // Constants

        /// <summary>
        /// Specifies the default value for the <see cref="MaxConnectionAttempts"/> property.
        /// </summary>
        public const int DefaultMaxConnectionAttempts = -1;

        /// <summary>
        /// Specifies the default value for the <see cref="SendBufferSize"/> property.
        /// </summary>
        public const int DefaultSendBufferSize = 32768;

        /// <summary>
        /// Specifies the default value for the <see cref="ReceiveBufferSize"/> property.
        /// </summary>
        public const int DefaultReceiveBufferSize = 32768;

        /// <summary>
        /// Specifies the default value for the <see cref="PersistSettings"/> property.
        /// </summary>
        public const bool DefaultPersistSettings = false;

        /// <summary>
        /// Specifies the default value for the <see cref="SettingsCategory"/> property.
        /// </summary>
        public const string DefaultSettingsCategory = "CommunicationClient";

        // Events

        /// <summary>
        /// Occurs when client is attempting connection to the server.
        /// </summary>
        [Category("Connection")]
        [Description("Occurs when client is attempting connection to the server.")]
        public event EventHandler ConnectionAttempt;

        /// <summary>
        /// Occurs when client connection to the server is established.
        /// </summary>
        [Category("Connection")]
        [Description("Occurs when client connection to the server is established.")]
        public event EventHandler ConnectionEstablished;

        /// <summary>
        /// Occurs when client connection to the server is terminated.
        /// </summary>
        [Category("Connection")]
        [Description("Occurs when client connection to the server is terminated")]
        public event EventHandler ConnectionTerminated;

        /// <summary>
        /// Occurs when an <see cref="Exception"/> is encountered during connection attempt to the server.
        /// </summary>
        [Category("Connection")]
        [Description("Occurs when an Exception is encountered during connection attempt to the server.")]
        public event EventHandler<EventArgs<Exception>> ConnectionException;

        /// <summary>
        /// Occurs when the client begins sending data to the server.
        /// </summary>
        [Category("Data")]
        [Description("Occurs when the client begins sending data to the server.")]
        public event EventHandler SendDataStart;

        /// <summary>
        /// Occurs when the client has successfully sent data to the server.
        /// </summary>
        [Category("Data")]
        [Description("Occurs when the client has successfully sent data to the server.")]
        public event EventHandler SendDataComplete;

        /// <summary>
        /// Occurs when an <see cref="Exception"/> is encountered when sending data to the server.
        /// </summary>
        /// <remarks>
        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> encountered when sending data to the server.
        /// </remarks>
        [Category("Data")]
        [Description("Occurs when an Exception is encountered when sending data to the server.")]
        public event EventHandler<EventArgs<Exception>> SendDataException;

        /// <summary>
        /// Occurs when unprocessed data has been received from the server.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This event can be used to receive a notification that server data has arrived. The <see cref="Read"/> method can then be used
        /// to copy data to an existing buffer. In many cases it will be optimal to use an existing buffer instead of subscribing to the
        /// <see cref="ReceiveDataComplete"/> event.
        /// </para>
        /// <para>
        /// <see cref="EventArgs{T}.Argument"/> is the number of bytes received in the buffer from the server.
        /// </para>
        /// </remarks>
        [Category("Data")]
        [Description("Occurs when unprocessed data has been received from the server.")]
        public event EventHandler<EventArgs<int>> ReceiveData;

        /// <summary>
        /// Occurs when data received from the server has been processed and is ready for consumption.
        /// </summary>
        /// <remarks>
        /// <see cref="EventArgs{T1,T2}.Argument1"/> is a new buffer containing post-processed data received from the server starting at index zero.<br/>
        /// <see cref="EventArgs{T1,T2}.Argument2"/> is the number of post-processed bytes received in the buffer from the server.
        /// </remarks>
        [Category("Data")]
        [Description("Occurs when data received from the server has been processed and is ready for consumption.")]
        public event EventHandler<EventArgs<byte[], int>> ReceiveDataComplete;

        /// <summary>
        /// Occurs when an <see cref="Exception"/> is encountered when receiving data from the server.
        /// </summary>
        /// <remarks>
        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> encountered when receiving data from the server.
        /// </remarks>
        [Category("Data")]
        [Description("Occurs when an Exception is encountered when receiving data from the server.")]
        public event EventHandler<EventArgs<Exception>> ReceiveDataException;

        /// <summary>
        /// Occurs when an <see cref="Exception"/> is encountered in a user-defined function via an event dispatch.
        /// </summary>
        /// <remarks>
        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> thrown by the user-defined function.
        /// </remarks>
        [Category("User")]
        [Description("Occurs when an Exception is encountered when calling a user-defined function.")]
        public event EventHandler<EventArgs<Exception>> UnhandledUserException;

        // Fields
        private string m_connectionString;
        private int m_maxConnectionAttempts;
        private int m_sendBufferSize;
        private int m_receiveBufferSize;
        private string m_settingsCategory;
        private Encoding m_textEncoding;
        private ClientState m_currentState;
        private readonly TransportProtocol m_transportProtocol;
        private Ticks m_connectTime;
        private Ticks m_disconnectTime;
        private ManualResetEvent m_connectHandle;
        private bool m_initialized;

        private readonly Action<int> m_updateBytesSent;
        private readonly Action<int> m_updateBytesReceived;

        #endregion

        #region [ Constructors ]

        /// <summary>
        /// Initializes a new instance of the client.
        /// </summary>
        protected ClientBase()
        {
            m_textEncoding = Encoding.ASCII;
            m_currentState = ClientState.Disconnected;
            m_maxConnectionAttempts = DefaultMaxConnectionAttempts;
            m_sendBufferSize = DefaultSendBufferSize;
            m_receiveBufferSize = DefaultReceiveBufferSize;
            PersistSettings = DefaultPersistSettings;
            m_settingsCategory = DefaultSettingsCategory;
            Statistics = new TransportStatistics();
            m_updateBytesSent = TrackStatistics ? UpdateBytesSent : new Action<int>(bytes => { });
            m_updateBytesReceived = TrackStatistics ? UpdateBytesReceived : new Action<int>(bytes => { });
        }

        /// <summary>
        /// Initializes a new instance of the client.
        /// </summary>
        /// <param name="transportProtocol">One of the <see cref="TransportProtocol"/> values.</param>
        /// <param name="connectionString">The data used by the client for connection to a server.</param>
        protected ClientBase(TransportProtocol transportProtocol, string connectionString) : this()
        {
            m_transportProtocol = transportProtocol;
            ConnectionString = connectionString;
        }

        #endregion

        #region [ Properties ]

        /// <summary>
        /// Gets the server URI.
        /// </summary>
        public abstract string ServerUri { get; }

        /// <summary>
        /// Gets or sets the data required by the client to connect to the server.
        /// </summary>
        [Category("Settings")]
        [Description("The data required by the client to connect to the server.")]
        public virtual string ConnectionString
        {
            get => m_connectionString;
            set
            {
                ValidateConnectionString(value);

                m_connectionString = value;
                ReConnect();
            }
        }

        /// <summary>
        /// Gets or sets the maximum number of times the client will attempt to connect to the server.
        /// </summary>
        /// <remarks>Set <see cref="MaxConnectionAttempts"/> to -1 for infinite connection attempts.</remarks>
        [Category("Settings")]
        [DefaultValue(DefaultMaxConnectionAttempts)]
        [Description("The maximum number of times the client will attempt to connect to the server. Set MaxConnectionAttempts to -1 for infinite connection attempts.")]
        public virtual int MaxConnectionAttempts
        {
            get => m_maxConnectionAttempts;
            set => m_maxConnectionAttempts = value < 1 ? -1 : value;
        }

        /// <summary>
        /// Gets or sets the size of the buffer used by the client for sending data to the server.
        /// </summary>
        /// <exception cref="ArgumentException">The value being assigned is either zero or negative.</exception>
        [Category("Data")]
        [DefaultValue(DefaultSendBufferSize)]
        [Description("The size of the buffer used by the client for receiving data from the server.")]
        public virtual int SendBufferSize
        {
            get => m_sendBufferSize;
            set
            {
                if (value < 1)
                    throw new ArgumentException("Value cannot be zero or negative");

                m_sendBufferSize = value;
            }
        }

        /// <summary>
        /// Gets or sets the size of the buffer used by the client for receiving data from the server.
        /// </summary>
        /// <exception cref="ArgumentException">The value being assigned is either zero or negative.</exception>
        [Category("Data")]
        [DefaultValue(DefaultReceiveBufferSize)]
        [Description("The size of the buffer used by the client for receiving data from the server.")]
        public virtual int ReceiveBufferSize
        {
            get => m_receiveBufferSize;
            set
            {
                if (value < 1)
                    throw new ArgumentException("Value cannot be zero or negative");

                m_receiveBufferSize = value;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that indicates whether the client settings are to be saved to the config file.
        /// </summary>
        [Category("Persistence")]
        [DefaultValue(DefaultPersistSettings)]
        [Description("Indicates whether the client settings are to be saved to the config file.")]
        public bool PersistSettings { get; set; }

        /// <summary>
        /// Gets or sets the category under which the client settings are to be saved to the config file if the <see cref="PersistSettings"/> property is set to true.
        /// </summary>
        /// <exception cref="ArgumentNullException">The value being assigned is a null or empty string.</exception>
        [Category("Persistence")]
        [DefaultValue(DefaultSettingsCategory)]
        [Description("Category under which the client settings are to be saved to the config file if the PersistSettings property is set to true.")]
        public string SettingsCategory
        {
            get => m_settingsCategory;
            set
            {
                if (string.IsNullOrEmpty(value))
                    throw new ArgumentNullException(nameof(value));

                m_settingsCategory = value;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that indicates whether the client is currently enabled.
        /// </summary>
        /// <remarks>
        /// Setting <see cref="Enabled"/> to true will start connection cycle for the client if it
        /// is not connected, setting to false will disconnect the client if it is connected.
        /// </remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool Enabled
        {
            get => m_currentState == ClientState.Connected;
            set
            {
                if (value && !Enabled)
                    Connect();
                else if (!value && Enabled)
                    Disconnect();
            }
        }

        /// <summary>
        /// Gets a flag that indicates whether the object has been disposed.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsDisposed { get; private set; }

        /// <summary>
        /// Gets or sets the <see cref="Encoding"/> to be used for the text sent to the server.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual Encoding TextEncoding
        {
            get => m_textEncoding;
            set => m_textEncoding = value;
        }

        /// <summary>
        /// Gets the current <see cref="ClientState"/>.
        /// </summary>
        [Browsable(false)]
        public virtual ClientState CurrentState => m_currentState;

        /// <summary>
        /// Gets the <see cref="TransportProtocol"/> used by the client for the transportation of data with the server.
        /// </summary>
        [Browsable(false)]
        public virtual TransportProtocol TransportProtocol => m_transportProtocol;

        /// <summary>
        /// Gets the <see cref="Time"/> for which the client has been connected to the server.
        /// </summary>
        [Browsable(false)]
        public virtual Time ConnectionTime
        {
            get
            {
                Time clientConnectionTime = 0.0D;

                if (m_connectTime > 0)
                {
                    if (m_currentState == ClientState.Connected)
                    {
                        // Client is connected to the server.
                        clientConnectionTime = (DateTime.UtcNow.Ticks - m_connectTime).ToSeconds();
                    }
                    else
                    {
                        // Client is not connected to the server.
                        clientConnectionTime = (m_disconnectTime - m_connectTime).ToSeconds();
                    }
                }

                return clientConnectionTime;
            }
        }

        /// <summary>
        /// Gets or sets current read index for received data buffer incremented at each <see cref="Read"/> call.
        /// </summary>
        protected int ReadIndex { get; set; }

        /// <summary>
        /// Gets the unique identifier of the client.
        /// </summary>
        [Browsable(false)]
        public virtual string Name => m_settingsCategory;

        /// <summary>
        /// Gets the <see cref="TransportStatistics"/> for the client connection.
        /// </summary>
        public TransportStatistics Statistics { get; }

        /// <summary>
        /// Determines whether the base class should track statistics.
        /// </summary>
        protected virtual bool TrackStatistics => true;

        /// <summary>
        /// Gets the descriptive status of the client.
        /// </summary>
        [Browsable(false)]
        public virtual string Status
        {
            get
            {
                StringBuilder status = new StringBuilder();

                status.Append("              Client state: ");
                status.Append(m_currentState);
                status.AppendLine();
                status.Append("           Connection time: ");
                status.Append(ConnectionTime);
                status.AppendLine();
                status.Append("            Receive buffer: ");
                status.Append(m_receiveBufferSize.ToString());
                status.AppendLine();
                status.Append("        Transport protocol: ");
                status.Append(m_transportProtocol.ToString());
                status.AppendLine();
                status.Append("        Text encoding used: ");
                status.Append(m_textEncoding.EncodingName);
                status.AppendLine();

                return status.ToString();
            }
        }

        #endregion

        #region [ Methods ]

        /// <summary>
        /// When overridden in a derived class, reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset.
        /// </summary>
        /// <param name="buffer">Destination buffer used to hold copied bytes.</param>
        /// <param name="startIndex">0-based starting index into destination <paramref name="buffer"/> to begin writing data.</param>
        /// <param name="length">The number of bytes to read from current received data buffer and write into <paramref name="buffer"/>.</param><commit_msg>GSF.Communication: Fixed connection time display in client base status.<commit_after>﻿//******************************************************************************************************
//  ClientBase.cs - Gbtc
//
//  Copyright © 2015, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/MIT
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  06/01/2006 - <NAME>
//       Original version of source code generated.
//  09/06/2006 - <NAME>
//       Added bypass optimizations for high-speed client data access.
//  11/30/2007 - <NAME>
//       Modified the "design time" check in EndInit() method to use LicenseManager.UsageMode property
//       instead of DesignMode property as the former is more accurate than the latter.
//  02/19/2008 - <NAME>
//       Added code to detect and avoid redundant calls to Dispose().
//  09/29/2008 - <NAME>
//       Converted to C#.
//  06/18/2009 - <NAME>
//       Fixed the implementation of Enabled property.
//  07/02/2009 - <NAME>
//       Modified state altering properties to reconnect the client when changed.
//  07/08/2009 - <NAME>
//       Added WaitHandle return value from asynchronous connection.
//  07/15/2009 - <NAME>. Patel
//       Modified Connect() to wait for post-connection processing to complete.
//  07/17/2009 - <NAME>
//       Modified SharedSecret to be persisted as an encrypted value.
//  08/05/2009 - <NAME>
//       Edited Comments.
//  09/14/2009 - <NAME>
//       Added new header and license agreement.
//  11/29/2010 - <NAME>
//       Updated the implementation of Connect() method so it blocks correctly after updates made to 
//       ConnectAsync() method in the derived classes.
//  04/14/2011 - <NAME>
//       Updated to use new serialization methods in GSF.Serialization class.
//  12/02/2011 - <NAME>
//       Updated event data publication to provide "copy" of reusable buffer instead of original
//       buffer since you cannot assume how user will use the buffer (they may cache it).
//  12/29/2011 - <NAME>
//       Updated Status property to show ConnectionString information.
//  04/26/2012 - <NAME>
//       Updated Create() static method to apply settings from the connection string to the created 
//       client instance using reflection.
//  05/22/2015 - <NAME>
//       Added ZeroMQ to the create IClient options.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Reflection;
using System.Text;
using System.Threading;
using GSF.Configuration;
using GSF.Diagnostics;
using GSF.Units;

// ReSharper disable VirtualMemberCallInConstructor
namespace GSF.Communication
{
    /// <summary>
    /// Base class for a client involved in server-client communication.
    /// </summary>
    [ToolboxBitmap(typeof(ClientBase))]
    public abstract class ClientBase : Component, IClient, ISupportInitialize, IPersistSettings
    {
        #region [ Members ]

        // Constants

        /// <summary>
        /// Specifies the default value for the <see cref="MaxConnectionAttempts"/> property.
        /// </summary>
        public const int DefaultMaxConnectionAttempts = -1;

        /// <summary>
        /// Specifies the default value for the <see cref="SendBufferSize"/> property.
        /// </summary>
        public const int DefaultSendBufferSize = 32768;

        /// <summary>
        /// Specifies the default value for the <see cref="ReceiveBufferSize"/> property.
        /// </summary>
        public const int DefaultReceiveBufferSize = 32768;

        /// <summary>
        /// Specifies the default value for the <see cref="PersistSettings"/> property.
        /// </summary>
        public const bool DefaultPersistSettings = false;

        /// <summary>
        /// Specifies the default value for the <see cref="SettingsCategory"/> property.
        /// </summary>
        public const string DefaultSettingsCategory = "CommunicationClient";

        // Events

        /// <summary>
        /// Occurs when client is attempting connection to the server.
        /// </summary>
        [Category("Connection")]
        [Description("Occurs when client is attempting connection to the server.")]
        public event EventHandler ConnectionAttempt;

        /// <summary>
        /// Occurs when client connection to the server is established.
        /// </summary>
        [Category("Connection")]
        [Description("Occurs when client connection to the server is established.")]
        public event EventHandler ConnectionEstablished;

        /// <summary>
        /// Occurs when client connection to the server is terminated.
        /// </summary>
        [Category("Connection")]
        [Description("Occurs when client connection to the server is terminated")]
        public event EventHandler ConnectionTerminated;

        /// <summary>
        /// Occurs when an <see cref="Exception"/> is encountered during connection attempt to the server.
        /// </summary>
        [Category("Connection")]
        [Description("Occurs when an Exception is encountered during connection attempt to the server.")]
        public event EventHandler<EventArgs<Exception>> ConnectionException;

        /// <summary>
        /// Occurs when the client begins sending data to the server.
        /// </summary>
        [Category("Data")]
        [Description("Occurs when the client begins sending data to the server.")]
        public event EventHandler SendDataStart;

        /// <summary>
        /// Occurs when the client has successfully sent data to the server.
        /// </summary>
        [Category("Data")]
        [Description("Occurs when the client has successfully sent data to the server.")]
        public event EventHandler SendDataComplete;

        /// <summary>
        /// Occurs when an <see cref="Exception"/> is encountered when sending data to the server.
        /// </summary>
        /// <remarks>
        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> encountered when sending data to the server.
        /// </remarks>
        [Category("Data")]
        [Description("Occurs when an Exception is encountered when sending data to the server.")]
        public event EventHandler<EventArgs<Exception>> SendDataException;

        /// <summary>
        /// Occurs when unprocessed data has been received from the server.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This event can be used to receive a notification that server data has arrived. The <see cref="Read"/> method can then be used
        /// to copy data to an existing buffer. In many cases it will be optimal to use an existing buffer instead of subscribing to the
        /// <see cref="ReceiveDataComplete"/> event.
        /// </para>
        /// <para>
        /// <see cref="EventArgs{T}.Argument"/> is the number of bytes received in the buffer from the server.
        /// </para>
        /// </remarks>
        [Category("Data")]
        [Description("Occurs when unprocessed data has been received from the server.")]
        public event EventHandler<EventArgs<int>> ReceiveData;

        /// <summary>
        /// Occurs when data received from the server has been processed and is ready for consumption.
        /// </summary>
        /// <remarks>
        /// <see cref="EventArgs{T1,T2}.Argument1"/> is a new buffer containing post-processed data received from the server starting at index zero.<br/>
        /// <see cref="EventArgs{T1,T2}.Argument2"/> is the number of post-processed bytes received in the buffer from the server.
        /// </remarks>
        [Category("Data")]
        [Description("Occurs when data received from the server has been processed and is ready for consumption.")]
        public event EventHandler<EventArgs<byte[], int>> ReceiveDataComplete;

        /// <summary>
        /// Occurs when an <see cref="Exception"/> is encountered when receiving data from the server.
        /// </summary>
        /// <remarks>
        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> encountered when receiving data from the server.
        /// </remarks>
        [Category("Data")]
        [Description("Occurs when an Exception is encountered when receiving data from the server.")]
        public event EventHandler<EventArgs<Exception>> ReceiveDataException;

        /// <summary>
        /// Occurs when an <see cref="Exception"/> is encountered in a user-defined function via an event dispatch.
        /// </summary>
        /// <remarks>
        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> thrown by the user-defined function.
        /// </remarks>
        [Category("User")]
        [Description("Occurs when an Exception is encountered when calling a user-defined function.")]
        public event EventHandler<EventArgs<Exception>> UnhandledUserException;

        // Fields
        private string m_connectionString;
        private int m_maxConnectionAttempts;
        private int m_sendBufferSize;
        private int m_receiveBufferSize;
        private string m_settingsCategory;
        private Encoding m_textEncoding;
        private ClientState m_currentState;
        private readonly TransportProtocol m_transportProtocol;
        private Ticks m_connectTime;
        private Ticks m_disconnectTime;
        private ManualResetEvent m_connectHandle;
        private bool m_initialized;

        private readonly Action<int> m_updateBytesSent;
        private readonly Action<int> m_updateBytesReceived;

        #endregion

        #region [ Constructors ]

        /// <summary>
        /// Initializes a new instance of the client.
        /// </summary>
        protected ClientBase()
        {
            m_textEncoding = Encoding.ASCII;
            m_currentState = ClientState.Disconnected;
            m_maxConnectionAttempts = DefaultMaxConnectionAttempts;
            m_sendBufferSize = DefaultSendBufferSize;
            m_receiveBufferSize = DefaultReceiveBufferSize;
            PersistSettings = DefaultPersistSettings;
            m_settingsCategory = DefaultSettingsCategory;
            Statistics = new TransportStatistics();
            m_updateBytesSent = TrackStatistics ? UpdateBytesSent : new Action<int>(bytes => { });
            m_updateBytesReceived = TrackStatistics ? UpdateBytesReceived : new Action<int>(bytes => { });
        }

        /// <summary>
        /// Initializes a new instance of the client.
        /// </summary>
        /// <param name="transportProtocol">One of the <see cref="TransportProtocol"/> values.</param>
        /// <param name="connectionString">The data used by the client for connection to a server.</param>
        protected ClientBase(TransportProtocol transportProtocol, string connectionString) : this()
        {
            m_transportProtocol = transportProtocol;
            ConnectionString = connectionString;
        }

        #endregion

        #region [ Properties ]

        /// <summary>
        /// Gets the server URI.
        /// </summary>
        public abstract string ServerUri { get; }

        /// <summary>
        /// Gets or sets the data required by the client to connect to the server.
        /// </summary>
        [Category("Settings")]
        [Description("The data required by the client to connect to the server.")]
        public virtual string ConnectionString
        {
            get => m_connectionString;
            set
            {
                ValidateConnectionString(value);

                m_connectionString = value;
                ReConnect();
            }
        }

        /// <summary>
        /// Gets or sets the maximum number of times the client will attempt to connect to the server.
        /// </summary>
        /// <remarks>Set <see cref="MaxConnectionAttempts"/> to -1 for infinite connection attempts.</remarks>
        [Category("Settings")]
        [DefaultValue(DefaultMaxConnectionAttempts)]
        [Description("The maximum number of times the client will attempt to connect to the server. Set MaxConnectionAttempts to -1 for infinite connection attempts.")]
        public virtual int MaxConnectionAttempts
        {
            get => m_maxConnectionAttempts;
            set => m_maxConnectionAttempts = value < 1 ? -1 : value;
        }

        /// <summary>
        /// Gets or sets the size of the buffer used by the client for sending data to the server.
        /// </summary>
        /// <exception cref="ArgumentException">The value being assigned is either zero or negative.</exception>
        [Category("Data")]
        [DefaultValue(DefaultSendBufferSize)]
        [Description("The size of the buffer used by the client for receiving data from the server.")]
        public virtual int SendBufferSize
        {
            get => m_sendBufferSize;
            set
            {
                if (value < 1)
                    throw new ArgumentException("Value cannot be zero or negative");

                m_sendBufferSize = value;
            }
        }

        /// <summary>
        /// Gets or sets the size of the buffer used by the client for receiving data from the server.
        /// </summary>
        /// <exception cref="ArgumentException">The value being assigned is either zero or negative.</exception>
        [Category("Data")]
        [DefaultValue(DefaultReceiveBufferSize)]
        [Description("The size of the buffer used by the client for receiving data from the server.")]
        public virtual int ReceiveBufferSize
        {
            get => m_receiveBufferSize;
            set
            {
                if (value < 1)
                    throw new ArgumentException("Value cannot be zero or negative");

                m_receiveBufferSize = value;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that indicates whether the client settings are to be saved to the config file.
        /// </summary>
        [Category("Persistence")]
        [DefaultValue(DefaultPersistSettings)]
        [Description("Indicates whether the client settings are to be saved to the config file.")]
        public bool PersistSettings { get; set; }

        /// <summary>
        /// Gets or sets the category under which the client settings are to be saved to the config file if the <see cref="PersistSettings"/> property is set to true.
        /// </summary>
        /// <exception cref="ArgumentNullException">The value being assigned is a null or empty string.</exception>
        [Category("Persistence")]
        [DefaultValue(DefaultSettingsCategory)]
        [Description("Category under which the client settings are to be saved to the config file if the PersistSettings property is set to true.")]
        public string SettingsCategory
        {
            get => m_settingsCategory;
            set
            {
                if (string.IsNullOrEmpty(value))
                    throw new ArgumentNullException(nameof(value));

                m_settingsCategory = value;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that indicates whether the client is currently enabled.
        /// </summary>
        /// <remarks>
        /// Setting <see cref="Enabled"/> to true will start connection cycle for the client if it
        /// is not connected, setting to false will disconnect the client if it is connected.
        /// </remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool Enabled
        {
            get => m_currentState == ClientState.Connected;
            set
            {
                if (value && !Enabled)
                    Connect();
                else if (!value && Enabled)
                    Disconnect();
            }
        }

        /// <summary>
        /// Gets a flag that indicates whether the object has been disposed.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsDisposed { get; private set; }

        /// <summary>
        /// Gets or sets the <see cref="Encoding"/> to be used for the text sent to the server.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual Encoding TextEncoding
        {
            get => m_textEncoding;
            set => m_textEncoding = value;
        }

        /// <summary>
        /// Gets the current <see cref="ClientState"/>.
        /// </summary>
        [Browsable(false)]
        public virtual ClientState CurrentState => m_currentState;

        /// <summary>
        /// Gets the <see cref="TransportProtocol"/> used by the client for the transportation of data with the server.
        /// </summary>
        [Browsable(false)]
        public virtual TransportProtocol TransportProtocol => m_transportProtocol;

        /// <summary>
        /// Gets the <see cref="Time"/> for which the client has been connected to the server.
        /// </summary>
        [Browsable(false)]
        public virtual Time ConnectionTime
        {
            get
            {
                Time clientConnectionTime = 0.0D;

                if (m_connectTime > 0)
                {
                    if (m_currentState == ClientState.Connected)
                    {
                        // Client is connected to the server.
                        clientConnectionTime = (DateTime.UtcNow.Ticks - m_connectTime).ToSeconds();
                    }
                    else
                    {
                        // Client is not connected to the server.
                        clientConnectionTime = (m_disconnectTime - m_connectTime).ToSeconds();
                    }
                }

                return clientConnectionTime;
            }
        }

        /// <summary>
        /// Gets or sets current read index for received data buffer incremented at each <see cref="Read"/> call.
        /// </summary>
        protected int ReadIndex { get; set; }

        /// <summary>
        /// Gets the unique identifier of the client.
        /// </summary>
        [Browsable(false)]
        public virtual string Name => m_settingsCategory;

        /// <summary>
        /// Gets the <see cref="TransportStatistics"/> for the client connection.
        /// </summary>
        public TransportStatistics Statistics { get; }

        /// <summary>
        /// Determines whether the base class should track statistics.
        /// </summary>
        protected virtual bool TrackStatistics => true;

        /// <summary>
        /// Gets the descriptive status of the client.
        /// </summary>
        [Browsable(false)]
        public virtual string Status
        {
            get
            {
                StringBuilder status = new StringBuilder();

                status.Append("              Client state: ");
                status.Append(m_currentState);
                status.AppendLine();
                status.Append("           Connection time: ");
                status.Append(ConnectionTime.ToString(3));
                status.AppendLine();
                status.Append("            Receive buffer: ");
                status.Append(m_receiveBufferSize.ToString());
                status.AppendLine();
                status.Append("        Transport protocol: ");
                status.Append(m_transportProtocol.ToString());
                status.AppendLine();
                status.Append("        Text encoding used: ");
                status.Append(m_textEncoding.EncodingName);
                status.AppendLine();

                return status.ToString();
            }
        }

        #endregion

        #region [ Methods ]

        /// <summary>
        /// When overridden in a derived class, reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset.
        /// </summary>
        /// <param name="buffer">Destination buffer used to hold copied bytes.</param>
        /// <param name="startIndex">0-based starting index into destination <paramref name="buffer"/> to begin writing data.</param>
        /// <param name="length">The number of bytes to read from current received data buffer and write into <paramref name="buffer"/>.</param>
{'fraction_non_alphanumeric': 0.11162172249696743, 'fraction_numerical': 0.008187925725482877, 'mean_word_length': 2.024481444899111, 'pattern_counts': {'":': 0, '<': 363, '<?xml version=': 0, '>': 389, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '927319', 'n_tokens_mistral': 10184, 'n_tokens_neox': 9693, 'n_words': 3682}