private static void FindOrCreateInningsRecordsForBatter(List <PlayerInMatchStatisticsRecord> records, Match match, TeamInMatch homeTeam, TeamInMatch awayTeam, MatchInnings innings, bool homeTeamIsBatting, PlayerIdentity batter)
        {
            var allPlayerInningsForThisPlayer   = innings.PlayerInnings.Where(x => x.Batter.PlayerIdentityId == batter.PlayerIdentityId).OrderBy(x => x.BattingPosition).ToList();
            var firstPlayerInningsForThisPlayer = allPlayerInningsForThisPlayer.FirstOrDefault();

            // Find or add a record every batting team member in this innings regardless of whether they are recorded as batting
            var record = records.SingleOrDefault(x => x.MatchTeamId == innings.BattingMatchTeamId && x.PlayerIdentityId == batter.PlayerIdentityId && x.MatchInningsPair == innings.InningsPair() && (x.PlayerInningsNumber == 1 || x.PlayerInningsNumber == null));

            if (record == null)
            {
                record = CreateRecordForPlayerInInningsPair(match, innings, batter, homeTeamIsBatting ? homeTeam : awayTeam, homeTeamIsBatting ? awayTeam : homeTeam);
                records.Add(record);
            }
            record.PlayerInningsNumber = 1;
            AddPlayerInningsDataToRecord(firstPlayerInningsForThisPlayer, record);
            // There may be player innings with DismissalType = null, but that means they *were* dismissed, so for players who are missing from the batting card assume DidNotBat rather than null
            record.DismissalType      = firstPlayerInningsForThisPlayer != null ? firstPlayerInningsForThisPlayer.DismissalType : DismissalType.DidNotBat;
            record.PlayerWasDismissed = firstPlayerInningsForThisPlayer != null?StatisticsConstants.DISMISSALS_THAT_ARE_OUT.Contains(firstPlayerInningsForThisPlayer.DismissalType) : false;

            // Add extra records for any players who batted multiple times in the same innings
            for (var i = 1; i < allPlayerInningsForThisPlayer.Count; i++)
            {
                var extraBattingRecord = records.SingleOrDefault(x => x.MatchTeamId == innings.BattingMatchTeamId && x.PlayerIdentityId == batter.PlayerIdentityId && x.MatchInningsPair == innings.InningsPair() && x.PlayerInningsNumber == i + 1);
                if (extraBattingRecord == null)
                {
                    extraBattingRecord = CreateRecordForPlayerInInningsPair(match, innings, batter, homeTeamIsBatting ? homeTeam : awayTeam, homeTeamIsBatting ? awayTeam : homeTeam);
                    extraBattingRecord.PlayerInningsNumber = i + 1;
                    AddPlayerInningsDataToRecord(allPlayerInningsForThisPlayer[i], extraBattingRecord);
                    extraBattingRecord.DismissalType      = allPlayerInningsForThisPlayer[i].DismissalType;
                    extraBattingRecord.PlayerWasDismissed = StatisticsConstants.DISMISSALS_THAT_ARE_OUT.Contains(allPlayerInningsForThisPlayer[i].DismissalType);
                    records.Add(extraBattingRecord);
                }
            }
        }
        private static PlayerInMatchStatisticsRecord CreateRecordForPlayerInInningsPair(Match match, MatchInnings innings, PlayerIdentity identity, TeamInMatch team, TeamInMatch opposition)
        {
            var isOnBattingTeam = team.MatchTeamId == innings.BattingMatchTeamId;
            var pairedInnings   = match.MatchInnings.Single(x => x.InningsPair() == innings.InningsPair() && x.MatchInningsId != innings.MatchInningsId);

            return(new PlayerInMatchStatisticsRecord
            {
                PlayerId = identity.Player.PlayerId.Value,
                PlayerIdentityId = identity.PlayerIdentityId.Value,
                PlayerIdentityName = identity.PlayerIdentityName,
                PlayerRoute = identity.Player.PlayerRoute,
                MatchId = match.MatchId.Value,
                MatchName = match.MatchName,
                MatchType = match.MatchType,
                MatchPlayerType = match.PlayerType,
                MatchRoute = match.MatchRoute,
                MatchStartTime = match.StartTime,
                TournamentId = match.Tournament?.TournamentId,
                MatchLocationId = match.MatchLocation?.MatchLocationId,
                SeasonId = match.Season?.SeasonId,
                CompetitionId = match.Season?.Competition?.CompetitionId,
                MatchInningsPair = innings.InningsPair(),
                TeamRunsScored = isOnBattingTeam ? innings.Runs : pairedInnings.Runs,
                TeamWicketsLost = isOnBattingTeam ? innings.Wickets : pairedInnings.Wickets,
                TeamBonusOrPenaltyRunsAwarded = isOnBattingTeam ? innings.BonusOrPenaltyRuns : pairedInnings.BonusOrPenaltyRuns,
                TeamRunsConceded = isOnBattingTeam ? pairedInnings.Runs : innings.Runs,
                TeamNoBallsConceded = isOnBattingTeam ? pairedInnings.NoBalls : innings.NoBalls,
                TeamWidesConceded = isOnBattingTeam ? pairedInnings.Wides : innings.Wides,
                TeamByesConceded = isOnBattingTeam ? pairedInnings.Byes : innings.Byes,
                TeamWicketsTaken = isOnBattingTeam ? pairedInnings.Wickets : innings.Wickets,
                MatchTeamId = team.MatchTeamId.Value,
                ClubId = team.Team.Club?.ClubId,
                TeamId = team.Team.TeamId.Value,
                TeamName = team.Team.TeamName,
                TeamRoute = team.Team.TeamRoute,
                OppositionTeamId = opposition.Team.TeamId.Value,
                OppositionTeamName = opposition.Team.TeamName,
                OppositionTeamRoute = opposition.Team.TeamRoute,
            });
        }
        private void FindOrCreateInningsRecordForFielder(List <PlayerInMatchStatisticsRecord> records, Match match, TeamInMatch homeTeam, TeamInMatch awayTeam, MatchInnings innings, bool homeTeamIsBatting, PlayerIdentity fielder)
        {
            var record = records.SingleOrDefault(x => x.MatchTeamId == innings.BowlingMatchTeamId && x.PlayerIdentityId == fielder.PlayerIdentityId && x.MatchInningsPair == innings.InningsPair() && (x.PlayerInningsNumber == 1 || x.PlayerInningsNumber == null));

            if (record == null)
            {
                record = CreateRecordForPlayerInInningsPair(match, innings, fielder, homeTeamIsBatting ? awayTeam : homeTeam, homeTeamIsBatting ? homeTeam : awayTeam);
                record.PlayerInningsNumber = null;
                records.Add(record);
            }
            var bowlingFigures = innings.BowlingFigures.SingleOrDefault(x => x.Bowler.PlayerIdentityId == fielder.PlayerIdentityId);
            var oversBowled    = innings.OversBowled.Where(x => x.Bowler.PlayerIdentityId == fielder.PlayerIdentityId);

            record.Catches = innings.PlayerInnings.Count(x => (x.DismissalType == DismissalType.Caught && x.DismissedBy?.PlayerIdentityId == fielder.PlayerIdentityId) ||
                                                         (x.DismissalType == DismissalType.CaughtAndBowled && x.Bowler?.PlayerIdentityId == fielder.PlayerIdentityId));
            record.RunOuts = innings.PlayerInnings.Count(x => x.DismissalType == DismissalType.RunOut && x.DismissedBy?.PlayerIdentityId == fielder.PlayerIdentityId);

            record.BowlingFiguresId            = bowlingFigures?.BowlingFiguresId;
            record.OverNumberOfFirstOverBowled = innings.OversBowled.OrderBy(x => x.OverNumber).FirstOrDefault(x => x.Bowler.PlayerIdentityId == fielder.PlayerIdentityId)?.OverNumber;
            if (oversBowled.Any())
            {
                record.BallsBowled = oversBowled.Where(x => x.BallsBowled.HasValue).Sum(x => x.BallsBowled) + (oversBowled.Count(x => !x.BallsBowled.HasValue) * StatisticsConstants.BALLS_PER_OVER);
                record.NoBalls     = oversBowled.Sum(x => x.NoBalls);
                record.Wides       = oversBowled.Sum(x => x.Wides);
            }
            else
            {
                record.BallsBowled = bowlingFigures?.Overs != null?_oversHelper.OversToBallsBowled(bowlingFigures.Overs.Value) : (int?)null;
            }
            record.Overs              = bowlingFigures?.Overs;
            record.Maidens            = bowlingFigures?.Maidens;
            record.RunsConceded       = bowlingFigures?.RunsConceded;
            record.HasRunsConceded    = bowlingFigures?.RunsConceded != null;
            record.Wickets            = bowlingFigures?.Wickets;
            record.WicketsWithBowling = (bowlingFigures != null && oversBowled.Any()) ? bowlingFigures.Wickets : (int?)null;
        }