private static void ProcessJamPlayer(Dictionary <int, List <PenaltyGroup> > pgMap, Dictionary <int, List <PenaltyGroup> > jamBoxTimeMap, Dictionary <int, int> boxTimeEstimates,
                                             int jamID, JamTeamEffectiveness jte1, List <JamPlayerEffectiveness> pjeList, int jamTime, JamPlayer player)
        {
            // handle penalties
            var playerPenaltyGroups = pgMap.ContainsKey(player.PlayerID) ? pgMap[player.PlayerID] : null;

            ProcessPlayerJamPenalties(jamBoxTimeMap, jamID, playerPenaltyGroups);

            // try to estimate what portion of a jam someone missed via time in the box
            int timeInBox = 0;

            if (jamBoxTimeMap.ContainsKey(jamID))
            {
                foreach (PenaltyGroup group in jamBoxTimeMap[jamID])
                {
                    foreach (BoxTime bt in group.BoxTimes)
                    {
                        if (bt.PlayerID == player.PlayerID && bt.JamID == jamID)
                        {
                            // factor in estimated box time
                            timeInBox += boxTimeEstimates[bt.BoxTimeID];
                        }
                    }
                }
            }

            JamPlayerEffectiveness pje = new JamPlayerEffectiveness
            {
                PlayerID    = player.PlayerID,
                TeamID      = player.TeamID,
                JamPortion  = ((double)jamTime - timeInBox) / jamTime,
                BaseQuality = jte1.Percentile,
                JamID       = jamID,
                IsJammer    = player.IsJammer,
                PenaltyCost = 0
            };

            pjeList.Add(pje);
        }
        private void CalculatePlayerEffectiveness()
        {
            if (_jamTeamEffectiveness == null)
            {
                CalculateJamTeamEffectiveness();
            }

            SqlConnection connection = new SqlConnection(_connectionString);

            connection.Open();
            SqlTransaction transaction = connection.BeginTransaction();

            _jamPlayers    = new JamPlayerGateway(connection, transaction).GetJamPlayers();
            _penaltyGroups = new PenaltyGroupGateway(connection, transaction).GetAllPenaltyGroups();

            // these three get mapped by jam
            var jteMap     = _jamTeamEffectiveness.GroupBy(jte => jte.JamID).ToDictionary(g => g.Key);
            var jamDataMap = _jamTeamData.GroupBy(jf => jf.JamID).ToDictionary(g => g.Key);
            var jpJamMap   = _jamPlayers.GroupBy(jp => jp.JamID).ToDictionary(g => g.Key);
            var jamTimeMap = new JamTimeLimitGateway(connection, transaction).GetAllJamTimeEstimates().ToDictionary(jte => jte.JamID);
            //var jpPlayerMap = players.GroupBy(jp => jp.PlayerID).ToDictionary(g => g.Key);

            // the best we can do with this is map it by player
            var pgMap = _penaltyGroups.GroupBy(pg => pg.PlayerID).ToDictionary(g => g.Key, g => g.ToList());

            Dictionary <int, List <JamPlayerEffectiveness> > pjeMap         = new Dictionary <int, List <JamPlayerEffectiveness> >();
            Dictionary <int, List <PenaltyGroup> >           jamBoxTimeMap1 = new Dictionary <int, List <PenaltyGroup> >();
            Dictionary <int, List <PenaltyGroup> >           jamBoxTimeMap2 = new Dictionary <int, List <PenaltyGroup> >();

            _boxTimeEstimates = new BoxTimeEstimateGateway(connection, transaction).GetAllBoxTimeEstimates();
            int maxJamID = jteMap.Keys.Max();
            int jamID    = 1;

            while (jamID <= maxJamID)
            {
                if (!jteMap.ContainsKey(jamID))
                {
                    jamID++;
                    continue;
                }
                var tempJte = jteMap[jamID].OrderBy(jte => jte.TeamID);
                JamTeamEffectiveness jte1             = tempJte.First();
                JamTeamEffectiveness jte2             = tempJte.Last();
                var team1Players                      = jpJamMap[jamID].Where(jp => jp.TeamID == jte1.TeamID);
                var team2Players                      = jpJamMap[jamID].Where(jp => jp.TeamID == jte2.TeamID);
                List <JamPlayerEffectiveness> pjeList = new List <JamPlayerEffectiveness>(5);
                int jamTime = jamTimeMap[jamID].Estimate;

                // handle each player
                foreach (JamPlayer player in team1Players)
                {
                    ProcessJamPlayer(pgMap, jamBoxTimeMap1, _boxTimeEstimates, jamID, jte1, pjeList, jamTime, player);
                }

                List <JamPlayerEffectiveness> pjeList2 = new List <JamPlayerEffectiveness>(5);
                // handle each player
                foreach (JamPlayer player in team2Players)
                {
                    ProcessJamPlayer(pgMap, jamBoxTimeMap2, _boxTimeEstimates, jamID, jte2, pjeList, jamTime, player);
                }

                pjeMap[jamID] = pjeList;
                pjeMap[jamID].AddRange(pjeList2);
                jamID++;
            }

            jamID = 1;
            while (jamID <= maxJamID)
            {
                if (!jteMap.ContainsKey(jamID))
                {
                    jamID++;
                    continue;
                }
                var tempJte = jteMap[jamID].OrderBy(jte => jte.TeamID);
                JamTeamEffectiveness jte1 = tempJte.First();
                JamTeamEffectiveness jte2 = tempJte.Last();
                // apply box time costs to players with correlated penalties
                if (jamBoxTimeMap1.ContainsKey(jamID))
                {
                    AssignPenaltyCosts(jamDataMap, jamTimeMap, pjeMap, jamBoxTimeMap1, _boxTimeEstimates, jamID, jte1);
                }
                // apply box time costs to players with correlated penalties
                if (jamBoxTimeMap2.ContainsKey(jamID))
                {
                    AssignPenaltyCosts(jamDataMap, jamTimeMap, pjeMap, jamBoxTimeMap2, _boxTimeEstimates, jamID, jte2);
                }
                jamID++;
            }
            new JamPlayerEffectivenessGateway(connection, transaction).InsertJamPlayerEffectiveness(pjeMap);
            transaction.Commit();
            connection.Close();
        }
        private void AssignPenaltyCosts(Dictionary <int, IGrouping <int, JamTeamData> > jamDataMap, Dictionary <int, JamTimeEstimate> jamTimeMap,
                                        Dictionary <int, List <JamPlayerEffectiveness> > pjeMap, Dictionary <int, List <PenaltyGroup> > jamBoxTimeMap,
                                        Dictionary <int, int> boxTimeEstimates, int jamID, JamTeamEffectiveness jte)
        {
            List <PenaltyGroup> jamPenaltyGroups = jamBoxTimeMap[jamID];
            JamTeamData         thisJamData      = jamDataMap[jamID].First(jd => jd.TeamID == jte.TeamID);

            foreach (PenaltyGroup group in jamPenaltyGroups)
            {
                int penaltyPlayerID = group.Penalties[0].PlayerID;
                foreach (BoxTime boxTime in group.BoxTimes)
                {
                    if (boxTime.JamID == jamID)
                    {
                        // determine how big the swing would be if there had been no box time served
                        double qualityDifference = DetermineQualityWithoutPenalty(thisJamData, boxTime.IsJammer) - jte.Percentile;

                        // the cost of the box time is how bad that player would have to be in that jam to have an equivalent effect
                        // we estimate this based on how important the player is in the jam, by position
                        double penaltyCost      = boxTime.IsJammer ? qualityDifference * 2 : qualityDifference * 8;
                        double totalPenaltyTime = boxTime.IsJammer ?
                                                  thisJamData.JammerBoxTime :
                                                  thisJamData.BlockerBoxTime;
                        // modify this by how much of the total penalty time this player contributed

                        // doing the rough estimate version for now
                        double penaltyPortion = boxTimeEstimates[boxTime.BoxTimeID] / totalPenaltyTime;

                        // a portion of that cost goes to each penalty that factors into this box time
                        int penaltyCount = group.Penalties.Count;
                        foreach (Penalty penalty in group.Penalties)
                        {
                            var penaltyPJE = pjeMap[penalty.JamID].First(pje => pje.PlayerID == penalty.PlayerID);
                            penaltyPJE.PenaltyCost += penaltyPortion * penaltyCost / penaltyCount;
                            if (double.IsNaN(penaltyPJE.PenaltyCost))
                            {
                                throw new Exception(penalty.JamID + ": bad penalty data");
                            }
                        }

                        // for a given penalty group, there should only ever be
                        // a single box time in a given jam
                        break;
                    }
                }
            }
        }