/// <summary>
        /// Retrieves a list of all titles from all available regions.
        /// </summary>
        /// <param name="shopID">Use 1 for the 3DS eShop, 2 for the WiiU eShop.</param>
        /// <returns>A list of titles</returns>
        public async Task <List <EshopTitle> > GetAllTitles(int shopID)
        {
            // get title counts for all regions
            Console.WriteLine("Getting title count for {0} regions ...", Enum.GetValues(typeof(Region)).Length - 2);
            Dictionary <Region, int> titleCounts = new Dictionary <Region, int>();

            foreach (Region region in Enum.GetValues(typeof(Region)))
            {
                if (region == Region.None || region == Region.ALL)
                {
                    continue;
                }
                await Retry(async() => {
                    titleCounts.Add(region, await Samurai.GetTitleCountForRegion(webClient, region, shopID));
                });
            }

            int totalTitleCount = titleCounts.Values.Sum();

            Console.WriteLine("Downloading metadata for {0} titles ...", totalTitleCount);
            progressManager.SetTitle(string.Format("Downloading metadata for {0} titles ...", totalTitleCount));
            progressManager.Reset(totalTitleCount);

            DateTime          currentDate = DateTime.Today;
            List <EshopTitle> titleList   = new List <EshopTitle>();

            // loop through regions
            foreach (KeyValuePair <Region, int> pair in titleCounts)
            {
                Region region     = pair.Key;
                int    titleCount = pair.Value;

                // get titles from samurai
                for (int offset = 0; offset < titleCount; offset += TitleRequestLimit)
                {
                    XDocument titlesXml = null;
                    await Retry(async() => {
                        titlesXml = await Samurai.GetTitlesXmlForRegion(webClient, region, shopID, TitleRequestLimit, offset);
                    });

                    /*  structure:
                     *  <eshop><contents ...>
                     *      <content index=1><title ...>[title info]</title></content>
                     *      <content index=2><title ...>[title info]</title></content>
                     *  </contents></eshop>
                     */
                    XElement contentsElement = titlesXml.Root.Element("contents");

                    // iterate over titles in xml
                    foreach (XElement titleElement in contentsElement.Elements().Select(e => e.Element("title")))
                    {
                        // check release date
                        string releaseDateString = titleElement.Element("release_date_on_eshop")?.Value;
                        if (!String.IsNullOrEmpty(releaseDateString))
                        {
                            DateTime releaseDate;
                            if (!DateTime.TryParseExact(releaseDateString, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out releaseDate))
                            {
                                continue;
                            }
                            if (releaseDate > currentDate)
                            {
                                continue;
                            }
                        }

                        // create title and add data
                        EshopTitle title = new EshopTitle();
                        title.Region = region;
                        // set title fields from xml (incomplete, some fields must be retrieved from ninja)
                        title.AddDataFromXml(titleElement);

                        progressManager.Step(string.Format("{0}-{1}", region.ToCountryCode(), title.EshopId));

                        // some exceptions:
                        if (title.Platform == 63 || title.Platform == 1003) // ignore 3DS software updates
                        {
                            continue;
                        }
                        if (title.Platform == 143) // ignore some unknown platform with just 1 title
                        {
                            continue;
                        }

                        // get remaining data from ninja
                        XElement titleECInfoElement;
                        await Retry(async() => {
                            titleECInfoElement = await Ninja.GetECInfoForRegionAndTitleID(certWebClient, region, title.EshopId);
                            title.AddDataFromXml(titleECInfoElement);
                            if (title.JsonType != DatabaseJsonType.None)
                            {
                                titleList.Add(title);
                            }
                        });
                    }
                }
            }

            // merge titles that occur in all regions into one title with the 'ALL' region
            IEnumerable <EshopTitle> titleListEnumerable = titleList;
            var allRegions = Enum.GetValues(typeof(Region)).OfType <Region>().Where(r => r != Region.None && r != Region.ALL);
            var groups     = titleListEnumerable.GroupBy(t => t.TitleId).Select(g => g.ToList()).ToList();

            foreach (var group in groups)
            {
                var groupRegions = group.Select(t => t.Region);
                if (allRegions.All(groupRegions.Contains))
                {
                    titleListEnumerable = titleListEnumerable.Except(group.Skip(1));
                    group[0].Region     = Region.ALL;
                }
            }

            titleList = titleListEnumerable.ToList();

            return(titleList);
        }