public ClientWebSocket(System.String URL, System.TimeSpan PingInterval, System.Boolean AutomaticReconnection)
        {
            this.URL          = URL;
            this.PingInterval = PingInterval;
            if ((this.PingInterval != System.TimeSpan.Zero) && (this.PingInterval.TotalSeconds < 1.0D))
            {
                throw new System.Exception("Invalid ping interval. The minimum value is 1 second.");
            }

            this.AutomaticReconnection = AutomaticReconnection;
            this.Headers    = new System.Collections.Generic.Dictionary <System.String, System.String>();
            this._State     = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Disconnected;
            this._LastState = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Disconnected;
            this._Latency   = 0;
        }
        public async System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken CancellationToken = default)
        {
            this._ReconnectionTimer?.Change(System.Threading.Timeout.Infinite, 0);

            switch (this.WebSocket.State)
            {
            case System.Net.WebSockets.WebSocketState.Open:
            {
                await this.WebSocket.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "", CancellationToken);

                this.State = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Disconnected;
                break;
            }

            default:
                break;
            }
        }
        private void Reconnect(System.Object State)
        {
            if (this.WebSocket.State == System.Net.WebSockets.WebSocketState.Open)
            {
                System.String Message = $"{{\"ping\":{System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}}}";

                /*
                 #if DEBUG
                 * System.Console.WriteLine(Message); // Debug Ping Messages
                 #endif
                 */
                this.WebSocket.SendAsync(new System.ArraySegment <System.Byte>(System.Text.Encoding.UTF8.GetBytes(Message)), System.Net.WebSockets.WebSocketMessageType.Text, true, System.Threading.CancellationToken.None).ConfigureAwait(false);
                return;
            }

            if (this.WebSocket.State == System.Net.WebSockets.WebSocketState.Connecting)
            {
                return;
            }

            if (this._LastState != this._State)
            {
                this.State = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Disconnected;
                this.Closed?.Invoke(this.WebSocket.CloseStatus == System.Net.WebSockets.WebSocketCloseStatus.NormalClosure || this.WebSocket.CloseStatusDescription == null ? null : new System.Exception(this.WebSocket.CloseStatusDescription));
            }

            if ((!(this.AutomaticReconnection)) || (this.WebSocket.CloseStatus == System.Net.WebSockets.WebSocketCloseStatus.NormalClosure))
            {
                return;
            }

            this.WebSocket.Abort();
            this.WebSocket.Dispose();
            this.WebSocket = null;
            try { this.StartAsync().ConfigureAwait(false); } catch { }
        }
        public async System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken CancellationToken = default)
        {
            if (this.WebSocket == null)
            {
                this.WebSocket = new System.Net.WebSockets.ClientWebSocket();
                foreach (System.Collections.Generic.KeyValuePair <System.String, System.String> Header in this.Headers)
                {
                    WebSocket.Options.SetRequestHeader(Header.Key, Header.Value);
                }

                if (this.PingInterval != System.TimeSpan.Zero)
                {
                    WebSocket.Options.SetRequestHeader("pingInterval", this.PingInterval.TotalMilliseconds.ToString());
                }
            }

            switch (this.WebSocket.State)
            {
            case System.Net.WebSockets.WebSocketState.Connecting:
            case System.Net.WebSockets.WebSocketState.Open:
                return;

            default:
                break;
            }

            if (!(this._IsReconnection))
            {
                this.State = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Connecting;
            }
            else
            {
                this.Reconnecting?.Invoke(null);
                this.State = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Reconnecting;
            }

            try
            {
                await this.WebSocket.ConnectAsync(new System.Uri(this.URL), CancellationToken);
            }
            catch
            {
                this.State = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Disconnected;
                throw;
            }

            if (this.WebSocket.State != System.Net.WebSockets.WebSocketState.Open)
            {
                this.State = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Disconnected;
                return;
            }

            this.State = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Connected;
            if (this._IsReconnection)
            {
                this.Reconnected?.Invoke(null);
            }

            _ = System.Threading.Tasks.Task.Run(async() =>
            {
                const System.Int16 BufferSize = 512;
                System.Net.WebSockets.WebSocketReceiveResult Result;
                do
                {
                    System.Collections.Generic.IEnumerable <System.Byte> Data = new System.Byte[] { };
                    do
                    {
                        System.Byte[] Buffer = new System.Byte[BufferSize];
                        Result = await this.WebSocket.ReceiveAsync(new System.ArraySegment <System.Byte>(Buffer), System.Threading.CancellationToken.None);
                        if (Result.MessageType == System.Net.WebSockets.WebSocketMessageType.Text)
                        {
                            Data = Data.Concat(Buffer);
                        }
                    }while (!(Result.EndOfMessage));

                    if ((Result.MessageType == System.Net.WebSockets.WebSocketMessageType.Text) && (this.ReceiveMessageAction != null))
                    {
                        System.String Message = System.Text.Encoding.UTF8.GetString(Data.SkipLast(BufferSize - Result.Count).ToArray());

                        if (!(Message.StartsWith("{\"pong\":")))
                        {
                            this.ReceiveMessageAction?.Invoke(Message);
                        }
                        else
                        {
                            System.Int64 ServerUnixTime = Message.ToJsonElement().GetInt64("pong");
                            this._Latency = ServerUnixTime > 0 ? System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - ServerUnixTime : 0;

                            /*
                             #if DEBUG
                             * System.Console.WriteLine(Message); // Debug Pong Messages
                             #endif
                             */
                        }
                    }
                    else if (Result.MessageType == System.Net.WebSockets.WebSocketMessageType.Close)
                    {
                        this.State = SoftmakeAll.SDK.Communication.ClientWebSocket.ConnectionStates.Disconnected;
                        this.Closed?.Invoke(Result.CloseStatus == System.Net.WebSockets.WebSocketCloseStatus.NormalClosure || Result.CloseStatusDescription == null ? null : new System.Exception(Result.CloseStatusDescription));
                    }
                }while (this.WebSocket.State == System.Net.WebSockets.WebSocketState.Open);
            });

            if ((!(this._IsReconnection)) && (this.PingInterval != System.TimeSpan.Zero))
            {
                this._ReconnectionTimer = new System.Threading.Timer(this.Reconnect, null, System.TimeSpan.Zero, this.PingInterval);
            }

            this._IsReconnection = true;
        }