/// <summary>
        /// Sends the outgoing message
        /// </summary>
        /// <param name="message">The message string</param>
        /// <param name="id">The message string</param>
        public async void SendMessage(string message, string id)
        {
            // encrypt the message with other user's key
            string encryptedMessage;
            {
                string key = (await SharedStuff.Database.Table <DatabaseHelper.Users>().Where(user => user.Username == Username)
                              .FirstAsync()).Key;
                encryptedMessage = BouncyCastleHelper.AesGcmEncrypt(message, key);
            }
            // create json
            string json = JsonConvert.SerializeObject(new JsonTypes.SendMessage
            {
                Type    = 0,
                Id      = id,
                Payload = new JsonTypes.SendMessagePayload
                {
                    To      = Username,
                    Message = encryptedMessage
                }
            });

            // send the json
            SharedStuff.Websocket.SendAsync(json, ok =>
            {
                SharedStuff.Database.InsertAsync(new DatabaseHelper.Messages
                {
                    Date      = DateTime.Now,
                    MyMessage = true,
                    Payload   = message,
                    Type      = 0,
                    Username  = Username
                }).Wait();
            });
            // add to main window
            Application.Current.Dispatcher.Invoke(delegate
            {
                var u     = MainChatsWindow.MessagesList.First(x => x.Username == Username);
                u.Message = message;
                u.IsLastMessageForUser = true;
            });
        }
        private async void OpenFileClicked(object sender, RoutedEventArgs e)
        {
            if (sender is Button btn)
            {
                if (btn.CommandParameter is string token)
                {
                    DatabaseHelper.Files f = new DatabaseHelper.Files();
                    try
                    {
                        f = await SharedStuff.Database.Table <DatabaseHelper.Files>()
                            .Where(file => file.Token == token).FirstAsync();

                        if (string.IsNullOrEmpty(f.Location))
                        {
                            throw new InvalidOperationException();
                        }
                        if (f.Location == " ") // null means the file is not downloaded. ' ' means it does not exists
                        {
                            throw new FileNotFoundException();
                        }
                        if (File.Exists(f.Location)) // check if the downloaded file exists
                        {
                            System.Diagnostics.Process.Start(f.Location);
                        }
                        else
                        {
                            await SharedStuff.Database.ExecuteAsync(
                                "UPDATE Files SET Location = ? WHERE Token = ?"
                                , " ", token);

                            throw new FileNotFoundException();
                        }
                    }
                    catch (FileNotFoundException)
                    {
                        // at first check if server has the file
                        var err = new ErrorDialogSample
                        {
                            ErrorText =
                            {
                                Text =
                                    "This file does not exists on your computer. Ask other user to send this file again."
                            },
                            ErrorTitle = { Text = "Cannot Open File" }
                        };
                        await DialogHost.Show(err, "ChatDialogHost" + Username);
                    }
                    catch (InvalidOperationException) // this token does not exists in database
                    {
                        // download this file
                        await Application.Current.Dispatcher.Invoke(async delegate
                        {
                            ProgressBar bar = ((StackPanel)((DockPanel)((Button)sender).Parent).Parent)
                                              .Children[1] as ProgressBar;
                            try
                            {
                                string downloadFileUrl =
                                    $"https://{Properties.Settings.Default.ServerAddress}/download?token={token}"; // token does not contain special characters so we are good
                                string destinationFilePath = Path.GetTempFileName();

                                using (var client =
                                           new HttpClientDownloadWithProgress(downloadFileUrl, destinationFilePath))
                                {
                                    client.ProgressChanged +=
                                        (totalFileSize, totalBytesDownloaded, progressPercentage) =>
                                    {
                                        bar.Value = progressPercentage ?? 0;
                                    };

                                    await client.StartDownload();
                                }

                                // get the key
                                string key = (await SharedStuff.Database.Table <DatabaseHelper.Users>()
                                              .Where(user => user.Username == Username).FirstAsync()).Key;
                                BouncyCastleHelper.AesGcmDecrypt(new FileInfo(destinationFilePath),
                                                                 new FileInfo(Path.Combine(SharedStuff.DownloadPath, f.Name)), key);
                                await SharedStuff.Database.ExecuteAsync(
                                    "UPDATE Files SET Location = ? WHERE Token = ?"
                                    , Path.Combine(SharedStuff.DownloadPath, f.Name), token);
                            }
                            catch (FileNotFoundException) // This error means that the files does not exists on server. (it's too old)
                            {
                                await SharedStuff.Database.ExecuteAsync(
                                    "UPDATE Files SET Location = ? WHERE Token = ?"
                                    , " ", token); // this will make the File.Exists always return false
                                MessagesList.First(msg => msg.Token == token).DownloadButtonIcon =
                                    PackIconKind.DownloadOff;
                                var err = new ErrorDialogSample
                                {
                                    ErrorText =
                                    {
                                        Text = "This file is too old and could not be downloaded. Ask other user to send this file again."
                                    },
                                    ErrorTitle = { Text = "Cannot Download File" }
                                };
                                await DialogHost.Show(err, "ChatDialogHost" + Username);
                            }
                            catch (Exception ex)
                            {
                                var err = new ErrorDialogSample
                                {
                                    ErrorText =
                                    {
                                        Text = ex.Message
                                    },
                                    ErrorTitle = { Text = "Cannot Download File" }
                                };
                                await DialogHost.Show(err, "ChatDialogHost" + Username);
                            }
                            finally
                            {
                                bar.Value = 101;
                            }
                        });
                    }
                }
            }
        }
        /// <summary>
        /// Encrypts and uploads a file to server
        /// </summary>
        /// <param name="guid">The ID of message</param>
        public static async void UploadFile(string guid)
        {
            // at first encrypt the file
            var    encryptedFile = new FileInfo(Path.GetTempFileName());
            string username      = PendingMessages[guid].With;
            string key           = (await Database.Table <DatabaseHelper.Users>()
                                    .Where(user => user.Username == username).FirstAsync()).Key;
            await Task.Run(() =>
                           BouncyCastleHelper.AesGcmEncrypt(new FileInfo(PendingMessages[guid].FilePath),
                                                            encryptedFile, key));

            // now upload the file
            try
            {
                using (HttpClient localClient = new HttpClient())
                    using (var multiForm = new MultipartFormDataContent())
                    {
                        localClient.Timeout = TimeSpan.FromMinutes(15); // You may need this if you are uploading a big file

                        var file = new ProgressableStreamContent(
                            new StreamContent(encryptedFile.OpenRead())
                            , (sent, total) => { });
                        multiForm.Add(file, "document", Path.GetFileName(PendingMessages[guid].FilePath)); // Add the file

                        localClient.DefaultRequestHeaders.Add("token", PendingMessages[guid].Token);

                        var response =
                            await localClient.PostAsync("https://" + Properties.Settings.Default.ServerAddress + "/upload",
                                                        multiForm);

                        if (response.StatusCode == HttpStatusCode.OK)
                        {
                            string res = await response.Content.ReadAsStringAsync();

                            Console.WriteLine(res);
                            // parse result
                            var jsonRes = JsonConvert.DeserializeObject <JsonTypes.ServerStatus>(res);
                            if (!jsonRes.Ok)
                            {
                                throw new WebException("Server returned an error: " + response.StatusCode);
                            }
                            // submit the file
                            Websocket.Send(JsonConvert.SerializeObject(new JsonTypes.SendMessage
                            {
                                Id      = guid,
                                Type    = 1,
                                Payload = new JsonTypes.SendMessagePayload
                                {
                                    To      = username,
                                    Message = JsonConvert.SerializeObject(new JsonTypes.FilePayload
                                    {
                                        FileName = Path.GetFileName(PendingMessages[guid].FilePath),
                                        Token    = PendingMessages[guid].Token,
                                        FileSize = ByteSizeToString(new FileInfo(PendingMessages[guid].FilePath).Length)
                                    })
                                }
                            }));
                            // update UI
                            PendingMessages[guid].Sent     = 0;
                            PendingMessages[guid].Progress = 101;
                            // update database
                            await Database.RunInTransactionAsync(tran =>
                            {
                                tran.Insert(new DatabaseHelper.Messages
                                {
                                    Date      = DateTime.Now,
                                    MyMessage = true,
                                    Payload   = PendingMessages[guid].Token,
                                    Type      = 1,
                                    Username  = PendingMessages[guid].With
                                });
                                tran.Insert(new DatabaseHelper.Files
                                {
                                    Token    = PendingMessages[guid].Token,
                                    Location = PendingMessages[guid].FilePath,
                                    Name     = Path.GetFileName(PendingMessages[guid].FilePath)
                                });
                            });
                        }
                        else
                        {
                            throw new WebException("HTTP status code is not ok: " + response.StatusCode);
                        }
                    }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Cannot upload file: " + ex);
                PendingMessages[guid].Sent     = 2;   // show the user that the upload has been failed
                PendingMessages[guid].Progress = 101; // show the user that the upload has been failed
            }
            finally
            {
                encryptedFile.Delete();
                PendingMessages.Remove(guid);
            }
        }
        private async void WS_OnMessage(object sender, MessageEventArgs e)
        {
            // at first try to parse the message as Server status.
            try
            {
                var status = JsonConvert.DeserializeObject <JsonTypes.MessageStatus>(e.Data);
                if (status.Id != null) // a real message status
                {
                    if (!status.Ok)
                    {
                        Console.WriteLine("Error on message " + status.Id + ": " + status.Message);
                    }
                    if (status.Message != "sent") // this is a file token update
                    {
                        SharedStuff.PendingMessages[status.Id].Token = status.Message;
                        // now start upload
                        SharedStuff.UploadFile(status.Id);
                    }
                    else
                    {
                        SharedStuff.PendingMessages[status.Id].Sent = status.Ok ? (byte)0 : (byte)2;
                        // Do not remove files from here; They are removed at "finally" block of SharedStuff.UploadFile
                        if (SharedStuff.PendingMessages[status.Id].Type != 1)
                        {
                            SharedStuff.PendingMessages.Remove(status.Id);
                        }
                    }
                    return;
                }

                if (status.Message != null && !status.Ok)
                {
                    // TODO: HANDLE THIS SHIT
                    return;
                }
            }
            catch (Exception)
            {
                // ignored : the type might be something else
            }
            // try ClientUpdateTypeStruct
            await _mu.WaitAsync(); // get the data in order that server sends them

            string nameSafe = "";

            try
            {
                var msg     = JsonConvert.DeserializeObject <JsonTypes.Updates>(e.Data);
                var keyList = await SharedStuff.Database.Table <DatabaseHelper.Users>().Where(k => k.Username == msg.Payload.From)
                              .ToArrayAsync(); // get the key of the user

                string key;
                if (keyList.Length == 0) // Key agreement
                {
                    var data = await SharedStuff.GetUserData(msg.Payload.From);

                    key = Convert.ToBase64String(SharedStuff.Curve.calculateAgreement(
                                                     Convert.FromBase64String(_savedData.PrivateKey),
                                                     Convert.FromBase64String(data.PublicKey)));
                    await SharedStuff.Database.InsertAsync(new DatabaseHelper.Users // save the key in database
                    {
                        Username = data.Username,
                        Name     = data.Name,
                        Key      = key,
                    });

                    nameSafe = data.Name;
                }
                else // get the key from database; There is only one row because Username is unique
                {
                    key = keyList[0].Key;
                }

                string toShowOnMainMenu           = "";
                JsonTypes.FilePayload filePayload = new JsonTypes.FilePayload();
                switch (msg.Type)
                {
                case 0:     // Text message
                    // decrypt the message or assign the file token
                    try
                    {
                        string message = BouncyCastleHelper.AesGcmDecrypt(msg.Payload.Message, key);
                        await SharedStuff.Database.InsertAsync(new DatabaseHelper.Messages
                        {
                            Username = msg.Payload.From,
                            Type     = msg.Type,
                            Date     = msg.Payload.Date,
                            Payload  = message
                        });

                        toShowOnMainMenu = message;
                    }
                    catch (Exception)
                    {
                        // TODO: RE-REQUEST public key
                    }
                    break;

                case 1:     // File message
                    filePayload = JsonConvert.DeserializeObject <JsonTypes.FilePayload>(msg.Payload.Message);
                    await SharedStuff.Database.RunInTransactionAsync(trans =>
                    {
                        trans.Insert(new DatabaseHelper.Messages
                        {
                            Username = msg.Payload.From,
                            Type     = msg.Type,
                            Date     = msg.Payload.Date,
                            Payload  = filePayload.Token
                        });
                        trans.Insert(new DatabaseHelper.Files
                        {
                            Token = filePayload.Token,
                            Name  = filePayload.FileName
                        });
                    });

                    toShowOnMainMenu = filePayload.FileName;
                    break;
                }

                // save the value
                bool inserted = false, open = OpenWindowsList.ContainsKey(msg.Payload.From);
                int  index = -1;
                try
                {
                    index = MessagesList.IndexOf(MessagesList.First(x =>
                                                                    x.Username == msg.Payload.From)); // get index of the UI
                    if (index == -1)
                    {
                        throw new Exception();
                    }
                }
                catch (Exception)
                {
                    string name = nameSafe;
                    if (name == "")
                    {
                        name = (await SharedStuff.GetUserData(msg.Payload.From)).Name;
                    }
                    Application.Current.Dispatcher.Invoke(delegate
                    {
                        MessagesList.Insert(0, new MainMessagesNotify
                        {
                            FullDate             = msg.Payload.Date,
                            Message              = toShowOnMainMenu,
                            IsLastMessageForUser = false,
                            Name        = name,
                            NewMessages = 1,
                            Username    = msg.Payload.From
                        });
                    });
                    inserted = true;
                }
                if (index == -1)
                {
                    return;
                }

                if (!open) // increase unread messages
                {
                    await SharedStuff.Database.ExecuteAsync("UPDATE Users SET UnreadMessages = ? WHERE Username = ?",
                                                            MessagesList[index].NewMessages + 1, msg.Payload.From);
                }
                if (!inserted)
                {
                    Application.Current.Dispatcher.Invoke(delegate
                    {
                        // Show the user the update
                        MessagesList.Move(index, 0);
                        MessagesList[0].FullDate             = msg.Payload.Date;
                        MessagesList[0].Message              = toShowOnMainMenu;
                        MessagesList[0].IsLastMessageForUser = false;
                        if (!open)
                        {
                            MessagesList[0].NewMessages++;
                        }
                    });
                }

                // update the open windows
                if (open)
                {
                    if (msg.Type == 0) // add text messages
                    {
                        OpenWindowsList[msg.Payload.From].AddMessageText(toShowOnMainMenu, msg.Payload.Date);
                    }
                    else if (msg.Type == 1) // add files
                    {
                        OpenWindowsList[msg.Payload.From].AddMessageFile(filePayload.Token, filePayload.FileName, msg.Payload.Date);
                    }
                }
            }
            catch (Exception ex)
            {
                // TODO: HANDLE THIS SHIT
            }
            finally
            {
                _mu.Release();
            }
        }
        private async void LoginButtonClicked(object sender, RoutedEventArgs e)
        {
            LoadingDialogSample loadingDialog = new LoadingDialogSample // simple dialog with a spinning indicator
            {
                LoadingTextText = { Text = "Connecting to server..." } // set the text of the dialog
            };
            Exception error = null;
            await DialogHost.Show(loadingDialog, "LoginDialogHost", delegate(object sender1, DialogOpenedEventArgs args) // show the dialog
            {
                string serverAddress = ServerUrlTxt.Text, username = UsernameTxt.Text, password = PasswordTxt.Password;
                bool trustEveryThing = TrustSslCheckBox.IsChecked.HasValue && TrustSslCheckBox.IsChecked.Value;
                Task.Factory.StartNew(async() =>  // connect to server from another thread
                {
                    // let's see to trust all certificates or not
                    if (trustEveryThing)
                    {
                        ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true;
                    }
                    else
                    {
                        ServicePointManager.ServerCertificateValidationCallback = null;
                    }
                    // at first get server's public RSA key
                    try
                    {
                        if (SharedStuff.ServerPublicKey == null)
                        {
                            using (var wc = new WebClient())
                            {
                                string key = wc.DownloadString("https://" + serverAddress + "/publicKey");
                                SharedStuff.ServerPublicKey = BouncyCastleHelper.ReadAsymmetricKeyParameter(key);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        error = new Exception("Can't get public key", ex);
                        // close dialog on main thread (because it is not running from main thread)
                        await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => args.Session.Close()));
                        return;
                    }

                    // now login the user
                    string toSave;
                    try
                    {
                        // at first generate a ecdh curve25519 key pair
                        var curveKeys = SharedStuff.Curve.generateKeyPair();
                        // encrypt the password with server's public key
                        string rsaPassword = Convert.ToBase64String(BouncyCastleHelper.RsaEncrypt(SharedStuff.ServerPublicKey, password));
                        // build the request
                        var postValues = new FormUrlEncodedContent(new[]
                        {
                            new KeyValuePair <string, string>("username", username),
                            new KeyValuePair <string, string>("password", rsaPassword),
                            new KeyValuePair <string, string>("key", Convert.ToBase64String(curveKeys.getPublicKey())),
                        });
                        var result           = await SharedStuff.Client.PostAsync("https://" + serverAddress + "/users/registerClient", postValues);
                        string resultContent = await result.Content.ReadAsStringAsync();
                        // parse the server result
                        var jsonRes = JsonConvert.DeserializeObject <JsonTypes.ServerStatus>(resultContent);
                        if (!jsonRes.Ok)
                        {
                            throw new Exception("Server returned an error: " + jsonRes.Message);
                        }
                        // save the login details
                        toSave = JsonConvert.SerializeObject(new JsonTypes.SavedData
                        {
                            Username   = username,
                            Password   = password,
                            PublicKey  = Convert.ToBase64String(curveKeys.getPublicKey()),
                            PrivateKey = Convert.ToBase64String(curveKeys.getPrivateKey())
                        });
                        Properties.Settings.Default.Name          = jsonRes.Message;
                        Properties.Settings.Default.ServerAddress = serverAddress;
                    }
                    catch (Exception ex)
                    {
                        error = new Exception("Can't login you", ex);
                        // close dialog on main thread (because it is not running from main thread)
                        await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => args.Session.Close()));
                        return;
                    }

                    // save the data (we had a successful login)
                    try
                    {
                        byte[] encrypted = ProtectedData.Protect(Encoding.UTF8.GetBytes(toSave), null, DataProtectionScope.CurrentUser); // encrypt the data
                        Properties.Settings.Default.LoginData       = Convert.ToBase64String(encrypted);                                 // save data to application settings
                        Properties.Settings.Default.TrustInvalidSSL = trustEveryThing;
                        Properties.Settings.Default.Save();
                    }
                    catch (Exception ex)
                    {
                        error = new Exception("Cannot save data", ex);
                        // TODO: logout the user if this fails
                        // close dialog on main thread (because it is not running from main thread)
                        await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => args.Session.Close()));
                        return;
                    }

                    // close dialog on main thread (because it is not running from main thread)
                    await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => args.Session.Close()));
                });
            });

            // check for errors
            if (error != null)
            {
                ErrorDialogSample errorDialog = new ErrorDialogSample
                {
                    ErrorTitle = { Text = error.Message },
                    ErrorText  = { Text = error.InnerException.Message } // Inner exception is never empty
                };
                await DialogHost.Show(errorDialog, "LoginDialogHost");
            }
            else // login the user
            {
                Hide();
                new MainChatsWindow().Show();
            }
        }