context: using System;
  using System.Collections.Generic;
  using System.Linq;
  using SmartStore.Core;
  using SmartStore.Core.Caching;
  using SmartStore.Core.Data;
  using SmartStore.Core.Domain.Catalog;
  using SmartStore.Core.Domain.Security;
  using SmartStore.Core.Domain.Stores;
  using SmartStore.Core.Events;
  using SmartStore.Services.Customers;
  using SmartStore.Services.Localization;
  using SmartStore.Services.Security;
  using SmartStore.Services.Stores;
  
  namespace SmartStore.Services.Catalog
  {
      /// <summary>
      /// Category service
      /// </summary>
      public partial class CategoryService : ICategoryService
      {
          #region Constants
  
          private const string CATEGORIES_BY_PARENT_CATEGORY_ID_KEY = "SmartStore.category.byparent-{0}-{1}-{2}-{3}";
  		private const string PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY = "SmartStore.productcategory.allbycategoryid-{0}-{1}-{2}-{3}-{4}-{5}";
  		private const string PRODUCTCATEGORIES_ALLBYPRODUCTID_KEY = "SmartStore.productcategory.allbyproductid-{0}-{1}-{2}-{3}";
          private const string CATEGORIES_PATTERN_KEY = "SmartStore.category.";
          private const string PRODUCTCATEGORIES_PATTERN_KEY = "SmartStore.productcategory.";
          private const string CATEGORIES_BY_ID_KEY = "SmartStore.category.id-{0}";
  
          #endregion
  
          #region Fields
  
          private readonly IRepository<Category> _categoryRepository;
          private readonly IRepository<ProductCategory> _productCategoryRepository;
          private readonly IRepository<Product> _productRepository;
          private readonly IRepository<AclRecord> _aclRepository;
  		private readonly IRepository<StoreMapping> _storeMappingRepository;
          private readonly IWorkContext _workContext;
  		private readonly IStoreContext _storeContext;
          private readonly IEventPublisher _eventPublisher;
          private readonly ICacheManager _cacheManager;
  		private readonly IStoreMappingService _storeMappingService;
  		private readonly IAclService _aclService;
          private readonly Lazy<IEnumerable<ICategoryNavigationFilter>> _navigationFilters;
          private readonly ICustomerService _customerService;
          private readonly IProductService _productService;
          private readonly IStoreService _storeService;
          
          #endregion
          
          #region Ctor
  
          /// <summary>
          /// Ctor
          /// </summary>
          /// <param name="cacheManager">Cache manager</param>
          /// <param name="categoryRepository">Category repository</param>
          /// <param name="productCategoryRepository">ProductCategory repository</param>
          /// <param name="productRepository">Product repository</param>
          /// <param name="aclRepository">ACL record repository</param>
  		/// <param name="storeMappingRepository">Store mapping repository</param>
          /// <param name="workContext">Work context</param>
  		/// <param name="storeContext">Store context</param>
          /// <param name="eventPublisher">Event publisher</param>
          public CategoryService(ICacheManager cacheManager,
              IRepository<Category> categoryRepository,
              IRepository<ProductCategory> productCategoryRepository,
              IRepository<Product> productRepository,
              IRepository<AclRecord> aclRepository,
  			IRepository<StoreMapping> storeMappingRepository,
              IWorkContext workContext,
  			IStoreContext storeContext,
              IEventPublisher eventPublisher,
  			IStoreMappingService storeMappingService,
  			IAclService aclService,
              Lazy<IEnumerable<ICategoryNavigationFilter>> navigationFilters,
              ICustomerService customerService,
              IProductService productService,
              IStoreService storeService)
          {
              this._cacheManager = cacheManager;
              this._categoryRepository = categoryRepository;
              this._productCategoryRepository = productCategoryRepository;
              this._productRepository = productRepository;
              this._aclRepository = aclRepository;
  			this._storeMappingRepository = storeMappingRepository;
              this._workContext = workContext;
  			this._storeContext = storeContext;
              this._eventPublisher = eventPublisher;
  			this._storeMappingService = storeMappingService;
  			this._aclService = aclService;
              this._navigationFilters = navigationFilters;
              this._customerService = customerService;
              this._productService = productService;
              this._storeService = storeService;
  
  			this.QuerySettings = DbQuerySettings.Default;
          }
  
  		public DbQuerySettings QuerySettings { get; set; }
  
          #endregion
  
  		#region Utilities
  
  		private void DeleteAllCategories(IList<Category> categories, bool delete)
  		{
  			foreach (var category in categories)
  			{				
  				if (delete)
  					category.Deleted = true;
  				else
  					category.ParentCategoryId = 0;
  
  				UpdateCategory(category);
  
  				var childCategories = GetAllCategoriesByParentCategoryId(category.Id, true);
  				DeleteAllCategories(childCategories, delete);
  			}
  		}
  
  		#endregion
  
  		#region Methods
  
          public virtual void InheritAclIntoChildren(int categoryId, 
              bool touchProductsWithMultipleCategories = false,
              bool touchExistingAcls = false,
              bool categoriesOnly = false)
          {
  
              var category = GetCategoryById(categoryId);
              var subcategories = GetAllCategoriesByParentCategoryId(categoryId, true);
              var context = new ProductSearchContext { PageSize = int.MaxValue, ShowHidden = true };
              context.CategoryIds.AddRange(subcategories.Select(x => x.Id));
              context.CategoryIds.Add(categoryId);
              var products = _productService.SearchProducts(context);
              var allCustomerRoles = _customerService.GetAllCustomerRoles(true);
              var categoryCustomerRoles = _aclService.GetCustomerRoleIdsWithAccess(category);
  
              using (var scope = new DbContextScope(ctx: _aclRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false))
              {
                  _aclRepository.AutoCommitEnabled = false;
  
                  foreach (var subcategory in subcategories)
                  {
                      if (subcategory.SubjectToAcl != category.SubjectToAcl)
                      {
                          subcategory.SubjectToAcl = category.SubjectToAcl;
                          _categoryRepository.Update(subcategory);
                      }
  
                      var existingAclRecords = _aclService.GetAclRecords(subcategory).ToDictionary(x => x.CustomerRoleId);
  
                      foreach (var customerRole in allCustomerRoles)
                      {
                          if (categoryCustomerRoles.Contains(customerRole.Id))
                          {
                              if (!existingAclRecords.ContainsKey(customerRole.Id))
                              {
                                  _aclRepository.Insert(new AclRecord { CustomerRole = customerRole, CustomerRoleId = customerRole.Id, EntityId = subcategory.Id, EntityName = "Category" });
                              }
                          }
                          else
                          {
                              AclRecord aclRecordToDelete;
                              if (existingAclRecords.TryGetValue(customerRole.Id, out aclRecordToDelete))
                              {
                                  _aclRepository.Delete(aclRecordToDelete);
                              }
                          }
                      }
                  }
                  
                  _aclRepository.Context.SaveChanges();
  
                  foreach (var product in products)
                  {
                      if (product.SubjectToAcl != category.SubjectToAcl)
                      {
                          product.SubjectToAcl = category.SubjectToAcl;
                          _productRepository.Update(product);
                      }
  
                      var existingAclRecords = _aclService.GetAclRecords(product).ToDictionary(x => x.CustomerRoleId);
  
                      foreach (var customerRole in allCustomerRoles)
                      {
                          if (categoryCustomerRoles.Contains(customerRole.Id))
                          {
                              if (!existingAclRecords.ContainsKey(customerRole.Id))
                              {
                                  _aclRepository.Insert(new AclRecord { CustomerRole = customerRole, CustomerRoleId = customerRole.Id, EntityId = product.Id, EntityName = "Product" });
                              }
                          }
                          else
                          {
                              AclRecord aclRecordToDelete;
                              if (existingAclRecords.TryGetValue(customerRole.Id, out aclRecordToDelete))
                              {
                                  _aclRepository.Delete(aclRecordToDelete);
                              }
                          }
                      }
                  }
  
                  _aclRepository.Context.SaveChanges();
              }
          }
  
          public virtual void InheritStoresIntoChildren(int categoryId, 
              bool touchProductsWithMultipleCategories = false,
              bool touchExistingAcls = false,
              bool categoriesOnly = false)
          {
  
              var category = GetCategoryById(categoryId);
              var subcategories = GetAllCategoriesByParentCategoryId(categoryId, true);
              var context = new ProductSearchContext { PageSize = int.MaxValue , ShowHidden = true };
              context.CategoryIds.AddRange(subcategories.Select(x => x.Id));
              context.CategoryIds.Add(categoryId);
              var products = _productService.SearchProducts(context);
  
              var allStores = _storeService.GetAllStores();
              var categoryStoreMappings = _storeMappingService.GetStoresIdsWithAccess(category);
  
              using (var scope = new DbContextScope(ctx: _storeMappingRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false))
              {
                  _storeMappingRepository.AutoCommitEnabled = false;
  
                  foreach (var subcategory in subcategories)
                  {
                      if (subcategory.LimitedToStores != category.LimitedToStores)
                      {
                          subcategory.LimitedToStores = category.LimitedToStores;
                          _categoryRepository.Update(subcategory);
                      }
  
                      var existingStoreMappingsRecords = _storeMappingService.GetStoreMappings(subcategory).ToDictionary(x => x.StoreId);
  
                      foreach (var store in allStores)
                      {
                          if (categoryStoreMappings.Contains(store.Id))
                          {
                              if (!existingStoreMappingsRecords.ContainsKey(store.Id))
                              {
                                  _storeMappingRepository.Insert(new StoreMapping { StoreId = store.Id, EntityId = subcategory.Id, EntityName = "Category" });
                              }
                          }
                          else
                          {
                              StoreMapping storeMappingToDelete;
                              if (existingStoreMappingsRecords.TryGetValue(store.Id, out storeMappingToDelete))
                              {
                                  _storeMappingRepository.Delete(storeMappingToDelete);
                              }
                          }
                      }
                  }
  
                  _storeMappingRepository.Context.SaveChanges();
  
                  foreach (var product in products)
                  {
                      if (product.LimitedToStores != category.LimitedToStores)
                      {
                          product.LimitedToStores = category.LimitedToStores;
                          _productRepository.Update(product);
                      }
  
                      var existingStoreMappingsRecords = _storeMappingService.GetStoreMappings(product).ToDictionary(x => x.StoreId);
  
                      foreach (var store in allStores)
                      {
                          if (categoryStoreMappings.Contains(store.Id))
                          {
                              if (!existingStoreMappingsRecords.ContainsKey(store.Id))
                              {
                                  _storeMappingRepository.Insert(new StoreMapping { StoreId = store.Id, EntityId = product.Id, EntityName = "Product" });
                              }
                          }
                          else
                          {
                              StoreMapping storeMappingToDelete;
                              if (existingStoreMappingsRecords.TryGetValue(store.Id, out storeMappingToDelete))
                              {
                                  _storeMappingRepository.Delete(storeMappingToDelete);
                              }
                          }
                      }
                  }
  
                  _storeMappingRepository.Context.SaveChanges();
              }
          }
  
  		/// <summary>
          /// Delete category
          /// </summary>
          /// <param name="category">Category</param>
  		/// <param name="deleteChilds">Whether to delete child categories or to set them to no parent.</param>
  		public virtual void DeleteCategory(Category category, bool deleteChilds = false)
          {
              if (category == null)
                  throw new ArgumentNullException("category");
  
              category.Deleted = true;
              UpdateCategory(category);
  
  			var childCategories = GetAllCategoriesByParentCategoryId(category.Id, true);
  			DeleteAllCategories(childCategories, deleteChilds);
          }
          
          /// <summary>
          /// Gets all categories
          /// </summary>
          /// <param name="categoryName">Category name</param>
          /// <param name="pageIndex">Page index</param>
          /// <param name="pageSize">Page size</param>
          /// <param name="showHidden">A value indicating whether to show hidden records</param>
  		/// <param name="alias">Alias to be filtered</param>
          /// <param name="applyNavigationFilters">Whether to apply <see cref="ICategoryNavigationFilter"/> instances to the actual categories query. Never applied when <paramref name="showHidden"/> is <c>true</c></param>
  		/// <param name="ignoreCategoriesWithoutExistingParent">A value indicating whether categories without parent category in provided category list (source) should be ignored</param>
  		/// <param name="storeId">Store identifier; 0 to load all records</param>
          /// <returns>Categories</returns>
          public virtual IPagedList<Category> GetAllCategories(string categoryName = "", int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string alias = null,
  			bool applyNavigationFilters = true, bool ignoreCategoriesWithoutExistingParent = true, int storeId = 0)
          {
              var query = _categoryRepository.Table;
  
              if (!showHidden)
                  query = query.Where(c => c.Published);
  
              if (!String.IsNullOrWhiteSpace(categoryName))
                  query = query.Where(c => c.Name.Contains(categoryName) || c.FullName.Contains(categoryName));
  
  			if (!String.IsNullOrWhiteSpace(alias))
  
gt: 				query = query.Where(c => c.Alias.Contains(alias));