private void LoadTweets()
        {
            gridTweets.Dispatcher.BeginInvoke(new Action(delegate()
            {
                gridTweets.ItemsSource        = null;
                gridOverlayLoading.Visibility = System.Windows.Visibility.Visible;
                gridContainer.IsEnabled       = false;
            }));

            tweetsCollectionView = null;
            notDeletedTweets     = new ObservableRangeCollection <tweetTJson>();

            List <JsFile> jsFiles = Application.Current.Properties["jsFiles"] as List <JsFile>;

            if (jsFiles == null)
            {
                //TODO error message here
                return;
            }

            foreach (JsFile jsFile in jsFiles)
            {
                tweets.AddRange(GetTweetsFromFile(jsFile));
            }

            txtFeedback.Dispatcher.BeginInvoke(new Action(delegate()
            {
                txtFeedback.Text += "\nDone!";
            }));

            tweetsCollectionView = CollectionViewSource.GetDefaultView(tweets);

            gridTweets.Dispatcher.BeginInvoke(new Action(delegate()
            {
                gridTweets.ItemsSource        = tweetsCollectionView;
                gridContainer.IsEnabled       = true;
                txtTotalTweetsNB.Text         = String.Format("(Total tweets: {0})", tweets.Count);
                gridOverlayLoading.Visibility = System.Windows.Visibility.Hidden;
            }));
        }
        void StartTwitterErase(object nbParallelConnectionsObj)
        {
            int nbParallelConnections = (int)nbParallelConnectionsObj;

            TwitterContext ctx = (TwitterContext)Application.Current.Properties["context"];

            //No need to synchronize here, all tasks are (supposed?) not started yet.
            nbTweetsToErase = tweets.Where(t => t.ToErase == true || !String.IsNullOrEmpty(t.Status)).Count();

#if !DEBUG_TEST
            if (ctx == null)
            {
                MessageBox.Show("Error loading twitter authentication info; please try again", "Twitter Archive Eraser", MessageBoxButton.OK, MessageBoxImage.Error);
                isErasing = false;
                EnableControls();
                return;
            }
#endif

#if DEBUG_TEST
            sleepFakeWaitMilliseconds = 5000 / nbParallelConnections;
#endif

            Task[] tasks = new Task[nbParallelConnections];

            for (int i = 0; i < nbParallelConnections; i++)
            {
                tasks[i] = Task.Factory.StartNew(() => EraseTweetsAction(ctx, cancellationSource.Token));
            }

            try
            {
                Task.WaitAll(tasks);
                EnableControls();
            }
            catch (Exception e)
            {
                nextTweetID = 0;
            }

            isErasing = false;  // Done erasing

            if (nbTweetsDeleted >= (nbTweetsToErase - nbParallelConnections))
            {
                progressBar.Dispatcher.BeginInvoke(new Action(delegate()
                {
                    progressBar.Value = 100;
                    txtPrcnt.Text     = "100%";

                    EnableControls();
                    nextTweetID     = 0;
                    nbTweetsDeleted = 0;
                    nbTweetsToErase = 0;

                    if (notDeletedTweets.Count == 0)
                    {
                        Application.Current.Properties["nbTeetsDeleted"] = tweets.Where(t => String.Equals(t.Status, STATUS_DELETED)).Count();
                        SendTweet sendTweetWindow = new SendTweet();
                        sendTweetWindow.ShowDialog();
                    }
                    else
                    {
                        string jsonTweets = JsonConvert.SerializeObject(notDeletedTweets);
                        File.WriteAllText(notDeletedTweetsFilename, jsonTweets);

                        if (MessageBox.Show(notDeletedTweets.Count + " tweets were not deleted!\n" +
                                            "Do you want to retry deleting these tweets again?\n\n" +
                                            "You can try deleting these tweets later by loading them in Twitter Archive Eraser from the following file:\n\n" +
                                            notDeletedTweetsFilename + "\n\n" +
                                            "Select 'Yes' to retry now, or 'No' to retry later",
                                            "Twitter Archive Eraser",
                                            MessageBoxButton.YesNo,
                                            MessageBoxImage.Exclamation) == MessageBoxResult.Yes)
                        {
                            WebUtils.ReportStats((string)Application.Current.Properties["userName"],
                                                 (string)Application.Current.Properties["sessionGUID"],
                                                 tweets.Count,
                                                 tweets.Where(t => String.Equals(t.Status, STATUS_DELETED)).Count(),
                                                 tweets.Where(t => String.Equals(t.Status, STATUS_NOT_FOUND)).Count(),
                                                 tweets.Where(t => String.Equals(t.Status, STATUS_ERROR)).Count(),
                                                 tweets.Where(t => String.Equals(t.Status, STATUS_NOT_ALLOWED)).Count(),
                                                 true,
                                                 nbParallelConnections,
                                                 filters,
                                                 (DateTime.Now - startTime).TotalSeconds);

                            tweets = new ObservableRangeCollection <Tweet>();
                            tweets.AddRange(notDeletedTweets.Select(t => new Tweet()
                            {
                                Text      = t.text,
                                ID        = t.id_str,
                                IsRetweet = t.retweeted_status != null ? "    ✔" : "",
                                Status    = "",
                                ToErase   = true,
                                Date      = ParseDateTime(t.created_at)
                            }));

                            // We pass an empty list of jsFiles to reuse code from DeleteTweets_Loaded
                            // Nothing of clean code I know!
                            // Sometimes I have hard times sleeping because I know such things is released to the wild
                            Application.Current.Properties["jsFiles"] = new List <JsFile>();
                            DeleteTweets_Loaded(null, null);
                        }
                        else
                        {
                            Application.Current.Properties["nbTeetsDeleted"] = tweets.Where(t => String.Equals(t.Status, STATUS_DELETED)).Count();
                            SendTweet sendTweetWindow = new SendTweet();
                            sendTweetWindow.ShowDialog();
                        }
                    }
                }));
            }
        }
        void StartTwitterErase(int nbParallelConnections)
        {
            TwitterContext ctx = appSettings.Context;

            //No need to synchronize here, no tasks were started yet.
            nbTweetsToErase         = mTweetsCollection.Where(t => t.ToErase == true && string.IsNullOrEmpty(t.Status) /* did not get deleted previously */).Count();
            nbOutstandingOperations = nbTweetsToErase;
            nextTweetID             = 0;

#if !DEBUG_TEST
            if (ctx == null)
            {
                MessageBox.Show("Error loading twitter authentication info; please try again", "Twitter Archive Eraser", MessageBoxButton.OK, MessageBoxImage.Error);
                isCurrentlyErasing = false;
                SetStoppedDeletionState();
                return;
            }
#endif

#if DEBUG_TEST
            sleepFakeWaitMilliseconds = 1000;
#endif

            Task[] tasks = new Task[nbParallelConnections];

            for (int i = 0; i < nbParallelConnections; i++)
            {
                tasks[i] = Task.Factory.StartNew(() => EraseTweetsAction(ctx, globalCancellationSource.Token));
            }

            try
            {
                Task.WaitAll(tasks);
            }
            catch (Exception e)
            {
                nextTweetID = 0;
                RunOnUIThread(delegate()
                {
                    MessageBox.Show("Error: " + e.Message);
                });
            }
            finally
            {
                SetStoppedDeletionState();

                // This is called after all tasks are canceled, should renew cacelation token
                globalCancellationSource = new CancellationTokenSource();
            }

            isCurrentlyErasing = false;  // Done erasing

            //termination condition
            if (nbOutstandingOperations == 0) // nbTweetsDeleted >= (nbTweetsToErase - nbParallelConnections))
            {
                RunOnUIThread(delegate()
                {
                    progressBar.Value = 100;
                    txtPrcnt.Text     = "100%";

                    SetStoppedDeletionState();
                    nextTweetID     = 0;
                    nbTweetsToErase = 0;

                    var lastTweet = mTweetsCollection.Where(t => !string.IsNullOrEmpty(t.Status)).LastOrDefault();
                    if (lastTweet != null)
                    {
                        gridTweets.BringItemIntoView(lastTweet);
                    }

                    appSettings.TotalRunningMillisec += runningTime.ElapsedMilliseconds;

                    if (notDeletedTweets.Count == 0)
                    {
                        ShowShareTweetDialog(mTweetsCollection.Where(t => String.Equals(t.Status, STATUS_DELETED)).Count());
                    }
                    else
                    {
                        string jsonTweets = JsonConvert.SerializeObject(notDeletedTweets);
                        File.WriteAllText(notDeletedTweetsFilename, jsonTweets);

                        if (MessageBox.Show(notDeletedTweets.Count + " tweets were not deleted!\n" +
                                            "Do you want to retry deleting these tweets again?\n\n" +
                                            "You can try deleting these tweets later by loading them in Twitter Archive Eraser from the following file:\n\n" +
                                            notDeletedTweetsFilename + "\n\n" +
                                            "Select 'Yes' to retry now, or 'No' to retry later",
                                            "Twitter Archive Eraser",
                                            MessageBoxButton.YesNo,
                                            MessageBoxImage.Exclamation) == MessageBoxResult.Yes)
                        {
                            WebUtils.ReportStats(appSettings.Username,
                                                 appSettings.SessionId.ToString(),
                                                 appSettings.EraseType.ToString(),
                                                 mTweetsCollection.Count,
                                                 mTweetsCollection.Where(t => String.Equals(t.Status, STATUS_DELETED)).Count(),
                                                 mTweetsCollection.Where(t => String.Equals(t.Status, STATUS_NOT_FOUND)).Count(),
                                                 mTweetsCollection.Where(t => String.Equals(t.Status, STATUS_ERROR)).Count(),
                                                 mTweetsCollection.Where(t => String.Equals(t.Status, STATUS_NOT_ALLOWED)).Count(),
                                                 true,
                                                 nbParallelConnections,
                                                 filters,
                                                 (DateTime.Now - startTime).TotalSeconds);

                            gridTweets.ItemsSource = null;
                            gridTweets.Items.Refresh();

                            tweetsCollectionView = null;

                            mTweetsCollection = new ObservableRangeCollection <Tweet>();

                            mTweetsCollection.AddRange(notDeletedTweets.Select(t => new Tweet()
                            {
                                Text    = t.text,
                                ID      = t.id_str,
                                Type    = t.retweeted_status != null ? TweetType.Retweet : TweetType.Tweet,
                                Status  = "",
                                ToErase = true,
                                Date    = Helpers.ParseDateTime(t.created_at)
                            }));

                            areTweetsFetchedThroughAPI = true;

                            notDeletedTweets.Clear();

                            // We pass an empty list of jsFiles to reuse code from DeleteTweets_Loaded
                            // Nothing of clean code I know!
                            // Sometimes I have hard times sleeping because I know such things are released in the wild
                            appSettings.JsFiles = new List <JsFile>();
                            DeleteTweets_Loaded(null, null);
                        }
                        else
                        {
                            ShowShareTweetDialog(mTweetsCollection.Where(t => String.Equals(t.Status, STATUS_DELETED)).Count());
                        }
                    }
                });
            }
        }