/// <summary>
        /// Finds the most frequent commercial type allocated to the break
        /// </summary>
        /// <param name="breakCommercials">Break/commercials instance</param>
        /// <returns>The commercial type and the frequency it occurs</returns>
        private (string CommercialType, int Frequency) GetMostFrequentCommercialType(
            BreakCommercials breakCommercials)
        {
            var commercialFrequency =
                breakCommercials.Commercials
                .GroupBy(c => c.CommercialType)
                .Select(
                    g => new
            {
                CommercialType = g.Key,
                Count          = g.Select(c => c.CommercialType).Count()
            });
            var maxValue =
                commercialFrequency.First(
                    value1 =>
                    value1.Count == commercialFrequency.Max(value2 => value2.Count));

            return(maxValue.CommercialType, maxValue.Count);
        }
        /// <summary>
        /// For a specific commercial swap this method calculates what the affect on
        /// the total rating will be, so we can determine if it's better than other
        /// available swaps
        /// </summary>
        /// <param name="sourceBreakCommercials">Source break/commercials instance</param>
        /// <param name="sourceCommercial">Source commercial</param>
        /// <param name="targetBreakCommercials">Target break/commercials instance</param>
        /// <param name="targetCommercial">Target commercial</param>
        /// <returns>The rating change</returns>
        private int GetRatingChangeForSwap(
            BreakCommercials sourceBreakCommercials,
            Commercial sourceCommercial,
            BreakCommercials targetBreakCommercials,
            Commercial targetCommercial)
        {
            var ratingTotalAfterSwap = 0;

            if (sourceBreakCommercials != null)
            {
                ratingTotalAfterSwap += sourceBreakCommercials.Break.BreakDemographics
                                        .Where(value => value.Demographic.Id == targetCommercial.Demographic.Id)
                                        .Select(value => value.Rating).FirstOrDefault();
            }

            if (targetBreakCommercials != null)
            {
                ratingTotalAfterSwap += targetBreakCommercials.Break.BreakDemographics
                                        .Where(value => value.Demographic.Id == sourceCommercial.Demographic.Id)
                                        .Select(value => value.Rating).FirstOrDefault();
            }

            var ratingTotalBeforeSwap = 0;

            if (sourceBreakCommercials != null)
            {
                ratingTotalBeforeSwap += sourceBreakCommercials.Break.BreakDemographics
                                         .Where(value => value.Demographic.Id == sourceCommercial.Demographic.Id)
                                         .Select(value => value.Rating).FirstOrDefault();
            }

            if (targetBreakCommercials != null)
            {
                ratingTotalBeforeSwap += targetBreakCommercials.Break.BreakDemographics
                                         .Where(value => value.Demographic.Id == targetCommercial.Demographic.Id)
                                         .Select(value => value.Rating).FirstOrDefault();
            }

            return(ratingTotalAfterSwap - ratingTotalBeforeSwap);
        }
        /// <summary>
        /// Checks if the commercial can be moved to the break based on commercial type
        /// restrictions.
        /// </summary>
        /// <param name="breakCommercials">The Break/commercials instance</param>
        /// <param name="commercial">The commercial to add</param>
        /// <returns>True if the commercial can be added to the break</returns>
        private bool IsCommercialValidForBreak(
            BreakCommercials breakCommercials,
            Commercial commercial)
        {
            //Is the source commercial valid for the target break?
            if (breakCommercials.Break.InvalidCommercialTypes != null &&
                breakCommercials.Break.InvalidCommercialTypes.Contains(commercial.CommercialType))
            {
                //Commercial type is invalid for this break
                return(false);
            }

            var targetCountAlreadyInBreak =
                breakCommercials.Commercials.Count(
                    value => value.CommercialType == commercial.CommercialType);

            if (targetCountAlreadyInBreak + 1 > MaxBreakCapacityForCommercialType(breakCommercials.Break))
            {
                //Adding this type to the break will mean there's too many of the type
                return(false);
            }

            return(true);
        }
        /// <summary>
        /// Resolves invalid commercial allocations by attempting swaps with other breaks
        /// </summary>
        private void SwapInvalidCommercials(
            List <BreakCommercials> allBreakCommercials,
            List <Commercial> unusedCommercials)
        {
            foreach (var breakCommercials in allBreakCommercials)
            {
                //If there's any commercials of an invalid type, they have to go
                if (breakCommercials.Break.InvalidCommercialTypes != null)
                {
                    var invalidCommercials = breakCommercials.Commercials.Where(
                        value =>
                        breakCommercials.Break.InvalidCommercialTypes.Contains(value.CommercialType)).ToList();

                    foreach (var invalidCommercial in invalidCommercials)
                    {
                        var bestSwapResult =
                            GetBestSwap(
                                breakCommercials,
                                invalidCommercial,
                                allBreakCommercials.Where(
                                    value => value.Break.Id != breakCommercials.Break.Id).ToList(),
                                unusedCommercials);
                        if (bestSwapResult.HasValue)
                        {
                            var selectedBreakCommercials = bestSwapResult.Value.SelectedBreakCommercials;
                            var selectedTargetCommercial = bestSwapResult.Value.NewSourceCommercial;
                            selectedBreakCommercials.Commercials.Remove(selectedTargetCommercial);
                            selectedBreakCommercials.Commercials.Add(invalidCommercial);
                            breakCommercials.Commercials.Remove(invalidCommercial);
                            breakCommercials.Commercials.Add(selectedTargetCommercial);

                            Debug.WriteLine($"Swapped {invalidCommercial.Id} for {selectedTargetCommercial.Id}");
                        }
                        else
                        {
                            throw new ArgumentException(
                                      "Unable to fill the breaks with the available commercials - cannot reallocate invalid commercial");
                        }
                    }
                }

                //Next check if there's too many commercials of a certain type
                //(e.g. if we have 4 slots and 3 of a certain type, there's no way
                //to arrange them so they aren't adjacent). In this case we will go through
                //comparing the rating change for the potential swaps and pick the most favourable one
                var frequentCommercialResult = GetMostFrequentCommercialType(breakCommercials);

                string tooFrequentCommercialType = null;
                int    maxFrequencyAllowed       = MaxBreakCapacityForCommercialType(breakCommercials.Break);

                if (frequentCommercialResult.Frequency > maxFrequencyAllowed)
                {
                    tooFrequentCommercialType = frequentCommercialResult.CommercialType;
                }

                if (!string.IsNullOrEmpty(tooFrequentCommercialType))
                {
                    var matchingCommercials =
                        breakCommercials.Commercials.Where(
                            value =>
                            value.CommercialType == tooFrequentCommercialType).ToList();

                    while (matchingCommercials.Count > maxFrequencyAllowed)
                    {
                        var bestRatingChange = int.MinValue;
                        BreakCommercials selectedBreakCommercials = null;
                        Commercial       oldSourceCommercial      = null;
                        Commercial       newSourceCommercial      = null;
                        Commercial       oldTargetCommercial      = null;
                        Commercial       newTargetCommercial      = null;

                        foreach (var matchingCommercial in matchingCommercials)
                        {
                            var bestSwapResult =
                                GetBestSwap(
                                    breakCommercials,
                                    matchingCommercial,
                                    allBreakCommercials.Where(
                                        value => value.Break.Id != breakCommercials.Break.Id).ToList(),
                                    unusedCommercials);
                            if (bestSwapResult.HasValue && bestSwapResult.Value.RatingChange > bestRatingChange)
                            {
                                oldSourceCommercial      = matchingCommercial;
                                newSourceCommercial      = bestSwapResult.Value.NewSourceCommercial;
                                oldTargetCommercial      = bestSwapResult.Value.OldTargetCommercial;
                                newTargetCommercial      = bestSwapResult.Value.NewTargetCommercial;
                                selectedBreakCommercials = bestSwapResult.Value.Item1;
                                bestRatingChange         = bestSwapResult.Value.RatingChange;

                                if (oldSourceCommercial != newSourceCommercial)
                                {
                                    Debug.WriteLine(
                                        $"Swapped {oldSourceCommercial.Id} for {newSourceCommercial.Id}");
                                }
                                if (oldTargetCommercial != newTargetCommercial)
                                {
                                    Debug.WriteLine(
                                        $"Swapped {oldTargetCommercial.Id} for {newTargetCommercial.Id}");
                                }
                            }
                        }

                        //If we have a valid swap setup, do it
                        if (newSourceCommercial == null ||
                            newSourceCommercial == oldSourceCommercial)
                        {
                            throw new ArgumentException(
                                      "Unable to fill the breaks with the available commercials - cannot reallocate too-frequent commercial");
                        }

                        breakCommercials.Commercials.Remove(oldSourceCommercial);
                        breakCommercials.Commercials.Add(newSourceCommercial);
                        selectedBreakCommercials.Commercials.Remove(oldTargetCommercial);
                        selectedBreakCommercials.Commercials.Add(newTargetCommercial);
                        matchingCommercials.Remove(oldSourceCommercial);

                        //Ensure we add any now-unused commercials back to the unused collection
                        if (oldSourceCommercial != newSourceCommercial &&
                            oldSourceCommercial != newTargetCommercial)
                        {
                            unusedCommercials.Add(oldSourceCommercial);
                        }

                        if (oldTargetCommercial != newSourceCommercial &&
                            oldTargetCommercial != newTargetCommercial)
                        {
                            unusedCommercials.Add(oldTargetCommercial);
                        }
                    }
                }
            }
        }
        GetBestSwap(
            BreakCommercials sourceBreakCommercials,
            Commercial sourceCommercial,
            List <BreakCommercials> otherBreakCommercials,
            List <Commercial> unusedCommercials)
        {
            var bestRatingChange = int.MinValue;
            BreakCommercials selectedBreakCommercials    = null;
            Commercial       selectedOldTargetCommercial = null;
            Commercial       selectedNewTargetCommercial = null;
            Commercial       selectedNewSourceCommercial = sourceCommercial;

            //Check the rating change if we swap the source commercial with an unused one
            foreach (var unusedCommercial in unusedCommercials)
            {
                var ratingChange =
                    GetRatingChangeForSwap(
                        sourceBreakCommercials,
                        sourceCommercial,
                        null,
                        unusedCommercial);

                if (ratingChange > bestRatingChange)
                {
                    bestRatingChange            = ratingChange;
                    selectedNewSourceCommercial = unusedCommercial;
                }
            }

            //Create list of possible swaps, ignoring commercials which are invalid
            //or of the same type
            foreach (var breakCommercials in otherBreakCommercials)
            {
                //Is the source commercial valid for the target break?
                if (!IsCommercialValidForBreak(breakCommercials, sourceCommercial))
                {
                    continue;
                }

                foreach (var targetCommercial in breakCommercials.Commercials)
                {
                    //Is the target commercial valid for the source break?
                    if (!IsCommercialValidForBreak(sourceBreakCommercials, targetCommercial))
                    {
                        continue;
                    }

                    //What's the rating total change after the swap?
                    var ratingChange =
                        GetRatingChangeForSwap(
                            sourceBreakCommercials,
                            sourceCommercial,
                            breakCommercials,
                            targetCommercial);

                    //Check the rating change if we swap the target commercial with an unused one
                    Commercial selectedUnusedCommercial     = null;
                    int        unusedCommercialRatingChange = int.MinValue;

                    foreach (var unusedCommercial in unusedCommercials)
                    {
                        var unusedRatingChange =
                            GetRatingChangeForSwap(
                                null,
                                unusedCommercial,
                                breakCommercials,
                                targetCommercial);

                        if (unusedRatingChange > unusedCommercialRatingChange)
                        {
                            unusedCommercialRatingChange = unusedRatingChange;
                            selectedUnusedCommercial     = unusedCommercial;
                        }
                    }

                    if (unusedCommercialRatingChange > 0)
                    {
                        ratingChange += unusedCommercialRatingChange;
                    }

                    if (bestRatingChange < ratingChange)
                    {
                        bestRatingChange            = ratingChange;
                        selectedBreakCommercials    = breakCommercials;
                        selectedNewSourceCommercial = targetCommercial;
                        selectedOldTargetCommercial = targetCommercial;

                        if (unusedCommercialRatingChange > 0)
                        {
                            selectedNewTargetCommercial = selectedUnusedCommercial;
                        }
                        else
                        {
                            selectedNewTargetCommercial = sourceCommercial;
                        }
                    }
                }
            }

            return(
                selectedBreakCommercials,
                selectedNewSourceCommercial,
                selectedOldTargetCommercial,
                selectedNewTargetCommercial,
                bestRatingChange);
        }