// TODO: Abort early when bsaber.com is down (check if all items in block failed?)
        // TODO: Make cancellationToken actually do something.
        /// <summary>
        /// Gets all songs from the feed defined by the provided settings.
        /// </summary>
        /// <param name="settings"></param>
        /// <param name="cancellationToken"></param>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="_settings"/> is null.</exception>
        /// <exception cref="InvalidCastException">Thrown when the passed IFeedSettings isn't a BeastSaberFeedSettings.</exception>
        /// <exception cref="OperationCanceledException"></exception>
        /// <returns></returns>
        public async override Task <FeedResult> GetSongsFromFeedAsync(IFeedSettings settings, IProgress <ReaderProgress> progress, CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(FeedResult.CancelledResult);
            }
            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings), "settings cannot be null for BeastSaberReader.GetSongsFromFeedAsync.");
            }
            Dictionary <string, ScrapedSong> retDict = new Dictionary <string, ScrapedSong>();

            if (!(settings is BeastSaberFeedSettings _settings))
            {
                throw new InvalidCastException(INVALIDFEEDSETTINGSMESSAGE);
            }
            if (_settings.Feed != BeastSaberFeedName.CuratorRecommended && string.IsNullOrEmpty(_settings.Username))
            {
                _settings.Username = Username;
            }
            BeastSaberFeed feed = new BeastSaberFeed(_settings)
            {
                StoreRawData = StoreRawData
            };

            try
            {
                feed.EnsureValidSettings();
            }
            catch (InvalidFeedSettingsException ex)
            {
                return(new FeedResult(null, null, ex, FeedResultError.Error));
            }
            int  pageIndex    = settings.StartingPage;
            int  maxPages     = _settings.MaxPages;
            int  pagesChecked = 0;
            bool useMaxSongs  = _settings.MaxSongs != 0;
            bool useMaxPages  = maxPages != 0;

            if (useMaxPages && pageIndex > 1)
            {
                maxPages = maxPages + pageIndex - 1;
            }
            var ProcessPageBlock = new TransformBlock <int, PageReadResult>(async pageNum =>
            {
                return(await feed.GetSongsFromPageAsync(pageNum, cancellationToken).ConfigureAwait(false));
            }, new ExecutionDataflowBlockOptions
            {
                MaxDegreeOfParallelism = MaxConcurrency,
                BoundedCapacity        = MaxConcurrency,
                CancellationToken      = cancellationToken
                                         //#if NETSTANDARD
                                         //                , EnsureOrdered = true
                                         //#endif
            });
            bool continueLooping = true;
            int  itemsInBlock    = 0;
            List <PageReadResult> pageResults = new List <PageReadResult>(maxPages + 2);

            do
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    continueLooping = false;
                }
                while (continueLooping)
                {
                    if (Utilities.IsPaused)
                    {
                        await Utilities.WaitUntil(() => !Utilities.IsPaused, 500, cancellationToken).ConfigureAwait(false);
                    }
                    if (cancellationToken.IsCancellationRequested)
                    {
                        continueLooping = false;
                        break;
                    }
                    await ProcessPageBlock.SendAsync(pageIndex, cancellationToken).ConfigureAwait(false); // TODO: Need check with SongsPerPage

                    itemsInBlock++;
                    pageIndex++;

                    if ((pageIndex > maxPages && useMaxPages) || cancellationToken.IsCancellationRequested)
                    {
                        continueLooping = false;
                    }
                    // TODO: Better http error handling, what if only a single page is broken and returns 0 songs?
                    while (ProcessPageBlock.OutputCount > 0 || itemsInBlock == MaxConcurrency || !continueLooping)
                    {
                        if (cancellationToken.IsCancellationRequested)
                        {
                            continueLooping = false;
                            break;
                        }
                        if (itemsInBlock <= 0)
                        {
                            break;
                        }
                        await ProcessPageBlock.OutputAvailableAsync(cancellationToken).ConfigureAwait(false);

                        while (ProcessPageBlock.TryReceive(out PageReadResult pageResult))
                        {
                            int songsAdded = 0;
                            if (pageResult != null)
                            {
                                pageResults.Add(pageResult);
                            }
                            if (Utilities.IsPaused)
                            {
                                await Utilities.WaitUntil(() => !Utilities.IsPaused, 500, cancellationToken).ConfigureAwait(false);
                            }
                            itemsInBlock--;
                            if (pageResult.IsLastPage || pageResult == null || pageResult.Count == 0) // TODO: This will trigger if a single page has an error.
                            {
                                Logger?.Debug("Received no new songs, last page reached.");
                                ProcessPageBlock.Complete();
                                itemsInBlock    = 0;
                                continueLooping = false;
                                break;
                            }
                            if (pageResult.Count > 0)
                            {
                                Logger?.Debug($"Receiving {pageResult.Count} potential songs from {pageResult.Uri}");
                            }
                            else
                            {
                                Logger?.Debug($"Did not find any songs on page '{pageResult.Uri}' of {Name}.{settings.FeedName}.");
                            }

                            // TODO: Process PageReadResults for better error feedback.
                            foreach (var song in pageResult.Songs)
                            {
                                if (!retDict.ContainsKey(song.Hash))
                                {
                                    if (retDict.Count < settings.MaxSongs || settings.MaxSongs == 0)
                                    {
                                        retDict.Add(song.Hash, song);
                                        songsAdded++;
                                    }
                                    if (retDict.Count >= settings.MaxSongs && useMaxSongs)
                                    {
                                        continueLooping = false;
                                    }
                                }
                            }
                            int prog = Interlocked.Increment(ref pagesChecked);
                            progress?.Report(new ReaderProgress(prog, songsAdded));
                            if (!useMaxPages || pageIndex <= maxPages)
                            {
                                if (retDict.Count < settings.MaxSongs)
                                {
                                    continueLooping = true;
                                }
                            }
                        }
                    }
                }
            }while (continueLooping);
            if (pageResults.Any(r => r.PageError == PageErrorType.Cancelled))
            {
                return(FeedResult.GetCancelledResult(retDict, pageResults));
            }
            return(new FeedResult(retDict, pageResults));
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="_settings"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        /// <exception cref="InvalidCastException">Throw when the provided settings object isn't a BeatSaverFeedSettings</exception>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="_settings"/> is null.</exception>
        public async Task <FeedResult> GetSongsFromScoreSaberAsync(ScoreSaberFeedSettings _settings, IProgress <ReaderProgress> progress, CancellationToken cancellationToken)
        {
            if (_settings == null)
            {
                throw new ArgumentNullException(nameof(_settings), "settings cannot be null for ScoreSaberReader.GetSongsFromScoreSaberAsync");
            }
            if (!(_settings is ScoreSaberFeedSettings settings))
            {
                throw new InvalidCastException(INVALIDFEEDSETTINGSMESSAGE);
            }
            // "https://scoresaber.com/api.php?function=get-leaderboards&cat={CATKEY}&limit={LIMITKEY}&page={PAGENUMKEY}&ranked={RANKEDKEY}"
            int songsPerPage = settings.SongsPerPage;

            if (songsPerPage == 0)
            {
                songsPerPage = 100;
            }
            int pageNum = settings.StartingPage;
            //int maxPages = (int)Math.Ceiling(settings.MaxSongs / ((float)songsPerPage));
            int maxPages     = settings.MaxPages;
            int pagesChecked = 0;

            if (pageNum > 1 && maxPages != 0)
            {
                maxPages = maxPages + pageNum - 1;
            }
            //if (settings.MaxPages > 0)
            //    maxPages = maxPages < settings.MaxPages ? maxPages : settings.MaxPages; // Take the lower limit.
            var feed = new ScoreSaberFeed(settings);

            try
            {
                feed.EnsureValidSettings();
            }
            catch (InvalidFeedSettingsException ex)
            {
                return(new FeedResult(null, null, ex, FeedResultError.Error));
            }
            Dictionary <string, ScrapedSong> songs = new Dictionary <string, ScrapedSong>();

            var            pageResults = new List <PageReadResult>();
            Uri            uri         = feed.GetUriForPage(1);
            PageReadResult result      = await feed.GetSongsAsync(uri, cancellationToken).ConfigureAwait(false);

            pageResults.Add(result);
            foreach (var song in result.Songs)
            {
                if (!songs.ContainsKey(song.Hash) && (songs.Count < settings.MaxSongs || settings.MaxSongs == 0))
                {
                    songs.Add(song.Hash, song);
                }
            }
            pagesChecked++;
            progress?.Report(new ReaderProgress(pagesChecked, songs.Count));
            bool continueLooping = true;

            do
            {
                pageNum++;
                //int diffCount = 0;
                if ((maxPages > 0 && pageNum > maxPages) || (settings.MaxSongs > 0 && songs.Count >= settings.MaxSongs))
                {
                    break;
                }
                if (Utilities.IsPaused)
                {
                    await Utilities.WaitUntil(() => !Utilities.IsPaused, 500).ConfigureAwait(false);
                }

                // TODO: Handle PageReadResult here
                uri = feed.GetUriForPage(pageNum);
                var pageResult = await feed.GetSongsAsync(uri, cancellationToken).ConfigureAwait(false);

                pageResults.Add(pageResult);
                if (pageResult.PageError == PageErrorType.Cancelled)
                {
                    return(FeedResult.GetCancelledResult(songs, pageResults));
                }
                int uniqueSongCount = 0;
                foreach (var song in pageResult.Songs)
                {
                    //diffCount++;
                    if (!songs.ContainsKey(song.Hash) && (songs.Count < settings.MaxSongs || settings.MaxSongs == 0))
                    {
                        songs.Add(song.Hash, song);
                        uniqueSongCount++;
                    }
                }

                int prog = Interlocked.Increment(ref pagesChecked);
                progress?.Report(new ReaderProgress(prog, uniqueSongCount));
                if (uniqueSongCount > 0)
                {
                    Logger?.Debug($"Receiving {uniqueSongCount} potential songs from {pageResult.Uri}");
                }
                else
                {
                    Logger?.Debug($"Did not find any new songs on page '{pageResult.Uri}' of {Name}.{settings.FeedName}.");
                }
                if (pageResult.IsLastPage)
                {
                    Logger?.Debug($"Last page reached.");
                    continueLooping = false;
                }
                if (!pageResult.Successful)
                {
                    Logger?.Debug($"Page {pageResult.Uri.ToString()} failed, ending read.");
                    if (pageResult.Exception != null)
                    {
                        Logger?.Debug($"{pageResult.Exception.Message}\n{pageResult.Exception.StackTrace}");
                    }
                    continueLooping = false;
                }

                //pageReadTasks.Add(GetSongsFromPageAsync(url.ToString()));
                if ((maxPages > 0 && pageNum >= maxPages) || (settings.MaxSongs > 0 && songs.Count >= settings.MaxSongs))
                {
                    continueLooping = false;
                }
            } while (continueLooping);


            if (pageResults.Any(r => r.PageError == PageErrorType.Cancelled))
            {
                return(FeedResult.GetCancelledResult(songs, pageResults));
            }
            return(new FeedResult(songs, pageResults));
        }