public OrganizedShoppingCartItem(ShoppingCartItem item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			Item = item;	// must not be null
			ChildItems = new List<OrganizedShoppingCartItem>();
			BundleItemData = new ProductBundleItemData(item.BundleItem);
		}
        protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationContext context, ProductBundleItemData bundleItem, IEnumerable<ProductBundleItemData> bundleItems)
        {
            var taxRate = decimal.Zero;
            var attributesTotalPriceBase = decimal.Zero;
            var preSelectedPriceAdjustmentBase = decimal.Zero;
            var isBundle = (product.ProductType == ProductType.BundledProduct);
            var isBundleItemPricing = (bundleItem != null && bundleItem.Item.BundleProduct.BundlePerItemPricing);
            var isBundlePricing = (bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing);
            var bundleItemId = (bundleItem == null ? 0 : bundleItem.Item.Id);

            var selectedAttributes = new NameValueCollection();
            var selectedAttributeValues = new List<ProductVariantAttributeValue>();
            var attributes = context.Attributes.Load(product.Id);

            // 1. fill selectedAttributes with initially selected attributes
            foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Count > 0 && x.ShouldHaveValues()))
            {
                int preSelectedValueId = 0;
                ProductVariantAttributeValue defaultValue = null;
                var selectedValueIds = new List<int>();
                var pvaValues = attribute.ProductVariantAttributeValues;

                foreach (var pvaValue in pvaValues)
                {
                    ProductBundleItemAttributeFilter attributeFilter = null;

                    if (bundleItem.FilterOut(pvaValue, out attributeFilter))
                        continue;

                    if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected)
                        preSelectedValueId = attributeFilter.AttributeValueId;

                    if (!isBundlePricing && pvaValue.IsPreSelected)
                    {
                        decimal attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue);
                        decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate);

                        preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase);
                    }
                }

                // value pre-selected by a bundle item filter discards the default pre-selection
                if (preSelectedValueId != 0 && (defaultValue = pvaValues.FirstOrDefault(x => x.Id == preSelectedValueId)) != null)
                {
                    //defaultValue.IsPreSelected = true;
                    selectedAttributeValues.Add(defaultValue);
                    selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId);
                }
                else
                {
                    foreach (var value in pvaValues.Where(x => x.IsPreSelected))
                    {
                        selectedAttributeValues.Add(value);
                        selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, value.Id, product.Id, bundleItemId);
                    }
                }
            }

            // 2. find attribute combination for selected attributes and merge it
            if (!isBundle && selectedAttributes.Count > 0)
            {
                string attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _services.Localization,
                    _downloadService, _catalogSettings, _httpRequestBase, new List<string>(), true, bundleItemId);

                var combinations = context.AttributeCombinations.Load(product.Id);

                var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml, attributes));

                if (selectedCombination != null && selectedCombination.IsActive && selectedCombination.Price.HasValue)
                {
                    product.MergedDataValues = new Dictionary<string, object> { { "Price", selectedCombination.Price.Value } };
                }
            }

            if (_catalogSettings.EnableDynamicPriceUpdate && !isBundlePricing)
            {
                if (selectedAttributeValues.Count > 0)
                {
                    selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x));
                }
                else
                {
                    attributesTotalPriceBase = preSelectedPriceAdjustmentBase;
                }
            }

            if (bundleItem != null)
            {
                bundleItem.AdditionalCharge = attributesTotalPriceBase;
            }

            var result = GetFinalPrice(product, bundleItems, _services.WorkContext.CurrentCustomer, attributesTotalPriceBase, true, 1, bundleItem, context);
            return result;
        }
        public virtual decimal GetFinalPrice(
			Product product, 
			IEnumerable<ProductBundleItemData> bundleItems,
			Customer customer, 
			decimal additionalCharge, 
			bool includeDiscounts, 
			int quantity, 
			ProductBundleItemData bundleItem = null,
			PriceCalculationContext context = null)
        {
            if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)
            {
                decimal result = decimal.Zero;
                var items = bundleItems ?? _productService.GetBundleItems(product.Id);

                foreach (var itemData in items.Where(x => x.IsValid()))
                {
                    decimal itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context);

                    result = result + decimal.Multiply(itemPrice, itemData.Item.Quantity);
                }

                return (result < decimal.Zero ? decimal.Zero : result);
            }
            return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, quantity, bundleItem, context);
        }
        public virtual decimal GetFinalPrice(
			Product product, 
            Customer customer,
            decimal additionalCharge, 
            bool includeDiscounts, 
            int quantity,
			ProductBundleItemData bundleItem = null,
			PriceCalculationContext context = null)
        {
            //initial price
            decimal result = product.Price;

            //special price
            var specialPrice = GetSpecialPrice(product);
            if (specialPrice.HasValue)
                result = specialPrice.Value;

            //tier prices
            if (product.HasTierPrices && !bundleItem.IsValid())
            {
                decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity, context);
                if (tierPrice.HasValue)
                    result = Math.Min(result, tierPrice.Value);
            }

            //discount + additional charge
            if (includeDiscounts)
            {
                Discount appliedDiscount = null;
                decimal discountAmount = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscount, bundleItem, context);
                result = result + additionalCharge - discountAmount;
            }
            else
            {
                result = result + additionalCharge;
            }

            if (result < decimal.Zero)
                result = decimal.Zero;

            return result;
        }
        public virtual decimal GetDiscountAmount(
			Product product,
            Customer customer,
            decimal additionalCharge,
            int quantity,
            out Discount appliedDiscount,
			ProductBundleItemData bundleItem = null,
			PriceCalculationContext context = null)
        {
            appliedDiscount = null;
            decimal appliedDiscountAmount = decimal.Zero;
            decimal finalPriceWithoutDiscount = decimal.Zero;

            if (bundleItem.IsValid())
            {
                if (bundleItem.Item.Discount.HasValue && bundleItem.Item.BundleProduct.BundlePerItemPricing)
                {
                    appliedDiscount = new Discount
                    {
                        UsePercentage = bundleItem.Item.DiscountPercentage,
                        DiscountPercentage = bundleItem.Item.Discount.Value,
                        DiscountAmount = bundleItem.Item.Discount.Value
                    };

                    finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem, context);
                    appliedDiscountAmount = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount);
                }
            }
            else
            {
                // dont't apply when customer entered price or discounts should be ignored completely
                if (product.CustomerEntersPrice || _catalogSettings.IgnoreDiscounts)
                {
                    return appliedDiscountAmount;
                }

                var allowedDiscounts = GetAllowedDiscounts(product, customer, context);
                if (allowedDiscounts.Count == 0)
                {
                    return appliedDiscountAmount;
                }

                finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem, context);
                appliedDiscount = allowedDiscounts.GetPreferredDiscount(finalPriceWithoutDiscount);

                if (appliedDiscount != null)
                {
                    appliedDiscountAmount = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount);
                }
            }

            return appliedDiscountAmount;
        }
        /// <summary>
        /// Gets discount amount
        /// </summary>
		/// <param name="product">Product</param>
        /// <param name="customer">The customer</param>
        /// <param name="additionalCharge">Additional charge</param>
        /// <param name="quantity">Product quantity</param>
        /// <param name="appliedDiscount">Applied discount</param>
		/// <param name="bundleItem">A product bundle item</param>
        /// <returns>Discount amount</returns>
        public virtual decimal GetDiscountAmount(Product product,
            Customer customer,
            decimal additionalCharge,
            int quantity,
            out Discount appliedDiscount,
			ProductBundleItemData bundleItem = null)
        {
            appliedDiscount = null;
            decimal appliedDiscountAmount = decimal.Zero;

			if (bundleItem.IsValid())
			{
				if (bundleItem.Item.Discount.HasValue && bundleItem.Item.BundleProduct.BundlePerItemPricing)
				{
					appliedDiscount = new Discount()
					{
						UsePercentage = bundleItem.Item.DiscountPercentage,
						DiscountPercentage = bundleItem.Item.Discount.Value,
						DiscountAmount = bundleItem.Item.Discount.Value
					};
				}
			}
			else
			{
				//we don't apply discounts to products with price entered by a customer
				if (product.CustomerEntersPrice)
					return appliedDiscountAmount;

				appliedDiscount = GetPreferredDiscount(product, customer, additionalCharge, quantity);
			}

            if (appliedDiscount != null)
            {
                decimal finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem);
                appliedDiscountAmount = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount);
            }

            return appliedDiscountAmount;
        }
        protected virtual decimal GetPreselectedPrice(Product product, ProductBundleItemData bundleItem, IList<ProductBundleItemData> bundleItems)
        {
            var taxRate = decimal.Zero;
            var attributesTotalPriceBase = decimal.Zero;
            var preSelectedPriceAdjustmentBase = decimal.Zero;
            var isBundle = (product.ProductType == ProductType.BundledProduct);
            var isBundleItemPricing = (bundleItem != null && bundleItem.Item.BundleProduct.BundlePerItemPricing);
            var isBundlePricing = (bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing);
            var bundleItemId = (bundleItem == null ? 0 : bundleItem.Item.Id);
            var attributes = (isBundle ? new List<ProductVariantAttribute>() : _productAttributeService.GetProductVariantAttributesByProductId(product.Id));
            var selectedAttributes = new NameValueCollection();
            List<ProductVariantAttributeValue> selectedAttributeValues = null;

            foreach (var attribute in attributes)
            {
                int preSelectedValueId = 0;
                ProductVariantAttributeValue defaultValue = null;

                if (attribute.ShouldHaveValues())
                {
                    var pvaValues = _productAttributeService.GetProductVariantAttributeValues(attribute.Id);
                    if (pvaValues.Count == 0)
                        continue;

                    foreach (var pvaValue in pvaValues)
                    {
                        ProductBundleItemAttributeFilter attributeFilter = null;

                        if (bundleItem.FilterOut(pvaValue, out attributeFilter))
                            continue;

                        if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected)
                            preSelectedValueId = attributeFilter.AttributeValueId;

                        if (!isBundlePricing && pvaValue.IsPreSelected)
                        {
                            decimal attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue);
                            decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate);

                            preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase);
                        }
                    }

                    // value pre-selected by a bundle item filter discards the default pre-selection
                    if (preSelectedValueId != 0 && (defaultValue = pvaValues.FirstOrDefault(x => x.Id == preSelectedValueId)) != null)
                        defaultValue.IsPreSelected = true;

                    if (defaultValue == null)
                        defaultValue = pvaValues.FirstOrDefault(x => x.IsPreSelected);

                    if (defaultValue == null && attribute.IsRequired)
                        defaultValue = pvaValues.First();

                    if (defaultValue != null)
                        selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId);
                }
            }

            if (!isBundle && selectedAttributes.Count > 0)
            {
                string attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _commonServices.Localization,
                    _downloadService, _catalogSettings, _httpRequestBase, new List<string>(), true, bundleItemId);

                selectedAttributeValues = _productAttributeParser.ParseProductVariantAttributeValues(attributeXml).ToList();

                var combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id);

                var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml));

                if (selectedCombination != null && selectedCombination.IsActive)
                    product.MergeWithCombination(selectedCombination);
            }

            if (_catalogSettings.EnableDynamicPriceUpdate && !isBundlePricing)
            {
                if (selectedAttributeValues != null)
                {
                    selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x));
                }
                else
                {
                    attributesTotalPriceBase = preSelectedPriceAdjustmentBase;
                }
            }

            if (bundleItem != null)
            {
                bundleItem.AdditionalCharge = attributesTotalPriceBase;
            }

            var result = GetFinalPrice(product, bundleItems, _commonServices.WorkContext.CurrentCustomer, attributesTotalPriceBase, true, 1, bundleItem);
            return result;
        }
        /// <param name="selectedAttributes">Attributes explicitly selected by user or by query string.</param>
        public ProductDetailsModel PrepareProductDetailModel(
			ProductDetailsModel model,
			Product product,
			bool isAssociatedProduct = false,
			ProductBundleItemData productBundleItem = null,
			IList<ProductBundleItemData> productBundleItems = null,
			NameValueCollection selectedAttributes = null,
			int selectedQuantity = 1)
        {
            if (product == null)
                throw new ArgumentNullException("product");

            if (model == null)
                throw new ArgumentNullException("model");

            if (selectedAttributes == null)
                selectedAttributes = new NameValueCollection();

            var store = _services.StoreContext.CurrentStore;
            var customer = _services.WorkContext.CurrentCustomer;
            var currency = _services.WorkContext.WorkingCurrency;

            decimal preSelectedPriceAdjustmentBase = decimal.Zero;
            decimal preSelectedWeightAdjustment = decimal.Zero;
            bool displayPrices = _services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices);
            bool isBundle = (product.ProductType == ProductType.BundledProduct);
            bool isBundleItemPricing = (productBundleItem != null && productBundleItem.Item.BundleProduct.BundlePerItemPricing);
            bool isBundlePricing = (productBundleItem != null && !productBundleItem.Item.BundleProduct.BundlePerItemPricing);
            int bundleItemId = (productBundleItem == null ? 0 : productBundleItem.Item.Id);

            bool hasSelectedAttributesValues = false;
            bool hasSelectedAttributes = (selectedAttributes.Count > 0);
            List<ProductVariantAttributeValue> selectedAttributeValues = null;

            var variantAttributes = (isBundle ? new List<ProductVariantAttribute>() : _productAttributeService.GetProductVariantAttributesByProductId(product.Id));

            model.ProductPrice.DynamicPriceUpdate = _catalogSettings.EnableDynamicPriceUpdate;
            model.ProductPrice.BundleItemShowBasePrice = _catalogSettings.BundleItemShowBasePrice;

            if (!model.ProductPrice.DynamicPriceUpdate)
                selectedQuantity = 1;

            #region Product attributes

            if (!isBundle)		// bundles doesn't have attributes
            {
                foreach (var attribute in variantAttributes)
                {
                    var pvaModel = new ProductDetailsModel.ProductVariantAttributeModel
                    {
                        Id = attribute.Id,
                        ProductId = attribute.ProductId,
                        BundleItemId = bundleItemId,
                        ProductAttributeId = attribute.ProductAttributeId,
                        Alias = attribute.ProductAttribute.Alias,
                        Name = attribute.ProductAttribute.GetLocalized(x => x.Name),
                        Description = attribute.ProductAttribute.GetLocalized(x => x.Description),
                        TextPrompt = attribute.TextPrompt,
                        IsRequired = attribute.IsRequired,
                        AttributeControlType = attribute.AttributeControlType,
                        AllowedFileExtensions = _catalogSettings.FileUploadAllowedExtensions
                    };

                    if (attribute.AttributeControlType == AttributeControlType.Datepicker)
                    {
                        if (pvaModel.Alias.HasValue() && RegularExpressions.IsYearRange.IsMatch(pvaModel.Alias))
                        {
                            var match = RegularExpressions.IsYearRange.Match(pvaModel.Alias);
                            pvaModel.BeginYear = match.Groups[1].Value.ToInt();
                            pvaModel.EndYear = match.Groups[2].Value.ToInt();
                        }

                        if (hasSelectedAttributes)
                        {
                            var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id);
                            var day = selectedAttributes[attributeKey + "_day"].ToInt();
                            var month = selectedAttributes[attributeKey + "_month"].ToInt();
                            var year = selectedAttributes[attributeKey + "_year"].ToInt();
                            if (day > 0 && month > 0 && year > 0)
                            {
                                pvaModel.SelectedDay = day;
                                pvaModel.SelectedMonth = month;
                                pvaModel.SelectedYear = year;
                            }
                        }
                    }
                    else if (attribute.AttributeControlType == AttributeControlType.TextBox || attribute.AttributeControlType == AttributeControlType.MultilineTextbox)
                    {
                        if (hasSelectedAttributes)
                        {
                            var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id);
                            pvaModel.TextValue = selectedAttributes[attributeKey];
                        }
                    }

                    var preSelectedValueId = 0;
                    var pvaValues = (attribute.ShouldHaveValues() ? _productAttributeService.GetProductVariantAttributeValues(attribute.Id) : new List<ProductVariantAttributeValue>());

                    foreach (var pvaValue in pvaValues)
                    {
                        ProductBundleItemAttributeFilter attributeFilter = null;

                        if (productBundleItem.FilterOut(pvaValue, out attributeFilter))
                            continue;

                        if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected)
                            preSelectedValueId = attributeFilter.AttributeValueId;

                        var linkedProduct = _productService.GetProductById(pvaValue.LinkedProductId);

                        var pvaValueModel = new ProductDetailsModel.ProductVariantAttributeValueModel();
                        pvaValueModel.Id = pvaValue.Id;
                        pvaValueModel.Name = pvaValue.GetLocalized(x => x.Name);
                        pvaValueModel.Alias = pvaValue.Alias;
                        pvaValueModel.ColorSquaresRgb = pvaValue.ColorSquaresRgb; //used with "Color squares" attribute type
                        pvaValueModel.IsPreSelected = pvaValue.IsPreSelected;

                        if (linkedProduct != null && linkedProduct.VisibleIndividually)
                            pvaValueModel.SeName = linkedProduct.GetSeName();

                        if (hasSelectedAttributes)
                            pvaValueModel.IsPreSelected = false;	// explicitly selected always discards pre-selected by merchant

                        // display price if allowed
                        if (displayPrices && !isBundlePricing)
                        {
                            decimal taxRate = decimal.Zero;
                            decimal attributeValuePriceAdjustment = _priceCalculationService.GetProductVariantAttributeValuePriceAdjustment(pvaValue);
                            decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate);
                            decimal priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, currency);

                            if (priceAdjustmentBase > decimal.Zero)
                                pvaValueModel.PriceAdjustment = "+" + _priceFormatter.FormatPrice(priceAdjustment, true, false);
                            else if (priceAdjustmentBase < decimal.Zero)
                                pvaValueModel.PriceAdjustment = "-" + _priceFormatter.FormatPrice(-priceAdjustment, true, false);

                            if (pvaValueModel.IsPreSelected)
                            {
                                preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase);
                                preSelectedWeightAdjustment = decimal.Add(preSelectedWeightAdjustment, pvaValue.WeightAdjustment);
                            }

                            if (_catalogSettings.ShowLinkedAttributeValueQuantity && pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage)
                            {
                                pvaValueModel.QuantityInfo = pvaValue.Quantity;
                            }

                            pvaValueModel.PriceAdjustmentValue = priceAdjustment;
                        }

                        if (!_catalogSettings.ShowVariantCombinationPriceAdjustment)
                        {
                            pvaValueModel.PriceAdjustment = "";
                        }

                        if (_catalogSettings.ShowLinkedAttributeValueImage && pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage)
                        {
                            var linkagePicture = _pictureService.GetPicturesByProductId(pvaValue.LinkedProductId, 1).FirstOrDefault();
                            if (linkagePicture != null)
                                pvaValueModel.ImageUrl = _pictureService.GetPictureUrl(linkagePicture, _mediaSettings.VariantValueThumbPictureSize, false);
                        }

                        pvaModel.Values.Add(pvaValueModel);
                    }

                    // we need selected attributes to get initially displayed combination images
                    if (!hasSelectedAttributes)
                    {
                        ProductDetailsModel.ProductVariantAttributeValueModel defaultValue = null;

                        if (preSelectedValueId != 0)	// value pre-selected by a bundle item filter discards the default pre-selection
                        {
                            pvaModel.Values.Each(x => x.IsPreSelected = false);

                            if ((defaultValue = pvaModel.Values.FirstOrDefault(v => v.Id == preSelectedValueId)) != null)
                            {
                                defaultValue.IsPreSelected = true;
                                selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId);
                            }
                        }

                        if (defaultValue == null)
                        {
                            foreach (var value in pvaModel.Values.Where(x => x.IsPreSelected))
                            {
                                selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, value.Id, product.Id, bundleItemId);
                            }
                        }

                        //if (defaultValue == null)
                        //	defaultValue = pvaModel.Values.FirstOrDefault(v => v.IsPreSelected);

                        //if (defaultValue != null)
                        //	selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId);
                    }

                    model.ProductVariantAttributes.Add(pvaModel);
                }
            }

            #endregion

            #region Attribute combinations

            if (!isBundle)
            {
                if (selectedAttributes.Count > 0)
                {
                    // merge with combination data if there's a match
                    var warnings = new List<string>();
                    string attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, variantAttributes, _productAttributeParser, _localizationService,
                        _downloadService, _catalogSettings, _httpRequest, warnings, true, bundleItemId);

                    selectedAttributeValues = _productAttributeParser.ParseProductVariantAttributeValues(attributeXml).ToList();
                    hasSelectedAttributesValues = (selectedAttributeValues.Count > 0);

                    if (isBundlePricing)
                    {
                        model.AttributeInfo = _productAttributeFormatter.FormatAttributes(product, attributeXml, customer,
                            renderPrices: false, renderGiftCardAttributes: false, allowHyperlinks: false);
                    }

                    model.SelectedCombination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributeXml);

                    if (model.SelectedCombination != null && model.SelectedCombination.IsActive == false)
                    {
                        model.IsAvailable = false;
                        model.StockAvailability = T("Products.Availability.IsNotActive");
                    }

                    product.MergeWithCombination(model.SelectedCombination);

                    // mark explicitly selected as pre-selected
                    foreach (var attribute in model.ProductVariantAttributes)
                    {
                        foreach (var value in attribute.Values)
                        {
                            if (selectedAttributeValues.FirstOrDefault(v => v.Id == value.Id) != null)
                                value.IsPreSelected = true;

                            if (!_catalogSettings.ShowVariantCombinationPriceAdjustment)
                                value.PriceAdjustment = "";
                        }
                    }
                }
            }

            #endregion

            #region Properties

            if ((productBundleItem != null && !productBundleItem.Item.BundleProduct.BundlePerItemShoppingCart) ||
                (product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes && !hasSelectedAttributesValues))
            {
                // cases where stock inventory is not functional. determined by what ShoppingCartService.GetStandardWarnings and ProductService.AdjustInventory is not handling.
                model.IsAvailable = true;
                var hasAttributeCombinations = _services.DbContext.QueryForCollection(product, (Product p) => p.ProductVariantAttributeCombinations).Any();
                model.StockAvailability = !hasAttributeCombinations ? product.FormatStockMessage(_localizationService) : "";
            }
            else if (model.IsAvailable)
            {
                model.IsAvailable = product.IsAvailableByStock();
                model.StockAvailability = product.FormatStockMessage(_localizationService);
            }

            model.Id = product.Id;
            model.Name = product.GetLocalized(x => x.Name);
            model.ShowSku = _catalogSettings.ShowProductSku;
            model.Sku = product.Sku;
            model.ShortDescription = product.GetLocalized(x => x.ShortDescription);
            model.FullDescription = product.GetLocalized(x => x.FullDescription);
            model.MetaKeywords = product.GetLocalized(x => x.MetaKeywords);
            model.MetaDescription = product.GetLocalized(x => x.MetaDescription);
            model.MetaTitle = product.GetLocalized(x => x.MetaTitle);
            model.SeName = product.GetSeName();
            model.ShowManufacturerPartNumber = _catalogSettings.ShowManufacturerPartNumber;
            model.ManufacturerPartNumber = product.ManufacturerPartNumber;
            model.ShowDimensions = _catalogSettings.ShowDimensions;
            model.ShowWeight = _catalogSettings.ShowWeight;
            model.ShowGtin = _catalogSettings.ShowGtin;
            model.Gtin = product.Gtin;
            model.HasSampleDownload = product.IsDownload && product.HasSampleDownload;
            model.IsCurrentCustomerRegistered = customer.IsRegistered();
            model.IsBasePriceEnabled = product.BasePriceEnabled;
            model.BasePriceInfo = product.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, currency);
            model.ShowLegalInfo = _taxSettings.ShowLegalHintsInProductDetails;
            model.BundleTitleText = product.GetLocalized(x => x.BundleTitleText);
            model.BundlePerItemPricing = product.BundlePerItemPricing;
            model.BundlePerItemShipping = product.BundlePerItemShipping;
            model.BundlePerItemShoppingCart = product.BundlePerItemShoppingCart;

            //_taxSettings.TaxDisplayType == TaxDisplayType.ExcludingTax;

            var taxDisplayType = _services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id);
            string taxInfo = T(taxDisplayType == TaxDisplayType.IncludingTax ? "Tax.InclVAT" : "Tax.ExclVAT");

            var defaultTaxRate = "";
            var taxrate = Convert.ToString(_taxService.GetTaxRate(product, customer));
            if (_taxSettings.DisplayTaxRates && !taxrate.Equals("0", StringComparison.InvariantCultureIgnoreCase))
            {
                defaultTaxRate = "({0}%)".FormatWith(taxrate);
            }

            var additionalShippingCosts = String.Empty;
            var addShippingPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.AdditionalShippingCharge, currency);

            if (addShippingPrice > 0)
            {
                additionalShippingCosts = T("Common.AdditionalShippingSurcharge").Text.FormatInvariant(_priceFormatter.FormatPrice(addShippingPrice, true, false)) + ", ";
            }

            if (!product.IsShipEnabled || (addShippingPrice == 0 && product.IsFreeShipping))
            {
                model.LegalInfo += "{0} {1}, {2}".FormatInvariant(
                    product.IsTaxExempt ? "" : taxInfo,
                    product.IsTaxExempt ? "" : defaultTaxRate,
                    T("Common.FreeShipping"));
            }
            else
            {
                var topic = _topicService.Value.GetTopicBySystemName("ShippingInfo", store.Id);

                if (topic == null)
                {
                    model.LegalInfo = T("Tax.LegalInfoProductDetail2",
                        product.IsTaxExempt ? "" : taxInfo,
                        product.IsTaxExempt ? "" : defaultTaxRate,
                        additionalShippingCosts);
                }
                else
                {
                    model.LegalInfo = T("Tax.LegalInfoProductDetail",
                        product.IsTaxExempt ? "" : taxInfo,
                        product.IsTaxExempt ? "" : defaultTaxRate,
                        additionalShippingCosts,
                        _urlHelper.RouteUrl("Topic", new { SystemName = "shippinginfo" }));
                }
            }

            var dimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name;

            model.WeightValue = product.Weight;
            if (!isBundle)
            {
                if (selectedAttributeValues != null)
                {
                    foreach (var attributeValue in selectedAttributeValues)
                        model.WeightValue = decimal.Add(model.WeightValue, attributeValue.WeightAdjustment);
                }
                else
                {
                    model.WeightValue = decimal.Add(model.WeightValue, preSelectedWeightAdjustment);
                }
            }

            model.Weight = (model.WeightValue > 0) ? "{0} {1}".FormatCurrent(model.WeightValue.ToString("F2"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name) : "";
            model.Height = (product.Height > 0) ? "{0} {1}".FormatCurrent(product.Height.ToString("F2"), dimension) : "";
            model.Length = (product.Length > 0) ? "{0} {1}".FormatCurrent(product.Length.ToString("F2"), dimension) : "";
            model.Width = (product.Width > 0) ? "{0} {1}".FormatCurrent(product.Width.ToString("F2"), dimension) : "";

            if (productBundleItem != null)
                model.ThumbDimensions = _mediaSettings.BundledProductPictureSize;
            else if (isAssociatedProduct)
                model.ThumbDimensions = _mediaSettings.AssociatedProductPictureSize;

            if (model.IsAvailable)
            {
                var deliveryTime = _deliveryTimeService.GetDeliveryTime(product);
                if (deliveryTime != null)
                {
                    model.DeliveryTimeName = deliveryTime.GetLocalized(x => x.Name);
                    model.DeliveryTimeHexValue = deliveryTime.ColorHexValue;
                }
            }

            model.DisplayDeliveryTime = _catalogSettings.ShowDeliveryTimesInProductDetail;
            model.IsShipEnabled = product.IsShipEnabled;
            model.DisplayDeliveryTimeAccordingToStock = product.DisplayDeliveryTimeAccordingToStock(_catalogSettings);

            if (model.DeliveryTimeName.IsEmpty() && model.DisplayDeliveryTime)
            {
                model.DeliveryTimeName = T("ShoppingCart.NotAvailable");
            }

            var quantityUnit = _quantityUnitService.GetQuantityUnit(product);
            if (quantityUnit != null)
            {
                model.QuantityUnitName = quantityUnit.GetLocalized(x => x.Name);
            }

            //back in stock subscriptions)
            if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                product.BackorderMode == BackorderMode.NoBackorders &&
                product.AllowBackInStockSubscriptions &&
                product.StockQuantity <= 0)
            {
                //out of stock
                model.DisplayBackInStockSubscription = true;
                model.BackInStockAlreadySubscribed = _backInStockSubscriptionService.FindSubscription(customer.Id, product.Id, store.Id) != null;
            }

            #endregion

            #region Product price

            model.ProductPrice.ProductId = product.Id;

            if (displayPrices)
            {
                model.ProductPrice.HidePrices = false;

                if (product.CustomerEntersPrice && !isBundleItemPricing)
                {
                    model.ProductPrice.CustomerEntersPrice = true;
                }
                else
                {
                    if (product.CallForPrice && !isBundleItemPricing)
                    {
                        model.ProductPrice.CallForPrice = true;
                    }
                    else
                    {
                        decimal taxRate = decimal.Zero;
                        decimal oldPrice = decimal.Zero;
                        decimal finalPriceWithoutDiscountBase = decimal.Zero;
                        decimal finalPriceWithDiscountBase = decimal.Zero;
                        decimal attributesTotalPriceBase = decimal.Zero;
                        decimal finalPriceWithoutDiscount = decimal.Zero;
                        decimal finalPriceWithDiscount = decimal.Zero;

                        decimal oldPriceBase = _taxService.GetProductPrice(product, product.OldPrice, out taxRate);

                        if (model.ProductPrice.DynamicPriceUpdate && !isBundlePricing)
                        {
                            if (selectedAttributeValues != null)
                            {
                                selectedAttributeValues.Each(x => attributesTotalPriceBase += _priceCalculationService.GetProductVariantAttributeValuePriceAdjustment(x));
                            }
                            else
                            {
                                attributesTotalPriceBase = preSelectedPriceAdjustmentBase;
                            }
                        }

                        if (productBundleItem != null)
                        {
                            productBundleItem.AdditionalCharge = attributesTotalPriceBase;
                        }

                        finalPriceWithoutDiscountBase = _priceCalculationService.GetFinalPrice(product, productBundleItems,
                            customer, attributesTotalPriceBase, false, selectedQuantity, productBundleItem);

                        finalPriceWithDiscountBase = _priceCalculationService.GetFinalPrice(product, productBundleItems,
                            customer, attributesTotalPriceBase, true, selectedQuantity, productBundleItem);

                        finalPriceWithoutDiscountBase = _taxService.GetProductPrice(product, finalPriceWithoutDiscountBase, out taxRate);
                        finalPriceWithDiscountBase = _taxService.GetProductPrice(product, finalPriceWithDiscountBase, out taxRate);

                        oldPrice = _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, _services.WorkContext.WorkingCurrency);

                        finalPriceWithoutDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithoutDiscountBase, currency);
                        finalPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithDiscountBase, currency);

                        if (productBundleItem == null || isBundleItemPricing)
                        {
                            if (finalPriceWithoutDiscountBase != oldPriceBase && oldPriceBase > decimal.Zero)
                                model.ProductPrice.OldPrice = _priceFormatter.FormatPrice(oldPrice);

                            model.ProductPrice.Price = _priceFormatter.FormatPrice(finalPriceWithoutDiscount);

                            if (finalPriceWithoutDiscountBase != finalPriceWithDiscountBase)
                                model.ProductPrice.PriceWithDiscount = _priceFormatter.FormatPrice(finalPriceWithDiscount);
                        }

                        model.ProductPrice.PriceValue = finalPriceWithoutDiscount;
                        model.ProductPrice.PriceWithDiscountValue = finalPriceWithDiscount;
                        model.BasePriceInfo = product.GetBasePriceInfo(
                            _localizationService,
                            _priceFormatter,
                            _currencyService,
                            _taxService,
                            _priceCalculationService,
                            currency,
                            attributesTotalPriceBase);

                        if (!string.IsNullOrWhiteSpace(model.ProductPrice.OldPrice) || !string.IsNullOrWhiteSpace(model.ProductPrice.PriceWithDiscount))
                        {
                            model.ProductPrice.NoteWithoutDiscount = T(isBundle && product.BundlePerItemPricing ? "Products.Bundle.PriceWithoutDiscount.Note" : "Products.Price");
                        }

                        if ((isBundle && product.BundlePerItemPricing && !string.IsNullOrWhiteSpace(model.ProductPrice.PriceWithDiscount)) || product.HasTierPrices)
                        {
                            if (!product.HasTierPrices)
                            {
                                model.ProductPrice.NoteWithDiscount = T("Products.Bundle.PriceWithDiscount.Note");
                            }

                            model.BasePriceInfo = product.GetBasePriceInfo(
                                _localizationService,
                                _priceFormatter,
                                _currencyService,
                                _taxService,
                                _priceCalculationService,
                                currency,
                                (product.Price - finalPriceWithDiscount) * (-1));
                        }
                    }
                }
            }
            else
            {
                model.ProductPrice.HidePrices = true;
                model.ProductPrice.OldPrice = null;
                model.ProductPrice.Price = null;
            }
            #endregion

            #region 'Add to cart' model

            model.AddToCart.ProductId = product.Id;

            //quantity
            model.AddToCart.EnteredQuantity = product.OrderMinimumQuantity;

            //'add to cart', 'add to wishlist' buttons
            model.AddToCart.DisableBuyButton = product.DisableBuyButton || !_services.Permissions.Authorize(StandardPermissionProvider.EnableShoppingCart);
            model.AddToCart.DisableWishlistButton = product.DisableWishlistButton || !_services.Permissions.Authorize(StandardPermissionProvider.EnableWishlist);
            if (!displayPrices)
            {
                model.AddToCart.DisableBuyButton = true;
                model.AddToCart.DisableWishlistButton = true;
            }
            //pre-order
            model.AddToCart.AvailableForPreOrder = product.AvailableForPreOrder;

            //customer entered price
            model.AddToCart.CustomerEntersPrice = product.CustomerEntersPrice;
            if (model.AddToCart.CustomerEntersPrice)
            {
                var minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, currency);
                var maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, currency);

                model.AddToCart.CustomerEnteredPrice = minimumCustomerEnteredPrice;

                model.AddToCart.CustomerEnteredPriceRange = string.Format(T("Products.EnterProductPrice.Range"),
                    _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, true, false),
                    _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, true, false));
            }

            //allowed quantities
            var allowedQuantities = product.ParseAllowedQuatities();
            foreach (var qty in allowedQuantities)
            {
                model.AddToCart.AllowedQuantities.Add(new SelectListItem
                {
                    Text = qty.ToString(),
                    Value = qty.ToString()
                });
            }

            #endregion

            #region Gift card

            model.GiftCard.IsGiftCard = product.IsGiftCard;
            if (model.GiftCard.IsGiftCard)
            {
                model.GiftCard.GiftCardType = product.GiftCardType;
                model.GiftCard.SenderName = customer.GetFullName();
                model.GiftCard.SenderEmail = customer.Email;
            }

            #endregion

            return model;
        }
        public void PrepareProductDetailsPictureModel(
			ProductDetailsPictureModel model, 
			IList<Picture> pictures, 
			string name, 
			IList<int> allCombinationImageIds,
			bool isAssociatedProduct, 
			ProductBundleItemData bundleItem = null, 
			ProductVariantAttributeCombination combination = null)
        {
            model.Name = name;
            model.DefaultPictureZoomEnabled = _mediaSettings.DefaultPictureZoomEnabled;
            model.PictureZoomType = _mediaSettings.PictureZoomType;
            model.AlternateText = T("Media.Product.ImageAlternateTextFormat", model.Name);

            Picture defaultPicture = null;
            var combiAssignedImages = (combination == null ? null : combination.GetAssignedPictureIds());
            int defaultPictureSize;

            if (isAssociatedProduct)
                defaultPictureSize = _mediaSettings.AssociatedProductPictureSize;
            else if (bundleItem != null)
                defaultPictureSize = _mediaSettings.BundledProductPictureSize;
            else
                defaultPictureSize = _mediaSettings.ProductDetailsPictureSize;

            if (pictures.Count > 0)
            {
                if (pictures.Count <= _catalogSettings.DisplayAllImagesNumber)
                {
                    // show all images
                    foreach (var picture in pictures)
                    {
                        model.PictureModels.Add(CreatePictureModel(model, picture, _mediaSettings.ProductDetailsPictureSize));

                        if (defaultPicture == null && combiAssignedImages != null && combiAssignedImages.Contains(picture.Id))
                        {
                            model.GalleryStartIndex = model.PictureModels.Count - 1;
                            defaultPicture = picture;
                        }
                    }
                }
                else
                {
                    // images not belonging to any combination...
                    allCombinationImageIds = allCombinationImageIds ?? new List<int>();
                    foreach (var picture in pictures.Where(p => !allCombinationImageIds.Contains(p.Id)))
                    {
                        model.PictureModels.Add(CreatePictureModel(model, picture, _mediaSettings.ProductDetailsPictureSize));
                    }

                    // plus images belonging to selected combination
                    if (combiAssignedImages != null)
                    {
                        foreach (var picture in pictures.Where(p => combiAssignedImages.Contains(p.Id)))
                        {
                            model.PictureModels.Add(CreatePictureModel(model, picture, _mediaSettings.ProductDetailsPictureSize));

                            if (defaultPicture == null)
                            {
                                model.GalleryStartIndex = model.PictureModels.Count - 1;
                                defaultPicture = picture;
                            }
                        }
                    }
                }

                if (defaultPicture == null)
                {
                    model.GalleryStartIndex = 0;
                    defaultPicture = pictures.First();
                }
            }

            // default picture
            if (defaultPicture == null)
            {
                model.DefaultPictureModel = new PictureModel
                {
                    Title = T("Media.Product.ImageLinkTitleFormat", model.Name),
                    AlternateText = model.AlternateText
                };

                if (!_catalogSettings.HideProductDefaultPictures)
                {
                    model.DefaultPictureModel.ThumbImageUrl = _pictureService.GetDefaultPictureUrl(_mediaSettings.ProductThumbPictureSizeOnProductDetailsPage);
                    model.DefaultPictureModel.ImageUrl = _pictureService.GetDefaultPictureUrl(defaultPictureSize);
                    model.DefaultPictureModel.FullSizeImageUrl = _pictureService.GetDefaultPictureUrl();
                }
            }
            else
            {
                model.DefaultPictureModel = CreatePictureModel(model, defaultPicture, defaultPictureSize);
            }
        }
        public ProductDetailsModel PrepareProductDetailsPageModel(
			Product product, 
			bool isAssociatedProduct = false,
			ProductBundleItemData productBundleItem = null, 
			IList<ProductBundleItemData> productBundleItems = null, 
			NameValueCollection selectedAttributes = null,
			NameValueCollection queryData = null)
        {
            if (product == null)
                throw new ArgumentNullException("product");

            var model = new ProductDetailsModel
            {
                Id = product.Id,
                Name = product.GetLocalized(x => x.Name),
                ShortDescription = product.GetLocalized(x => x.ShortDescription),
                FullDescription = product.GetLocalized(x => x.FullDescription),
                MetaKeywords = product.GetLocalized(x => x.MetaKeywords),
                MetaDescription = product.GetLocalized(x => x.MetaDescription),
                MetaTitle = product.GetLocalized(x => x.MetaTitle),
                SeName = product.GetSeName(),
                ProductType = product.ProductType,
                VisibleIndividually = product.VisibleIndividually,
                //Manufacturers = _manufacturerService.GetProductManufacturersByProductId(product.Id),
                Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id), null, true),
                ReviewCount = product.ApprovedTotalReviews,
                DisplayAdminLink = _services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel),
                //EnableHtmlTextCollapser = Convert.ToBoolean(_settingService.GetSettingByKey<string>("CatalogSettings.EnableHtmlTextCollapser")),
                //HtmlTextCollapsedHeight = Convert.ToString(_settingService.GetSettingByKey<string>("CatalogSettings.HtmlTextCollapsedHeight")),
                ShowSku = _catalogSettings.ShowProductSku,
                Sku = product.Sku,
                ShowManufacturerPartNumber = _catalogSettings.ShowManufacturerPartNumber,
                DisplayProductReviews = _catalogSettings.ShowProductReviewsInProductDetail,
                ManufacturerPartNumber = product.ManufacturerPartNumber,
                ShowGtin = _catalogSettings.ShowGtin,
                Gtin = product.Gtin,
                StockAvailability = product.FormatStockMessage(_localizationService),
                HasSampleDownload = product.IsDownload && product.HasSampleDownload,
                IsCurrentCustomerRegistered = _services.WorkContext.CurrentCustomer.IsRegistered()
            };

            // get gift card values from query string
            if (queryData != null && queryData.Count > 0)
            {
                var giftCardItems = queryData.AllKeys
                    .Where(x => x.EmptyNull().StartsWith("giftcard_"))
                    .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v.TrimSafe() });

                foreach (var item in giftCardItems)
                {
                    var key = item.key.EmptyNull().ToLower();

                    if (key.EndsWith("recipientname"))
                        model.GiftCard.RecipientName = item.value;
                    else if (key.EndsWith("recipientemail"))
                        model.GiftCard.RecipientEmail = item.value;
                    else if (key.EndsWith("sendername"))
                        model.GiftCard.SenderName = item.value;
                    else if (key.EndsWith("senderemail"))
                        model.GiftCard.SenderEmail = item.value;
                    else if (key.EndsWith("message"))
                        model.GiftCard.Message = item.value;
                }
            }

            // Back in stock subscriptions
            if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                 product.BackorderMode == BackorderMode.NoBackorders &&
                 product.AllowBackInStockSubscriptions &&
                 product.StockQuantity <= 0)
            {
                //out of stock
                model.DisplayBackInStockSubscription = true;
                model.BackInStockAlreadySubscribed = _backInStockSubscriptionService
                    .FindSubscription(_services.WorkContext.CurrentCustomer.Id, product.Id, _services.StoreContext.CurrentStore.Id) != null;
            }

            //template
            var templateCacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_TEMPLATE_MODEL_KEY, product.ProductTemplateId);
            model.ProductTemplateViewPath = _services.Cache.Get(templateCacheKey, () =>
            {
                var template = _productTemplateService.GetProductTemplateById(product.ProductTemplateId);
                if (template == null)
                    template = _productTemplateService.GetAllProductTemplates().FirstOrDefault();
                return template.ViewPath;
            });

            IList<ProductBundleItemData> bundleItems = null;
            ProductVariantAttributeCombination combination = null;

            if (product.ProductType == ProductType.GroupedProduct && !isAssociatedProduct)	// associated products
            {
                var searchContext = new ProductSearchContext
                {
                    OrderBy = ProductSortingEnum.Position,
                    StoreId = _services.StoreContext.CurrentStore.Id,
                    ParentGroupedProductId = product.Id,
                    PageSize = int.MaxValue,
                    VisibleIndividuallyOnly = false
                };

                var associatedProducts = _productService.SearchProducts(searchContext);

                foreach (var associatedProduct in associatedProducts)
                    model.AssociatedProducts.Add(PrepareProductDetailsPageModel(associatedProduct, true));
            }
            else if (product.ProductType == ProductType.BundledProduct && productBundleItem == null)		// bundled items
            {
                bundleItems = _productService.GetBundleItems(product.Id);

                foreach (var itemData in bundleItems.Where(x => x.Item.Product.CanBeBundleItem()))
                {
                    var item = itemData.Item;
                    var bundleItemAttributes = new NameValueCollection();

                    if (selectedAttributes != null)
                    {
                        var keyPrefix = "product_attribute_{0}_{1}".FormatInvariant(item.ProductId, item.Id);

                        foreach (var key in selectedAttributes.AllKeys.Where(x => x.HasValue() && x.StartsWith(keyPrefix)))
                        {
                            bundleItemAttributes.Add(key, selectedAttributes[key]);
                        }
                    }

                    var bundledProductModel = PrepareProductDetailsPageModel(item.Product, false, itemData, null, bundleItemAttributes);

                    bundledProductModel.BundleItem.Id = item.Id;
                    bundledProductModel.BundleItem.Quantity = item.Quantity;
                    bundledProductModel.BundleItem.HideThumbnail = item.HideThumbnail;
                    bundledProductModel.BundleItem.Visible = item.Visible;
                    bundledProductModel.BundleItem.IsBundleItemPricing = item.BundleProduct.BundlePerItemPricing;

                    var bundleItemName = item.GetLocalized(x => x.Name);
                    if (bundleItemName.HasValue())
                        bundledProductModel.Name = bundleItemName;

                    var bundleItemShortDescription = item.GetLocalized(x => x.ShortDescription);
                    if (bundleItemShortDescription.HasValue())
                        bundledProductModel.ShortDescription = bundleItemShortDescription;

                    model.BundledItems.Add(bundledProductModel);
                }
            }

            model = PrepareProductDetailModel(model, product, isAssociatedProduct, productBundleItem, bundleItems, selectedAttributes);

            IList<int> combinationPictureIds = null;

            if (productBundleItem == null)
            {
                combinationPictureIds = _productAttributeService.GetAllProductVariantAttributeCombinationPictureIds(product.Id);
                if (combination == null && model.SelectedCombination != null)
                    combination = model.SelectedCombination;
            }

            // pictures
            var pictures = _pictureService.GetPicturesByProductId(product.Id);
            PrepareProductDetailsPictureModel(model.DetailsPictureModel, pictures, model.Name, combinationPictureIds, isAssociatedProduct, productBundleItem, combination);

            return model;
        }
		protected ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool isAssociatedProduct = false,
			ProductBundleItemData productBundleItem = null, IList<ProductBundleItemData> productBundleItems = null, FormCollection selectedAttributes = null)
        {
            if (product == null)
                throw new ArgumentNullException("product");

            var model = new ProductDetailsModel()
            {
                Id = product.Id,
                Name = product.GetLocalized(x => x.Name),
                ShortDescription = product.GetLocalized(x => x.ShortDescription),
                FullDescription = product.GetLocalized(x => x.FullDescription),
                MetaKeywords = product.GetLocalized(x => x.MetaKeywords),
                MetaDescription = product.GetLocalized(x => x.MetaDescription),
                MetaTitle = product.GetLocalized(x => x.MetaTitle),
                SeName = product.GetSeName(),
				ProductType = product.ProductType,
				VisibleIndividually = product.VisibleIndividually,
                //Manufacturers = _manufacturerService.GetProductManufacturersByProductId(product.Id),  /* codehint: sm-edit */
                Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id)),
                ReviewCount = product.ApprovedTotalReviews,                     /* codehint: sm-add */
                DisplayAdminLink = _permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel),
                EnableHtmlTextCollapser = Convert.ToBoolean(_settingService.GetSettingByKey<string>("CatalogSettings.EnableHtmlTextCollapser")),
                HtmlTextCollapsedHeight = Convert.ToString(_settingService.GetSettingByKey<string>("CatalogSettings.HtmlTextCollapsedHeight")),
				ShowSku = _catalogSettings.ShowProductSku,
				Sku = product.Sku,
				ShowManufacturerPartNumber = _catalogSettings.ShowManufacturerPartNumber,
				ManufacturerPartNumber = product.ManufacturerPartNumber,
				ShowGtin = _catalogSettings.ShowGtin,
				Gtin = product.Gtin,
				StockAvailability = product.FormatStockMessage(_localizationService),
				HasSampleDownload = product.IsDownload && product.HasSampleDownload,
				IsCurrentCustomerRegistered = _workContext.CurrentCustomer.IsRegistered()
            };

			// Back in stock subscriptions
			if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
				 product.BackorderMode == BackorderMode.NoBackorders &&
				 product.AllowBackInStockSubscriptions &&
				 product.StockQuantity <= 0)
			{
				//out of stock
				model.DisplayBackInStockSubscription = true;
				model.BackInStockAlreadySubscribed = _backInStockSubscriptionService
					.FindSubscription(_workContext.CurrentCustomer.Id, product.Id, _storeContext.CurrentStore.Id) != null;
			}

            //template
            var templateCacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_TEMPLATE_MODEL_KEY, product.ProductTemplateId);
            model.ProductTemplateViewPath = _cacheManager.Get(templateCacheKey, () =>
            {
                var template = _productTemplateService.GetProductTemplateById(product.ProductTemplateId);
                if (template == null)
                    template = _productTemplateService.GetAllProductTemplates().FirstOrDefault();
                return template.ViewPath;
            });

			IList<ProductBundleItemData> bundleItems = null;
            ProductVariantAttributeCombination combination = null;
            var combinationImageIds = new List<int>();

			if (product.ProductType == ProductType.GroupedProduct && !isAssociatedProduct)	// associated products
			{
				var searchContext = new ProductSearchContext()
				{
					StoreId = _storeContext.CurrentStore.Id,
					ParentGroupedProductId = product.Id,
					VisibleIndividuallyOnly = false
				};

				var associatedProducts = _productService.SearchProducts(searchContext);

				foreach (var associatedProduct in associatedProducts)
					model.AssociatedProducts.Add(PrepareProductDetailsPageModel(associatedProduct, true));
			}
			else if (product.ProductType == ProductType.BundledProduct && productBundleItem == null)		// bundled items
			{
				bundleItems = _productService.GetBundleItems(product.Id);

				foreach (var itemData in bundleItems.Where(x => x.Item.Product.CanBeBundleItem()))
				{
					var item = itemData.Item;
					var bundledProductModel = PrepareProductDetailsPageModel(item.Product, false, itemData);

					bundledProductModel.BundleItem.Id = item.Id;
					bundledProductModel.BundleItem.Quantity = item.Quantity;
					bundledProductModel.BundleItem.HideThumbnail = item.HideThumbnail;
					bundledProductModel.BundleItem.Visible = item.Visible;
					bundledProductModel.BundleItem.IsBundleItemPricing = item.BundleProduct.BundlePerItemPricing;

					string bundleItemName = item.GetLocalized(x => x.Name);
					if (bundleItemName.HasValue())
						bundledProductModel.Name = bundleItemName;

					string bundleItemShortDescription = item.GetLocalized(x => x.ShortDescription);
					if (bundleItemShortDescription.HasValue())
						bundledProductModel.ShortDescription = bundleItemShortDescription;

					model.BundledItems.Add(bundledProductModel);
				}
			}

			model = PrepareProductDetailModel(model, product, isAssociatedProduct, productBundleItem, bundleItems, selectedAttributes);

			if (productBundleItem == null)
			{
				model.Combinations.GetAllCombinationImageIds(combinationImageIds);

				if (combination == null && model.CombinationSelected != null)
					combination = model.CombinationSelected;
			}

            // pictures
            var pictures = _pictureService.GetPicturesByProductId(product.Id);
			PrepareProductDetailsPictureModel(model.DetailsPictureModel, pictures, model.Name, combinationImageIds, isAssociatedProduct, productBundleItem, combination);

            return model;
        }