private static DiscountBase ConvertDiscountAndLineToDiscountBase(PeriodicDiscount discountAndLine)
            {
                DiscountBase        discount    = null;
                OfferDiscount       offer       = null;
                MixAndMatchDiscount mixAndMatch = null;
                MultipleBuyDiscount multipleBuy = null;
                ThresholdDiscount   threshold   = null;

                switch (discountAndLine.PeriodicDiscountType)
                {
                case PeriodicDiscountOfferType.Offer:
                    offer    = new OfferDiscount(discountAndLine.ValidationPeriod);
                    discount = offer;
                    break;

                case PeriodicDiscountOfferType.MixAndMatch:
                    mixAndMatch = new MixAndMatchDiscount(discountAndLine.ValidationPeriod);
                    mixAndMatch.DealPriceValue              = discountAndLine.MixAndMatchDealPrice;
                    mixAndMatch.DiscountAmountValue         = discountAndLine.MixAndMatchDiscountAmount;
                    mixAndMatch.DiscountPercentValue        = discountAndLine.MixAndMatchDiscountPercent;
                    mixAndMatch.NumberOfLeastExpensiveLines = discountAndLine.MixAndMatchNumberOfLeastExpensiveLines;
                    mixAndMatch.NumberOfTimesApplicable     = discountAndLine.MixAndMatchNumberOfTimeApplicable;
                    mixAndMatch.LeastExpensiveMode          = discountAndLine.LeastExpensiveMode;
                    discount = mixAndMatch;
                    break;

                case PeriodicDiscountOfferType.MultipleBuy:
                    multipleBuy = new MultipleBuyDiscount(discountAndLine.ValidationPeriod);
                    discount    = multipleBuy;
                    break;

                case PeriodicDiscountOfferType.Threshold:
                    threshold = new ThresholdDiscount(discountAndLine.ValidationPeriod);
                    threshold.ShouldCountNonDiscountItems = discountAndLine.ShouldCountNonDiscountItems != 0;
                    discount = threshold;
                    break;
                }

                if (discount != null)
                {
                    discount.IsCategoryToProductOrVariantIdsMapSet = true;

                    discount.OfferId                = discountAndLine.OfferId;
                    discount.OfferName              = discountAndLine.Name;
                    discount.PeriodicDiscountType   = discountAndLine.PeriodicDiscountType;
                    discount.IsDiscountCodeRequired = discountAndLine.IsDiscountCodeRequired;
                    discount.ConcurrencyMode        = discountAndLine.ConcurrencyMode;
                    discount.PricingPriorityNumber  = discountAndLine.PricingPriorityNumber;
                    discount.CurrencyCode           = discountAndLine.CurrencyCode;
                    discount.DateValidationPeriodId = discountAndLine.ValidationPeriodId;
                    discount.DateValidationType     = (DateValidationType)discountAndLine.DateValidationType;
                    discount.DiscountType           = GetDiscountMethodType(discount.PeriodicDiscountType, discountAndLine.DiscountType);
                    discount.ValidFrom              = discountAndLine.ValidFromDate;
                    discount.ValidTo                = discountAndLine.ValidToDate;
                }

                return(discount);
            }
            internal bool IsOkayForMixAndMatchLeastExpensiveOneLineGroupPartialOptimizationAndFillupValueLookups(
                Dictionary <int, decimal> mixAndMatchRelativeValueLookup,
                List <int> itemGroupIndexListSortedByRelativePriceDescending,
                List <HashSet <int> > consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending,
                Dictionary <int, OfferDiscount> simpleDiscountOfferLookup,
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities)
            {
                // Special optimization for this overlapped group when there is
                //   only one mix and match
                //   with only one line group
                //   with least expensive deal price and %-off
                bool isOkay = this.MixAndMatchAndQuantityDiscounts.Count == 1;

                consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending.Clear();

                MixAndMatchDiscount mixAndMatchDiscount = null;

                if (isOkay)
                {
                    mixAndMatchDiscount = this.MixAndMatchAndQuantityDiscounts.First().Value as MixAndMatchDiscount;

                    isOkay = mixAndMatchDiscount != null;
                }

                if (isOkay)
                {
                    isOkay = mixAndMatchDiscount.LineGroupToNumberOfItemsMap.Count == 1;
                }

                if (isOkay)
                {
                    isOkay = mixAndMatchDiscount.DiscountType == DiscountMethodType.LeastExpensive &&
                             !mixAndMatchDiscount.IsLeastExpensiveAmountOff;
                }

                if (isOkay)
                {
                    var           lineGroupNumberOfItemsPair = mixAndMatchDiscount.LineGroupToNumberOfItemsMap.First();
                    string        lineGroup      = lineGroupNumberOfItemsPair.Key;
                    decimal       quantityNeeded = lineGroupNumberOfItemsPair.Value;
                    HashSet <int> mixAndMatchItemIndexGroupSet = null;
                    if (mixAndMatchDiscount.LineGroupToItemGroupIndexSetLookup.TryGetValue(lineGroup, out mixAndMatchItemIndexGroupSet))
                    {
                        foreach (int itemGroupIndex in mixAndMatchItemIndexGroupSet)
                        {
                            if (remainingQuantities[itemGroupIndex] > decimal.Zero)
                            {
                                // Move it to mix and match
                                decimal mixAndMatchValue = decimal.Zero;

                                decimal price = discountableItemGroups[itemGroupIndex].Price;
                                decimal totalPriceForDiscount = price * mixAndMatchDiscount.NumberOfLeastExpensiveLines;

                                if (mixAndMatchDiscount.DiscountPercentValue > decimal.Zero)
                                {
                                    mixAndMatchValue = DiscountBase.GetDiscountAmountForPercentageOff(totalPriceForDiscount, mixAndMatchDiscount.DiscountPercentValue);
                                }
                                else if (mixAndMatchDiscount.DiscountAmountValue == decimal.Zero && mixAndMatchDiscount.DealPriceValue > decimal.Zero)
                                {
                                    mixAndMatchValue = totalPriceForDiscount - mixAndMatchDiscount.DealPriceValue;
                                    if (mixAndMatchValue < decimal.Zero)
                                    {
                                        isOkay = false;
                                    }
                                }

                                if (!isOkay)
                                {
                                    break;
                                }

                                decimal simpleDiscountValue = decimal.Zero;
                                int     discountOfferCount  = 0;

                                // If an item has compounded discounts only, then we have already processed compounded discount offers, so we won't see them here.
                                //    And this won't affect algorithm.
                                // If we modify optimization before overlapped discounts, then this may not be true.
                                // See DiscountCalcuator.ReduceOverlappedOfferAndQuantityDiscountsPerItem.
                                foreach (KeyValuePair <string, DiscountBase> pair in this.OfferDiscounts)
                                {
                                    OfferDiscount offer = pair.Value as OfferDiscount;

                                    if (offer != null)
                                    {
                                        HashSet <decimal> discountLineNumberSetForItem = null;
                                        if (offer.ItemGroupIndexToDiscountLineNumberSetMap.TryGetValue(itemGroupIndex, out discountLineNumberSetForItem))
                                        {
                                            if (discountLineNumberSetForItem.Count >= 1)
                                            {
                                                decimal            discountLineNumber     = discountLineNumberSetForItem.First();
                                                RetailDiscountLine discountLineDefinition = offer.DiscountLines[discountLineNumber];

                                                // For now, it works for fully covered items.
                                                // Fully covered, as an example, buy 3 for $10, then if item price is $3, it won't get discount
                                                //     or buy 3 for $5 off, then if item price is $1, it won't get full discount.
                                                bool isFullyCovered = false;
                                                simpleDiscountValue = OfferDiscount.GetUnitDiscountAmountAndCheckWhetherItsFullyCovered(discountLineDefinition, price, out isFullyCovered) * quantityNeeded;
                                                if (!isFullyCovered)
                                                {
                                                    isOkay = false;
                                                }

                                                if (!isOkay)
                                                {
                                                    break;
                                                }

                                                simpleDiscountOfferLookup[itemGroupIndex] = offer;

                                                discountOfferCount++;
                                            }
                                        }
                                    }
                                }

                                // For now, we don't handle multiple discount offers.
                                // With optimization earlier, that's mostly the case. See DiscountCalcuator.ReduceOverlappedOfferAndQuantityDiscountsPerItem.
                                if (discountOfferCount > 1)
                                {
                                    isOkay = false;
                                }

                                if (!isOkay)
                                {
                                    break;
                                }

                                mixAndMatchRelativeValueLookup.Add(itemGroupIndex, mixAndMatchValue - simpleDiscountValue);
                            }
                        }
                    }
                }

                if (isOkay)
                {
                    // Now group items by relative price and price, and order them by relative price, in consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending.
                    // See tests in OverlappedDiscountsUnitTests.
                    itemGroupIndexListSortedByRelativePriceDescending.AddRange(mixAndMatchRelativeValueLookup.Keys);
                    ItemPriceComparer itemPriceComparer = new ItemPriceComparer(mixAndMatchRelativeValueLookup);
                    itemGroupIndexListSortedByRelativePriceDescending.Sort(itemPriceComparer.GetComparison());

                    decimal previousRelativePrice = decimal.Zero;
                    Dictionary <decimal, HashSet <int> > itemPriceToItemGroupIndexSetLookup = new Dictionary <decimal, HashSet <int> >();
                    for (int listIndex = 0; listIndex < itemGroupIndexListSortedByRelativePriceDescending.Count; listIndex++)
                    {
                        int     itemGroupIndex       = itemGroupIndexListSortedByRelativePriceDescending[listIndex];
                        decimal currentPrice         = discountableItemGroups[itemGroupIndex].Price;
                        decimal currentRelativePrice = mixAndMatchRelativeValueLookup[itemGroupIndex];
                        if (listIndex == 0)
                        {
                            previousRelativePrice = currentRelativePrice;
                            itemPriceToItemGroupIndexSetLookup.Add(currentPrice, new HashSet <int>()
                            {
                                itemGroupIndex
                            });
                        }
                        else
                        {
                            if (currentRelativePrice == previousRelativePrice)
                            {
                                HashSet <int> itemGroupIndexSetWithSameRelativePriceAndPrice = null;
                                if (itemPriceToItemGroupIndexSetLookup.TryGetValue(currentPrice, out itemGroupIndexSetWithSameRelativePriceAndPrice))
                                {
                                    itemGroupIndexSetWithSameRelativePriceAndPrice.Add(itemGroupIndex);
                                }
                                else
                                {
                                    itemPriceToItemGroupIndexSetLookup.Add(currentPrice, new HashSet <int>()
                                    {
                                        itemGroupIndex
                                    });
                                }
                            }

                            if (currentRelativePrice != previousRelativePrice)
                            {
                                foreach (KeyValuePair <decimal, HashSet <int> > pair in itemPriceToItemGroupIndexSetLookup)
                                {
                                    HashSet <int> itemGroupIndexSetWithSameRelativePriceAndPrice = pair.Value;

                                    consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending.Add(new HashSet <int>(itemGroupIndexSetWithSameRelativePriceAndPrice));
                                }

                                previousRelativePrice = currentRelativePrice;
                                itemPriceToItemGroupIndexSetLookup.Clear();
                                itemPriceToItemGroupIndexSetLookup.Add(currentPrice, new HashSet <int>()
                                {
                                    itemGroupIndex
                                });
                            }

                            if (listIndex == itemGroupIndexListSortedByRelativePriceDescending.Count - 1)
                            {
                                foreach (KeyValuePair <decimal, HashSet <int> > pair in itemPriceToItemGroupIndexSetLookup)
                                {
                                    HashSet <int> itemGroupIndexSetWithSameRelativePriceAndPrice = pair.Value;
                                    consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending.Add(new HashSet <int>(itemGroupIndexSetWithSameRelativePriceAndPrice));
                                }

                                itemPriceToItemGroupIndexSetLookup.Clear();
                            }
                        }
                    }
                }

                return(isOkay);
            }
            internal bool IsOkayForMixAndMatchOneLineGroupOptimizationAndFillupValueLookups(
                Dictionary <int, decimal> mixAndMatchRelativeValueLookup,
                Dictionary <int, OfferDiscount> simpleDiscountOfferLookup,
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities)
            {
                // Special optimization for this overlapped group when there is
                //   only one mix and match
                //   with only one line group
                //   with any of deal price, $ off, % off, or least expensive $-off.
                bool isOkay = this.MixAndMatchAndQuantityDiscounts.Count == 1;

                MixAndMatchDiscount mixAndMatchDiscount = null;

                if (isOkay)
                {
                    mixAndMatchDiscount = this.MixAndMatchAndQuantityDiscounts.First().Value as MixAndMatchDiscount;

                    isOkay = mixAndMatchDiscount != null;
                }

                if (isOkay)
                {
                    isOkay = mixAndMatchDiscount.DiscountType == DiscountMethodType.DealPrice ||
                             mixAndMatchDiscount.DiscountType == DiscountMethodType.DiscountPercent ||
                             mixAndMatchDiscount.DiscountType == DiscountMethodType.DiscountAmount ||
                             mixAndMatchDiscount.IsLeastExpensiveAmountOff;
                }

                if (isOkay)
                {
                    isOkay = mixAndMatchDiscount.LineGroupToNumberOfItemsMap.Count == 1;
                }

                if (isOkay)
                {
                    // Mix and match example: buy 3 for $10.
                    // For each item, calculate relative value of mix and match against discount offer with quantity 3 as a group.
                    //     relative value of item A = M(AAA) - 3S(A)
                    // See DiscountCalculator.TryOptimizeForOneMixAndMatchWithOneLineGroup
                    var           lineGroupNumberOfItemsPair = mixAndMatchDiscount.LineGroupToNumberOfItemsMap.First();
                    string        lineGroup      = lineGroupNumberOfItemsPair.Key;
                    decimal       quantityNeeded = lineGroupNumberOfItemsPair.Value;
                    HashSet <int> mixAndMatchItemIndexGroupSet = null;
                    if (mixAndMatchDiscount.LineGroupToItemGroupIndexSetLookup.TryGetValue(lineGroup, out mixAndMatchItemIndexGroupSet))
                    {
                        foreach (int itemGroupIndex in mixAndMatchItemIndexGroupSet)
                        {
                            if (remainingQuantities[itemGroupIndex] > decimal.Zero)
                            {
                                // Move it to mix and match
                                decimal mixAndMatchValue = decimal.Zero;

                                decimal price      = discountableItemGroups[itemGroupIndex].Price;
                                decimal totalPrice = price * quantityNeeded;
                                switch (mixAndMatchDiscount.DiscountType)
                                {
                                case DiscountMethodType.DealPrice:
                                    mixAndMatchValue = totalPrice - mixAndMatchDiscount.DealPriceValue;
                                    isOkay           = mixAndMatchValue >= decimal.Zero;
                                    break;

                                case DiscountMethodType.DiscountAmount:
                                    mixAndMatchValue = mixAndMatchDiscount.DiscountAmountValue;
                                    isOkay           = totalPrice >= mixAndMatchDiscount.DiscountAmountValue;
                                    break;

                                case DiscountMethodType.DiscountPercent:
                                    mixAndMatchValue = DiscountBase.GetDiscountAmountForPercentageOff(totalPrice, mixAndMatchDiscount.DiscountPercentValue);
                                    break;

                                default:
                                    isOkay = false;
                                    if (mixAndMatchDiscount.IsLeastExpensiveAmountOff && price >= mixAndMatchDiscount.DiscountAmountValue)
                                    {
                                        isOkay           = true;
                                        mixAndMatchValue = mixAndMatchDiscount.DiscountAmountValue;
                                    }

                                    break;
                                }

                                if (!isOkay)
                                {
                                    break;
                                }

                                int     discountOfferCount  = 0;
                                decimal simpleDiscountValue = decimal.Zero;
                                foreach (KeyValuePair <string, DiscountBase> pair in this.OfferDiscounts)
                                {
                                    OfferDiscount offer = pair.Value as OfferDiscount;

                                    if (offer != null)
                                    {
                                        HashSet <decimal> discountLineNumberSetForItem = null;
                                        if (offer.ItemGroupIndexToDiscountLineNumberSetMap.TryGetValue(itemGroupIndex, out discountLineNumberSetForItem))
                                        {
                                            if (discountLineNumberSetForItem.Count >= 1)
                                            {
                                                decimal            discountLineNumber     = discountLineNumberSetForItem.First();
                                                RetailDiscountLine discountLineDefinition = offer.DiscountLines[discountLineNumber];

                                                // For now, it works for fully covered items.
                                                // Fully covered, as an example, buy 3 for $10, then if item price is $3, it won't get discount
                                                //     or buy 3 for $5 off, then if item price is $1, it won't get full discount.
                                                bool isFullyCovered = false;
                                                simpleDiscountValue = OfferDiscount.GetUnitDiscountAmountAndCheckWhetherItsFullyCovered(discountLineDefinition, price, out isFullyCovered) * quantityNeeded;
                                                if (!isFullyCovered)
                                                {
                                                    isOkay = false;
                                                }

                                                if (!isOkay)
                                                {
                                                    break;
                                                }

                                                simpleDiscountOfferLookup[itemGroupIndex] = offer;
                                                discountOfferCount++;
                                            }
                                        }
                                    }
                                }

                                // For now, we don't handle multiple discount offers.
                                // With optimization earlier, that's mostly the case. See DiscountCalcuator.ReduceOverlappedOfferAndQuantityDiscountsPerItem.
                                if (discountOfferCount > 1)
                                {
                                    isOkay = false;
                                }

                                if (!isOkay)
                                {
                                    break;
                                }

                                mixAndMatchRelativeValueLookup.Add(itemGroupIndex, mixAndMatchValue - simpleDiscountValue);
                            }
                        }
                    }
                }

                return(isOkay);
            }
            /// <summary>
            /// Converts retail discount data from database to discount object.
            /// </summary>
            /// <param name="retailDiscount">Retail discount data from database.</param>
            /// <returns>Discount object.</returns>
            /// <remarks>This is private. Exposed as internal for test.</remarks>
            internal static DiscountBase ConvertRetailDiscountToDiscountBase(RetailDiscount retailDiscount)
            {
                DiscountBase        discount    = null;
                OfferDiscount       offer       = null;
                MixAndMatchDiscount mixAndMatch = null;
                MultipleBuyDiscount multipleBuy = null;
                ThresholdDiscount   threshold   = null;

                switch (retailDiscount.PeriodicDiscountType)
                {
                case PeriodicDiscountOfferType.Offer:
                    offer    = new OfferDiscount(retailDiscount.ValidationPeriod);
                    discount = offer;
                    break;

                case PeriodicDiscountOfferType.MixAndMatch:
                    mixAndMatch = new MixAndMatchDiscount(retailDiscount.ValidationPeriod);
                    mixAndMatch.DealPriceValue              = retailDiscount.MixAndMatchDealPrice;
                    mixAndMatch.DiscountAmountValue         = retailDiscount.MixAndMatchDiscountAmount;
                    mixAndMatch.DiscountPercentValue        = retailDiscount.MixAndMatchDiscountPercent;
                    mixAndMatch.NumberOfLeastExpensiveLines = retailDiscount.MixAndMatchNumberOfLeastExpensiveLines;
                    mixAndMatch.LeastExpensiveMode          = retailDiscount.LeastExpensiveMode;
                    mixAndMatch.NumberOfTimesApplicable     = retailDiscount.MixAndMatchNumberOfTimeApplicable;
                    foreach (RetailDiscountLine mixMatchLine in retailDiscount.DiscountLines)
                    {
                        if (!mixAndMatch.LineGroupToNumberOfItemsMap.ContainsKey(mixMatchLine.MixAndMatchLineGroup))
                        {
                            mixAndMatch.LineGroupToNumberOfItemsMap.Add(mixMatchLine.MixAndMatchLineGroup, mixMatchLine.MixAndMatchLineNumberOfItemsNeeded);
                        }
                    }

                    discount = mixAndMatch;
                    break;

                case PeriodicDiscountOfferType.MultipleBuy:
                    multipleBuy = new MultipleBuyDiscount(retailDiscount.ValidationPeriod);
                    multipleBuy.QuantityDiscountLevels.AddRange(retailDiscount.MultibuyQuantityTiers);
                    discount = multipleBuy;
                    break;

                case PeriodicDiscountOfferType.Threshold:
                    threshold = new ThresholdDiscount(retailDiscount.ValidationPeriod);
                    threshold.ShouldCountNonDiscountItems = retailDiscount.ShouldCountNonDiscountItems != 0;
                    threshold.ThresholdDiscountTiers.AddRange(retailDiscount.ThresholdDiscountTiers);
                    discount = threshold;
                    break;
                }

                if (discount != null)
                {
                    discount.IsCategoryToProductOrVariantIdsMapSet = false;

                    discount.OfferId                = retailDiscount.OfferId;
                    discount.OfferName              = retailDiscount.Name;
                    discount.PeriodicDiscountType   = retailDiscount.PeriodicDiscountType;
                    discount.IsDiscountCodeRequired = retailDiscount.IsDiscountCodeRequired;
                    discount.ConcurrencyMode        = retailDiscount.ConcurrencyMode;
                    discount.PricingPriorityNumber  = retailDiscount.PricingPriorityNumber;
                    discount.CurrencyCode           = retailDiscount.CurrencyCode;
                    discount.DateValidationPeriodId = retailDiscount.ValidationPeriodId;
                    discount.DateValidationType     = (DateValidationType)retailDiscount.DateValidationType;
                    discount.DiscountType           = GetDiscountMethodType(discount.PeriodicDiscountType, retailDiscount.DiscountType);
                    discount.ValidFrom              = retailDiscount.ValidFromDate;
                    discount.ValidTo                = retailDiscount.ValidToDate;

                    foreach (RetailDiscountLine discountLine in retailDiscount.DiscountLines)
                    {
                        discountLine.DiscountMethod = (int)GetLineDiscountOfferMethod(discount.PeriodicDiscountType, discount.DiscountType, discountLine.DiscountMethod, discountLine.MixAndMatchLineSpecificDiscountType);
                        discount.DiscountLines.Add(discountLine.DiscountLineNumber, discountLine);
                    }

                    foreach (RetailDiscountPriceGroup priceGroup in retailDiscount.PriceGroups)
                    {
                        discount.PriceDiscountGroupIds.Add(priceGroup.PriceGroupId);
                    }

                    foreach (DiscountCode discountCode in retailDiscount.DiscountCodes)
                    {
                        discount.DiscountCodes.Add(discountCode.Code);
                        discount.DiscountCodes.Add(discountCode.Barcode);
                    }
                }

                return(discount);
            }