context: using System;
  using System.Collections.Generic;
  using System.Collections.Specialized;
  using System.Linq;
  using SmartStore.Core;
  using SmartStore.Core.Data;
  using SmartStore.Core.Domain.Catalog;
  using SmartStore.Core.Domain.Customers;
  using SmartStore.Core.Domain.Orders;
  using SmartStore.Services.Catalog;
  using SmartStore.Services.Common;
  using SmartStore.Services.Customers;
  using SmartStore.Services.Directory;
  using SmartStore.Core.Events;
  using SmartStore.Services.Localization;
  using SmartStore.Services.Media;
  using SmartStore.Services.Security;
  using SmartStore.Services.Stores;
  
  namespace SmartStore.Services.Orders
  {
      /// <summary>
      /// Shopping cart service
      /// </summary>
      public partial class ShoppingCartService : IShoppingCartService
      {
          #region Fields
  
          private readonly IRepository<ShoppingCartItem> _sciRepository;
          private readonly IWorkContext _workContext;
  		private readonly IStoreContext _storeContext;
          private readonly ICurrencyService _currencyService;
          private readonly IProductService _productService;
          private readonly ILocalizationService _localizationService;
          private readonly IProductAttributeParser _productAttributeParser;
  		private readonly IProductAttributeService _productAttributeService;
          private readonly ICheckoutAttributeService _checkoutAttributeService;
          private readonly ICheckoutAttributeParser _checkoutAttributeParser;
          private readonly IPriceFormatter _priceFormatter;
          private readonly ICustomerService _customerService;
          private readonly ShoppingCartSettings _shoppingCartSettings;
          private readonly IEventPublisher _eventPublisher;
          private readonly IPermissionService _permissionService;
          private readonly IAclService _aclService;
  		private readonly IStoreMappingService _storeMappingService;
  		private readonly IGenericAttributeService _genericAttributeService;
  		private readonly IDownloadService _downloadService;
  		private readonly CatalogSettings _catalogSettings;
  
          #endregion
  
          #region Ctor
  
          /// <summary>
          /// Ctor
          /// </summary>
          /// <param name="sciRepository">Shopping cart repository</param>
          /// <param name="workContext">Work context</param>
  		/// <param name="storeContext">Store context</param>
          /// <param name="currencyService">Currency service</param>
          /// <param name="productService">Product settings</param>
          /// <param name="localizationService">Localization service</param>
          /// <param name="productAttributeParser">Product attribute parser</param>
          /// <param name="checkoutAttributeService">Checkout attribute service</param>
          /// <param name="checkoutAttributeParser">Checkout attribute parser</param>
          /// <param name="priceFormatter">Price formatter</param>
          /// <param name="customerService">Customer service</param>
          /// <param name="shoppingCartSettings">Shopping cart settings</param>
          /// <param name="eventPublisher">Event publisher</param>
          /// <param name="permissionService">Permission service</param>
          /// <param name="aclService">ACL service</param>
  		/// <param name="storeMappingService">Store mapping service</param>
  		/// <param name="genericAttributeService">Generic attribute service</param>
          public ShoppingCartService(IRepository<ShoppingCartItem> sciRepository,
  			IWorkContext workContext, IStoreContext storeContext, 
  			ICurrencyService currencyService,
              IProductService productService, ILocalizationService localizationService,
              IProductAttributeParser productAttributeParser,
  			IProductAttributeService productAttributeService,
              ICheckoutAttributeService checkoutAttributeService,
              ICheckoutAttributeParser checkoutAttributeParser,
              IPriceFormatter priceFormatter,
              ICustomerService customerService,
              ShoppingCartSettings shoppingCartSettings,
              IEventPublisher eventPublisher,
              IPermissionService permissionService, 
              IAclService aclService,
  			IStoreMappingService storeMappingService,
  			IGenericAttributeService genericAttributeService,
  			IDownloadService downloadService,
  			CatalogSettings catalogSettings)
          {
              this._sciRepository = sciRepository;
              this._workContext = workContext;
  			this._storeContext = storeContext;
              this._currencyService = currencyService;
              this._productService = productService;
              this._localizationService = localizationService;
              this._productAttributeParser = productAttributeParser;
  			this._productAttributeService = productAttributeService;
              this._checkoutAttributeService = checkoutAttributeService;
              this._checkoutAttributeParser = checkoutAttributeParser;
              this._priceFormatter = priceFormatter;
              this._customerService = customerService;
              this._shoppingCartSettings = shoppingCartSettings;
              this._eventPublisher = eventPublisher;
              this._permissionService = permissionService;
              this._aclService = aclService;
  			this._storeMappingService = storeMappingService;
  			this._genericAttributeService = genericAttributeService;
  			this._downloadService = downloadService;
  			this._catalogSettings = catalogSettings;
          }
  
          #endregion
  
          #region Methods
  
          /// <summary>
          /// Delete shopping cart item
          /// </summary>
          /// <param name="shoppingCartItem">Shopping cart item</param>
          /// <param name="resetCheckoutData">A value indicating whether to reset checkout data</param>
          /// <param name="ensureOnlyActiveCheckoutAttributes">A value indicating whether to ensure that only active checkout attributes are attached to the current customer</param>
  		/// <param name="deleteChildCartItems">A value indicating whether to delete child cart items</param>
          public virtual void DeleteShoppingCartItem(ShoppingCartItem shoppingCartItem, bool resetCheckoutData = true, 
              bool ensureOnlyActiveCheckoutAttributes = false, bool deleteChildCartItems = true)
          {
              if (shoppingCartItem == null)
                  throw new ArgumentNullException("shoppingCartItem");
  
              var customer = shoppingCartItem.Customer;
  			var storeId = shoppingCartItem.StoreId;
  			int cartItemId = shoppingCartItem.Id;
  
              //reset checkout data
              if (resetCheckoutData)
              {
  				_customerService.ResetCheckoutData(shoppingCartItem.Customer, shoppingCartItem.StoreId);
              }
  
              //delete item
              _sciRepository.Delete(shoppingCartItem);
  
              //validate checkout attributes
              if (ensureOnlyActiveCheckoutAttributes &&
                  //only for shopping cart items (ignore wishlist)
                  shoppingCartItem.ShoppingCartType == ShoppingCartType.ShoppingCart)
              {
  				var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, storeId);
  
  				var checkoutAttributesXml = customer.GetAttribute<string>(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService);
  				checkoutAttributesXml = _checkoutAttributeParser.EnsureOnlyActiveAttributes(checkoutAttributesXml, cart);
  				_genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CheckoutAttributes, checkoutAttributesXml);
              }
  
              //event notification
              _eventPublisher.EntityDeleted(shoppingCartItem);
  
  			// delete child items
  			if (deleteChildCartItems)
  			{
  				var childCartItems = _sciRepository.Table
  					.Where(x => x.CustomerId == customer.Id && x.ParentItemId != null && x.ParentItemId.Value == cartItemId && x.Id != cartItemId)
  					.ToList();
  
  				foreach (var cartItem in childCartItems)
  					DeleteShoppingCartItem(cartItem, resetCheckoutData, ensureOnlyActiveCheckoutAttributes, false);
  			}
          }
  
  		public virtual void DeleteShoppingCartItem(int shoppingCartItemId, bool resetCheckoutData = true,
  			bool ensureOnlyActiveCheckoutAttributes = false, bool deleteChildCartItems = true)
  		{
  			if (shoppingCartItemId != 0)
  			{
  				var shoppingCartItem = _sciRepository.GetById(shoppingCartItemId);
  
  				DeleteShoppingCartItem(shoppingCartItem, resetCheckoutData, ensureOnlyActiveCheckoutAttributes, deleteChildCartItems);
  			}
  		}
  
          /// <summary>
          /// Deletes expired shopping cart items
          /// </summary>
          /// <param name="olderThanUtc">Older than date and time</param>
          /// <returns>Number of deleted items</returns>
          public virtual int DeleteExpiredShoppingCartItems(DateTime olderThanUtc)
          {
              var query =
  				from sci in _sciRepository.Table
  				where sci.UpdatedOnUtc < olderThanUtc && sci.ParentItemId == null
  				select sci;
  
              var cartItems = query.ToList();
  
  			foreach (var parentItem in cartItems)
  			{
  				var childItems = _sciRepository.Table
  					.Where(x => x.ParentItemId != null && x.ParentItemId.Value == parentItem.Id && x.Id != parentItem.Id).ToList();
  
  				foreach (var childItem in childItems)
  					_sciRepository.Delete(childItem);
  
  				_sciRepository.Delete(parentItem);
  			}
  
              return cartItems.Count;
          }
          
          /// <summary>
  		/// Validates required products (products which require other variant to be added to the cart)
          /// </summary>
          /// <param name="customer">Customer</param>
          /// <param name="shoppingCartType">Shopping cart type</param>
  		/// <param name="product">Product</param>
  		/// <param name="storeId">Store identifier</param>
  		/// <param name="automaticallyAddRequiredProductsIfEnabled">Automatically add required products if enabled</param>
          /// <returns>Warnings</returns>
          public virtual IList<string> GetRequiredProductWarnings(Customer customer,
  			ShoppingCartType shoppingCartType, Product product,
  			int storeId, bool automaticallyAddRequiredProductsIfEnabled)
          {
              if (customer == null)
                  throw new ArgumentNullException("customer");
  
              if (product == null)
                  throw new ArgumentNullException("product");
  
              var cart = customer.GetCartItems(shoppingCartType, storeId);
              var warnings = new List<string>();
  
              if (product.RequireOtherProducts)
              {
                  var requiredProducts = new List<Product>();
                  foreach (var id in product.ParseRequiredProductIds())
                  {
  					var rp = _productService.GetProductById(id);
                      if (rp != null)
                          requiredProducts.Add(rp);
                  }
                  
                  foreach (var rp in requiredProducts)
                  {
                      //ensure that product is in the cart
                      bool alreadyInTheCart = false;
                      foreach (var sci in cart)
                      {
                          if (sci.Item.ProductId == rp.Id)
                          {
                              alreadyInTheCart = true;
                              break;
                          }
                      }
                      //not in the cart
                      if (!alreadyInTheCart)
                      {
                          if (product.AutomaticallyAddRequiredProducts)
                          {
                              //add to cart (if possible)
                              if (automaticallyAddRequiredProductsIfEnabled)
                              {
                                  //pass 'false' for 'automaticallyAddRequiredProducsIfEnabled' to prevent circular references
  								int shoppingCartItemId;
  								var addToCartWarnings = AddToCart(customer, rp, shoppingCartType, storeId, "", decimal.Zero, 1, false, out shoppingCartItemId);
                                  if (addToCartWarnings.Count > 0)
                                  {
                                      //a product wasn't atomatically added for some reasons
  
                                      //don't display specific errors from 'addToCartWarnings' variable
                                      //display only generic error
  									warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name)));
                                  }
                              }
                              else
                              {
  								warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name)));
                              }
                          }
                          else
                          {
  							warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name)));
                          }
                      }
                  }
              }
  
              return warnings;
          }
          
          /// <summary>
  		/// Validates a product for standard properties
          /// </summary>
          /// <param name="customer">Customer</param>
          /// <param name="shoppingCartType">Shopping cart type</param>
  		/// <param name="product">Product</param>
          /// <param name="selectedAttributes">Selected attributes</param>
          /// <param name="customerEnteredPrice">Customer entered price</param>
          /// <param name="quantity">Quantity</param>
          /// <returns>Warnings</returns>
          public virtual IList<string> GetStandardWarnings(Customer customer, ShoppingCartType shoppingCartType,
              Product product, string selectedAttributes, decimal customerEnteredPrice,
              int quantity)
          {
              if (customer == null)
                  throw new ArgumentNullException("customer");
  
              if (product == null)
                  throw new ArgumentNullException("product");
  
              var warnings = new List<string>();
  
              //deleted?
              if (product.Deleted)
              {
                  warnings.Add(_localizationService.GetResource("ShoppingCart.ProductDeleted"));
                  return warnings;
              }
  
  			// check if the product type is available for order
  			if (product.ProductType == ProductType.GroupedProduct)
  			{
  				warnings.Add(_localizationService.GetResource("ShoppingCart.ProductNotAvailableForOrder"));
  			}
  
  			// validate bundle
  			if (product.ProductType == ProductType.BundledProduct)
  			{
  				if (product.BundlePerItemPricing && customerEnteredPrice != decimal.Zero)
  					warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.NoCustomerEnteredPrice"));
  			}
  
              //published?
              if (!product.Published)
              {
                  warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished"));
              }
              
              //ACL
              if (!_aclService.Authorize(product, customer))
              {
                  warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished"));
              }
  
  			//Store mapping
  			if (!_storeMappingService.Authorize(product, _storeContext.CurrentStore.Id))
  			{
  				warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished"));
  			}
  
              //disabled "add to cart" button
              if (shoppingCartType == ShoppingCartType.ShoppingCart && product.DisableBuyButton)
              {
                  warnings.Add(_localizationService.GetResource("ShoppingCart.BuyingDisabled"));
              }
  
              //disabled "add to wishlist" button
              if (shoppingCartType == ShoppingCartType.Wishlist && product.DisableWishlistButton)
              {
                  warnings.Add(_localizationService.GetResource("ShoppingCart.WishlistDisabled"));
              }
  
              //call for price
              if (shoppingCartType == ShoppingCartType.ShoppingCart && product.CallForPrice)
              {
                  warnings.Add(_localizationService.GetResource("Products.CallForPrice"));
              }
  
              //customer entered price
              if (product.CustomerEntersPrice)
              {
                  if (customerEnteredPrice < product.MinimumCustomerEnteredPrice ||
                      customerEnteredPrice > product.MaximumCustomerEnteredPrice)
                  {
                      decimal minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, _workContext.WorkingCurrency);
                      decimal maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, _workContext.WorkingCurrency);
                      warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.CustomerEnteredPrice.RangeError"),
                          _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, false, false),
                          _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, false, false)));
                  }
              }
  
              //quantity validation
              var hasQtyWarnings = false;
              if (quantity < product.OrderMinimumQuantity)
              {
                  warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.MinimumQuantity"), product.OrderMinimumQuantity));
                  hasQtyWarnings = true;
              }
              if (quantity > product.OrderMaximumQuantity)
              {
                  warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.MaximumQuantity"), product.OrderMaximumQuantity));
                  hasQtyWarnings = true;
              }
              var allowedQuantities = product.ParseAllowedQuatities();
              if (allowedQuantities.Length > 0 && !allowedQuantities.Contains(quantity))
              {
                  warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.AllowedQuantities"), string.Join(", ", allowedQuantities)));
              }
  
              var validateOutOfStock = shoppingCartType == ShoppingCartType.ShoppingCart || !_shoppingCartSettings.AllowOutOfStockItemsToBeAddedToWishlist;
              if (validateOutOfStock && !hasQtyWarnings)
              {
                  switch (product.ManageInventoryMethod)
                  {
                      case ManageInventoryMethod.DontManageStock:
                          {
                          }
                          break;
                      case ManageInventoryMethod.ManageStock:
                          {
                              if ((BackorderMode)product.BackorderMode == BackorderMode.NoBackorders)
                              {
                                  if (product.StockQuantity < quantity)
                                  {
                                      int maximumQuantityCanBeAdded = product.StockQuantity;
                                      if (maximumQuantityCanBeAdded <= 0)
                                          warnings.Add(_localizationService.GetResource("ShoppingCart.OutOfStock"));
                                      else
                                          warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.QuantityExceedsStock"), maximumQuantityCanBeAdded));
                                  }
                              }
                          }
                          break;
                      case ManageInventoryMethod.ManageStockByAttributes:
                          {
                              var combination = product
                                  .ProductVariantAttributeCombinations
                                  .FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, selectedAttributes));
                              if (combination != null &&
                                  !combination.AllowOutOfStockOrders &&
                                  combination.StockQuantity < quantity)
                              {
                                  int maximumQuantityCanBeAdded = combination.StockQuantity;
                                  if (maximumQuantityCanBeAdded <= 0)
                                      warnings.Add(_localizationService.GetResource("ShoppingCart.OutOfStock"));
                                  else
                                      warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.QuantityExceedsStock"), maximumQuantityCanBeAdded));
                              }
                          }
                          break;
                      default:
                          break;
                  }
              }
  
              //availability dates
              bool availableStartDateError = false;
              if (product.AvailableStartDateTimeUtc.HasValue)
              {
                  DateTime now = DateTime.UtcNow;
                  DateTime availableStartDateTime = DateTime.SpecifyKind(product.AvailableStartDateTimeUtc.Value, DateTimeKind.Utc);
                  if (availableStartDateTime.CompareTo(now) > 0)
                  {
                      warnings.Add(_localizationService.GetResource("ShoppingCart.NotAvailable"));
                      availableStartDateError = true;
                  }
              }
              if (product.AvailableEndDateTimeUtc.HasValue && !availableStartDateError)
              {
                  DateTime now = DateTime.UtcNow;
                  DateTime availableEndDateTime = DateTime.SpecifyKind(product.AvailableEndDateTimeUtc.Value, DateTimeKind.Utc);
                  if (availableEndDateTime.CompareTo(now) < 0)
                  {
                      warnings.Add(_localizationService.GetResource("ShoppingCart.NotAvailable"));
                  }
              }
              return warnings;
          }
  
          /// <summary>
          /// Validates shopping cart item attributes
          /// </summary>
  		/// <param name="customer">The customer</param>
          /// <param name="shoppingCartType">Shopping cart type</param>
  		/// <param name="product">Product</param>
          /// <param name="selectedAttributes">Selected attributes</param>
  		/// <param name="quantity">Quantity</param>
  		/// <param name="bundleItem">Product bundle item</param>
          /// <returns>Warnings</returns>
  		public virtual IList<string> GetShoppingCartItemAttributeWarnings(Customer customer, ShoppingCartType shoppingCartType,
  			Product product, string selectedAttributes, int quantity = 1, ProductBundleItem bundleItem = null)
          {
              if (product == null)
                  throw new ArgumentNullException("product");
  
  
gt:             var warnings = new List<string>();