/// <summary>Gets the unique object for all the settings, used for determining unique cache id.</summary>
 /// <param name="imageHasher">The image Hasher.</param>
 /// <param name="resourcePivotKeys">The resource Pivot Keys.</param>
 /// <param name="dpiResources">The dpi Resources.</param>
 /// <returns>The settings object.</returns>
 private object GetVarBySettings(FileHasherActivity imageHasher, IEnumerable <ResourcePivotKey> resourcePivotKeys, IEnumerable <IDictionary <string, string> > dpiResources)
 {
     return(new
     {
         resourcePivotKeys,
         dpiResources,
         this.ShouldExcludeProperties,
         this.ShouldValidateForLowerCase,
         this.ShouldMergeMediaQueries,
         this.ShouldOptimize,
         this.ShouldAssembleBackgroundImages,
         this.ShouldMinify,
         this.ShouldPreventOrderBasedConflict,
         this.ShouldMergeBasedOnCommonDeclarations,
         this.IgnoreImagesWithNonDefaultBackgroundSize,
         this.HackSelectors,
         this.BannedSelectors,
         this.NonMergeSelectors,
         this.ImageAssembleReferencesToIgnore,
         this.OutputUnit,
         this.OutputUnitFactor,
         this.ImageAssemblyPadding,
         HashImages = imageHasher == null,
         this.ForcedSpritingImageType,
         this.ErrorOnInvalidSprite,
         this.ImageAssembleScanDestinationFile,
         this.ImageSpritingLogPath,
     });
 }
        internal void Execute(ContentItem contentItem = null, FileHasherActivity imageHasher = null)
        {
            if (contentItem == null)
            {
                if (string.IsNullOrWhiteSpace(this.SourceFile))
                {
                    throw new ArgumentException("MinifyCssActivity - The source file cannot be null or whitespace.");
                }

                if (!File.Exists(this.SourceFile))
                {
                    throw new FileNotFoundException("MinifyCssActivity - The source file cannot be found.", this.SourceFile);
                }
            }

            if (string.IsNullOrWhiteSpace(this.DestinationFile))
            {
                throw new ArgumentException("MinifyCssActivity - The destination file cannot be null or whitespace.");
            }

            if (contentItem == null)
            {
                contentItem = ContentItem.FromFile(this.SourceFile, Path.IsPathRooted(this.SourceFile) ? this.SourceFile.MakeRelativeToDirectory(this.context.Configuration.SourceDirectory) : this.SourceFile);
            }

            var minifyresult = this.Process(contentItem, imageHasher);

            var css = minifyresult.Css.FirstOrDefault();

            if (css != null)
            {
                css.WriteTo(this.DestinationFile);
            }

            if (minifyresult.SpritedImages != null && minifyresult.SpritedImages.Any())
            {
                foreach (var spritedImage in minifyresult.SpritedImages)
                {
                    spritedImage.WriteToContentPath(this.context.Configuration.DestinationDirectory);
                }
            }

            if (minifyresult.HashedImages != null && minifyresult.HashedImages.Any())
            {
                foreach (var hashedImage in minifyresult.HashedImages)
                {
                    hashedImage.WriteToRelativeHashedPath(this.context.Configuration.DestinationDirectory);
                }
            }
        }
        /// <summary>Hash the images.</summary>
        /// <param name="cssContent">The css content.</param>
        /// <param name="imageHasher">The image Hasher.</param>
        /// <param name="cacheSection">The cache section.</param>
        /// <param name="threadContext">The context.</param>
        /// <param name="sourceImages">The source Images.</param>
        /// <param name="missingImage">The missing Image.</param>
        /// <returns>The css with hashed images.</returns>
        private static Tuple <string, IEnumerable <ContentItem> > HashImages(string cssContent, FileHasherActivity imageHasher, ICacheSection cacheSection, IWebGreaseContext threadContext, IDictionary <string, string> sourceImages, string missingImage)
        {
            return(threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.ImageHash).Execute(() =>
            {
                var contentImagesToHash = new HashSet <string>();
                var hashedContentItems = new List <ContentItem>();
                var hashedImages = new Dictionary <string, string>();
                cssContent = UrlHashRegexPattern.Replace(
                    cssContent,
                    match =>
                {
                    var urlToHash = match.Groups["url"].Value;
                    var extraInfo = match.Groups["extra"].Value;

                    if (ResourcesResolver.LocalizationResourceKeyRegex.IsMatch(urlToHash))
                    {
                        return match.Value;
                    }

                    var normalizedHashUrl = urlToHash.NormalizeUrl();

                    var imageContentFile = sourceImages.TryGetValue(normalizedHashUrl);
                    if (imageContentFile == null && !string.IsNullOrWhiteSpace(missingImage))
                    {
                        imageContentFile = sourceImages.TryGetValue(missingImage);
                    }

                    if (imageContentFile == null)
                    {
                        throw new BuildWorkflowException("Could not find a matching source image for url: {0}".InvariantFormat(urlToHash));
                    }

                    if (contentImagesToHash.Add(normalizedHashUrl))
                    {
                        // Add the image as end result
                        var imageContentItem = ContentItem.FromFile(imageContentFile, normalizedHashUrl);
                        imageContentItem = imageHasher.Hash(imageContentItem);
                        cacheSection.AddSourceDependency(imageContentFile);
                        hashedContentItems.Add(imageContentItem);

                        imageContentFile =
                            Path.Combine(
                                imageHasher.BasePrefixToAddToOutputPath ?? Path.AltDirectorySeparatorChar.ToString(CultureInfo.InvariantCulture),
                                imageContentItem.RelativeHashedContentPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));

                        hashedImages.Add(normalizedHashUrl, imageContentFile);
                    }
                    else
                    {
                        imageContentFile = hashedImages[normalizedHashUrl];
                    }

                    return "url(" + imageContentFile + extraInfo + ")";
                });

                return Tuple.Create(cssContent, (IEnumerable <ContentItem>)hashedContentItems);
            }));
        }
        internal MinifyCssResult Process(ContentItem contentItem, FileHasherActivity imageHasher = null)
        {
            if (imageHasher != null)
            {
                this.availableSourceImages = this.context.GetAvailableFiles(this.context.Configuration.SourceDirectory, this.ImageDirectories, this.ImageExtensions, FileTypes.Image);
            }

            var cssContent = contentItem.Content;

            var minifiedContentItems     = new BlockingCollection <ContentItem>();
            var hashedImageContentItems  = new BlockingCollection <ContentItem>();
            var spritedImageContentItems = new BlockingCollection <ContentItem>();
            var mergedResources          = ResourcePivotActivity.GetUsedGroupedResources(cssContent, this.MergedResources);

            var dpiValues = this.Dpi;

            if (dpiValues == null || !dpiValues.Any())
            {
                dpiValues = new HashSet <float>(new[] { 1f });
            }

            var pivots = GetMinifyCssPivots(contentItem, dpiValues, mergedResources, this.DpiResources).ToArray();

            var nonIgnoredPivots = pivots.Where(p => !this.context.TemporaryIgnore(p.NewContentResourcePivotKeys)).ToArray();

            var parsedStylesheetNode = CssParser.Parse(this.context, cssContent, false);

            this.context.ParallelForEach(
                item => new[] { SectionIdParts.MinifyCssActivity },
                nonIgnoredPivots,
                (threadContext, pivot, parallelLoopState) =>
            {
                ContentItem minifiedContentItem = null;
                var resourceContentItem         = ContentItem.FromContent(contentItem.Content, pivot.NewContentResourcePivotKeys);
                var result = threadContext
                             .SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.Process)
                             .MakeCachable(resourceContentItem, this.GetVarBySettings(imageHasher, pivot.NewContentResourcePivotKeys, pivot.MergedResource))
                             .RestoreFromCacheAction(cacheSection =>
                {
                    minifiedContentItem = cacheSection.GetCachedContentItem(CacheFileCategories.MinifiedCssResult, contentItem.RelativeContentPath, contentItem.AbsoluteDiskPath, pivot.NewContentResourcePivotKeys);
                    hashedImageContentItems.AddRange(cacheSection.GetCachedContentItems(CacheFileCategories.HashedImage));
                    spritedImageContentItems.AddRange(cacheSection.GetCachedContentItems(CacheFileCategories.HashedSpriteImage));

                    if (minifiedContentItem == null)
                    {
                        context.Log.Error("Css minify cache result is null");
                        return(false);
                    }

                    if (spritedImageContentItems.Any(hi => hi == null))
                    {
                        context.Log.Error("Sprited image cache result is null");
                        return(false);
                    }

                    if (hashedImageContentItems.Any(hi => hi == null))
                    {
                        context.Log.Error("Hashed image cache result is null");
                        return(false);
                    }

                    return(true);
                })
                             .Execute(cacheSection =>
                {
                    try
                    {
                        // Apply all configured visitors, including, validating, optimizing, minifying and spriting.

                        // Applying of resources
                        var stylesheetNode = ApplyResources(parsedStylesheetNode, pivot.MergedResource, threadContext) as StyleSheetNode;

                        // Validation
                        stylesheetNode = this.ApplyValidation(stylesheetNode, threadContext) as StyleSheetNode;

                        // Optimization
                        stylesheetNode = this.ApplyOptimization(stylesheetNode, threadContext) as StyleSheetNode;

                        // Spriting
                        stylesheetNode = this.ApplySpriting(stylesheetNode, pivot.Dpi, spritedImageContentItems, threadContext) as StyleSheetNode;

                        // Output css as string
                        var processedCssContent = threadContext.SectionedAction(SectionIdParts.MinifyCssActivity, SectionIdParts.PrintCss).Execute(() =>
                                                                                                                                                   this.ShouldMinify ? stylesheetNode.MinifyPrint() : stylesheetNode.PrettyPrint());

                        // TODO: Hash the images on the styielsheetnode not on the css result.
                        // Hash images on the result css
                        if (imageHasher != null)
                        {
                            var hashResult      = HashImages(processedCssContent, imageHasher, cacheSection, threadContext, this.availableSourceImages, this.MissingImageUrl);
                            processedCssContent = hashResult.Item1;
                            hashedImageContentItems.AddRange(hashResult.Item2);
                        }

                        minifiedContentItem = ContentItem.FromContent(processedCssContent, this.DestinationFile, null, pivot.NewContentResourcePivotKeys);
                        cacheSection.AddResult(minifiedContentItem, CacheFileCategories.MinifiedCssResult);
                    }
                    catch (Exception ex)
                    {
                        context.Log.Error(ex, ex.ToString());
                        return(false);
                    }

                    return(true);
                });

                Safe.Lock(minifiedContentItems, () => minifiedContentItems.Add(minifiedContentItem));

                if (!result)
                {
                    context.Log.Error("An errror occurred while minifying '{0}' with resources '{1}'".InvariantFormat(contentItem.RelativeContentPath, pivot));
                }

                return(result);
            });

            return(new MinifyCssResult(
                       minifiedContentItems,
                       spritedImageContentItems.DistinctBy(hi => hi.RelativeContentPath).ToArray(),
                       hashedImageContentItems.DistinctBy(hi => hi.RelativeContentPath).ToArray()));
        }