public async override Task <ActionResult> Index(ContentModel contentModel)
        {
            if (contentModel is null)
            {
                throw new ArgumentNullException(nameof(contentModel));
            }

            var model = new MatchListingViewModel(contentModel.Content, Services?.UserService)
            {
                AppliedMatchFilter = _queryStringParser.ParseFilterFromQueryString(Request.QueryString),
                DateTimeFormatter  = _dateFormatter
            };

            string pageTitle = "Stoolball matches";

            if (Request.RawUrl.StartsWith("/clubs/", StringComparison.OrdinalIgnoreCase))
            {
                var club = await _clubDataSource.ReadClubByRoute(Request.RawUrl).ConfigureAwait(false);

                if (club == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " for " + club.ClubName;
                model.AppliedMatchFilter.TeamIds.AddRange(club.Teams.Select(x => x.TeamId.Value));
            }
            else if (Request.RawUrl.StartsWith("/teams/", StringComparison.OrdinalIgnoreCase))
            {
                var team = await _teamDataSource.ReadTeamByRoute(Request.RawUrl).ConfigureAwait(false);

                if (team == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " for " + team.TeamName;
                model.AppliedMatchFilter.TeamIds.Add(team.TeamId.Value);
            }
            else if (Request.RawUrl.StartsWith("/competitions/", StringComparison.OrdinalIgnoreCase))
            {
                var competition = await _competitionDataSource.ReadCompetitionByRoute(Request.RawUrl).ConfigureAwait(false);

                if (competition == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " in the " + competition.CompetitionName;
                model.AppliedMatchFilter.CompetitionIds.Add(competition.CompetitionId.Value);
            }
            else if (Request.RawUrl.StartsWith("/locations/", StringComparison.OrdinalIgnoreCase))
            {
                var location = await _matchLocationDataSource.ReadMatchLocationByRoute(Request.RawUrl).ConfigureAwait(false);

                if (location == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " at " + location.NameAndLocalityOrTown();
                model.AppliedMatchFilter.MatchLocationIds.Add(location.MatchLocationId.Value);
            }

            // Remove from date from filter if it's the default, and describe the remainder in the feed title.
            var clonedFilter = model.AppliedMatchFilter.Clone();

            if (clonedFilter.FromDate == DateTimeOffset.UtcNow.Date)
            {
                clonedFilter.FromDate = null;
            }
            // Remove to date filter if it's a rolling date
            // (if user has set a specific end date a exactly year in the future unfortunately we'll miss it, but this is only for the description)
            if (clonedFilter.UntilDate == DateTimeOffset.UtcNow.Date.AddDays(365).AddDays(1).AddSeconds(-1))
            {
                clonedFilter.UntilDate = null;
            }
            model.Metadata.PageTitle   = pageTitle + _matchFilterHumanizer.MatchingFilter(clonedFilter);
            model.Metadata.Description = $"New or updated stoolball matches on the Stoolball England website";
            if (model.AppliedMatchFilter.PlayerTypes.Any())
            {
                model.Metadata.PageTitle   = $"{model.AppliedMatchFilter.PlayerTypes.First().Humanize(LetterCasing.Sentence)} {model.Metadata.PageTitle.ToLower(CultureInfo.CurrentCulture)}";
                model.Metadata.Description = $"New or updated {model.AppliedMatchFilter.PlayerTypes.First()} stoolball matches on the Stoolball England website";
            }
            model.Matches = await _matchDataSource.ReadMatchListings(model.AppliedMatchFilter, MatchSortOrder.LatestUpdateFirst).ConfigureAwait(false);

            return(View(Request.QueryString["format"] == "tweet" ? "MatchTweets" : "MatchesRss", model));
        }
        public async override Task <ActionResult> Index(ContentModel contentModel)
        {
            if (contentModel is null)
            {
                throw new ArgumentNullException(nameof(contentModel));
            }

            var model = new MatchListingViewModel(contentModel.Content, Services?.UserService)
            {
                DefaultMatchFilter = new MatchFilter
                {
                    FromDate                 = DateTimeOffset.UtcNow.Date,
                    IncludeMatches           = true,
                    IncludeTournaments       = true,
                    IncludeTournamentMatches = false
                },
                DateTimeFormatter = _dateFormatter
            };

            model.AppliedMatchFilter = _matchFilterQueryStringParser.ParseQueryString(model.DefaultMatchFilter, HttpUtility.ParseQueryString(Request.Url.Query));

            // Don't allow matches in the past - this is a calendar for planning future events
            if (model.AppliedMatchFilter.FromDate < model.DefaultMatchFilter.FromDate)
            {
                model.AppliedMatchFilter.FromDate = model.DefaultMatchFilter.FromDate;
            }

            var pageTitle = "Stoolball matches and tournaments";
            var legacyTournamentsCalendarUrl = Regex.Match(Request.RawUrl, "/tournaments/(all|mixed|ladies|junior)/calendar.ics", RegexOptions.IgnoreCase);

            if (Request.RawUrl.StartsWith("/matches.ics", StringComparison.OrdinalIgnoreCase))
            {
                model.AppliedMatchFilter.IncludeTournaments = false;
                pageTitle = "Stoolball matches";
            }
            else if (legacyTournamentsCalendarUrl.Success || Request.RawUrl.StartsWith("/tournaments.ics", StringComparison.OrdinalIgnoreCase))
            {
                model.AppliedMatchFilter.IncludeMatches = false;
                pageTitle = "Stoolball tournaments";

                if (legacyTournamentsCalendarUrl.Success)
                {
                    switch (legacyTournamentsCalendarUrl.Groups[1].Value.ToUpperInvariant())
                    {
                    case "MIXED":
                        model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.Mixed);
                        break;

                    case "LADIES":
                        model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.Ladies);
                        break;

                    case "JUNIOR":
                        model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.JuniorMixed);
                        model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.JuniorGirls);
                        model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.JuniorBoys);
                        break;

                    default:
                        break;
                    }
                }
            }
            else if (Request.RawUrl.StartsWith("/tournaments/", StringComparison.OrdinalIgnoreCase))
            {
                var tournament = await _tournamentDataSource.ReadTournamentByRoute(Request.RawUrl).ConfigureAwait(false);

                if (tournament == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle = tournament.TournamentFullName(x => tournament.StartTimeIsKnown ? _dateFormatter.FormatDateTime(tournament.StartTime, true, false) : _dateFormatter.FormatDate(tournament.StartTime, true, false));
                model.Matches.Add(tournament.ToMatchListing());
            }
            else if (Request.RawUrl.StartsWith("/matches/", StringComparison.OrdinalIgnoreCase))
            {
                var match = await _matchDataSource.ReadMatchByRoute(Request.RawUrl).ConfigureAwait(false);

                if (match == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle = match.MatchFullName(x => match.StartTimeIsKnown ? _dateFormatter.FormatDateTime(match.StartTime, true, false) : _dateFormatter.FormatDate(match.StartTime, true, false));
                model.Matches.Add(match.ToMatchListing());
            }
            else if (Request.RawUrl.StartsWith("/clubs/", StringComparison.OrdinalIgnoreCase))
            {
                var club = await _clubDataSource.ReadClubByRoute(Request.RawUrl).ConfigureAwait(false);

                if (club == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " for " + club.ClubName;
                model.AppliedMatchFilter.TeamIds.AddRange(club.Teams.Select(x => x.TeamId.Value));
            }
            else if (Request.RawUrl.StartsWith("/teams/", StringComparison.OrdinalIgnoreCase))
            {
                var team = await _teamDataSource.ReadTeamByRoute(Request.RawUrl).ConfigureAwait(false);

                if (team == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " for " + team.TeamName;
                model.AppliedMatchFilter.TeamIds.Add(team.TeamId.Value);
            }
            else if (Request.RawUrl.StartsWith("/competitions/", StringComparison.OrdinalIgnoreCase))
            {
                var competition = await _competitionDataSource.ReadCompetitionByRoute(Request.RawUrl).ConfigureAwait(false);

                if (competition == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " in the " + competition.CompetitionName;
                model.AppliedMatchFilter.CompetitionIds.Add(competition.CompetitionId.Value);
            }
            else if (Request.RawUrl.StartsWith("/locations/", StringComparison.OrdinalIgnoreCase))
            {
                var location = await _matchLocationDataSource.ReadMatchLocationByRoute(Request.RawUrl).ConfigureAwait(false);

                if (location == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " at " + location.NameAndLocalityOrTown();
                model.AppliedMatchFilter.MatchLocationIds.Add(location.MatchLocationId.Value);
            }

            // Remove from date from filter if it's the default, and describe the remainder in the feed title.
            var clonedFilter = model.AppliedMatchFilter.Clone();

            if (clonedFilter.FromDate == model.DefaultMatchFilter.FromDate)
            {
                clonedFilter.FromDate = null;
            }
            model.Metadata.PageTitle = pageTitle + _matchFilterHumanizer.MatchingFilter(clonedFilter);
            if (model.AppliedMatchFilter.PlayerTypes.Any())
            {
                model.Metadata.PageTitle = $"{model.AppliedMatchFilter.PlayerTypes.First().Humanize(LetterCasing.Sentence).Replace("Junior mixed", "Junior")} {model.Metadata.PageTitle.ToLower(CultureInfo.CurrentCulture)}";
            }
            if (!model.Matches.Any())
            {
                model.Matches = await _matchListingDataSource.ReadMatchListings(model.AppliedMatchFilter, MatchSortOrder.LatestUpdateFirst).ConfigureAwait(false);
            }

            return(CurrentTemplate(model));
        }
        public async override Task <ActionResult> Index(ContentModel contentModel)
        {
            if (contentModel is null)
            {
                throw new ArgumentNullException(nameof(contentModel));
            }

            var model = new MatchListingViewModel(contentModel.Content, Services?.UserService)
            {
                AppliedMatchFilter = _matchFilterQueryStringParser.ParseQueryString(new MatchFilter(), HttpUtility.ParseQueryString(Request.Url.Query)),
                DateTimeFormatter  = _dateFormatter
            };

            model.AppliedMatchFilter.IncludeTournaments       = true;
            model.AppliedMatchFilter.IncludeTournamentMatches = false;
            model.AppliedMatchFilter.IncludeMatches           = false;
            if (!model.AppliedMatchFilter.FromDate.HasValue)
            {
                model.AppliedMatchFilter.FromDate = DateTimeOffset.UtcNow.AddDays(-1);
            }
            if (!model.AppliedMatchFilter.UntilDate.HasValue)
            {
                if (!int.TryParse(Request.QueryString["days"], out var daysAhead))
                {
                    daysAhead = 365;
                }
                model.AppliedMatchFilter.UntilDate = DateTimeOffset.UtcNow.AddDays(daysAhead);
            }

            var playerType = Path.GetFileNameWithoutExtension(Request.RawUrl.ToUpperInvariant());

            switch (playerType)
            {
            case "MIXED":
                model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.Mixed);
                break;

            case "LADIES":
                model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.Ladies);
                break;

            case "JUNIOR":
                model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.JuniorMixed);
                model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.JuniorGirls);
                model.AppliedMatchFilter.PlayerTypes.Add(PlayerType.JuniorBoys);
                break;

            default:
                playerType = null;
                break;
            }

            // Remove date from filter and describe the remainder in the feed title, because the date range is not the subject of the feed,
            // it's just what we're including in the feed right now to return only currently relevant data.
            var clonedFilter = model.AppliedMatchFilter.Clone();

            clonedFilter.FromDate      = clonedFilter.UntilDate = null;
            model.Metadata.PageTitle   = "Stoolball tournaments" + _matchFilterHumanizer.MatchingFilter(clonedFilter);
            model.Metadata.Description = $"New or updated stoolball tournaments on the Stoolball England website";
            if (!string.IsNullOrEmpty(playerType))
            {
                model.Metadata.PageTitle   = $"{playerType.ToLower(CultureInfo.CurrentCulture).Humanize(LetterCasing.Sentence)} {model.Metadata.PageTitle.ToLower(CultureInfo.CurrentCulture)}";
                model.Metadata.Description = $"New or updated {playerType.Humanize(LetterCasing.LowerCase)} stoolball tournaments on the Stoolball England website";
            }
            model.Matches = await _matchDataSource.ReadMatchListings(model.AppliedMatchFilter, MatchSortOrder.LatestUpdateFirst).ConfigureAwait(false);

            return(View(Request.QueryString["format"] == "tweet" ? "TournamentTweets" : "TournamentsRss", model));
        }