public void ReplaceCssImagesWithSprite(CombinatorResource resource)
        {
            Func<RuleSet, Term, bool> noSprite =
                (ruleSet, url) =>
                {
                    if (url.Value.Contains("no-sprite")
                        || ruleSet.Selectors.Any(selector => selector.SimpleSelectors.Any(simpleSelector => simpleSelector.Class == "no-sprite"))) return true;

                    // Images with a background position are not suitable for sprite generation if they aren't top-left
                    if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-position"
                        && (declaration.Expression.Terms.Count() != 2
                                || !declaration.Expression.Terms.Any(term => term.Value == "top")
                                || !declaration.Expression.Terms.Any(term => term.Value == "left")))) return true;

                    // Images with a background size are not suitable for sprite generation
                    if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-size")) return true;

                    // Images with a background repeat are not suitable for sprite generation
                    if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-repeat"
                        && (declaration.Expression.Terms.Count() != 1 || declaration.Expression.Terms.First().Value != "no-repeat"))) return true;

                    var backgroundTerms =
                        ruleSet.Declarations
                            .Where(declaration => declaration.Name == "background")
                            .SelectMany(declaration => declaration.Expression.Terms);

                    // Sized backgrounds are not suitable either
                    // LineHeightTerm is filled with a value when if in the shorthand background declaration there's the background size specified, e.g.:
                    // background: url(url-to-img) 300px 400px / 500px 600px no-repeat;
                    // Now there will be a term for 400px, having LineHeightTerm specified for 500px.
                    if (backgroundTerms.Any(term => term.LineHeightTerm != null)) return true;

                    var backgroundTermValues = backgroundTerms.Select(term => term.Value);

                    // Positioned backgrounds are not suitable either, except top-left ones
                    if (backgroundTerms
                        .Any(term =>
                            term.Value == "center" ||
                            term.Value == "top" ||
                            term.Value == "right" ||
                            term.Value == "bottom" ||
                            term.Unit != null) &&
                            !(backgroundTermValues.Contains("top") && backgroundTermValues.Contains("left"))) return true;

                    if (backgroundTermValues.Any(value => value == "repeat-x" || value == "repeat-y" || value == "repeat")) return true;

                    return false;
                };

            var images = new Dictionary<string, CssImage>();
            var stylesheet = new StylesheetParser().Parse(resource.Content);

            ProcessImageUrls(
                resource,
                stylesheet,
                (ruleSet, urlTerm) =>
                {
                    var url = urlTerm.Value;

                    if (noSprite(ruleSet, urlTerm)) return;

                    var imageContent = _resourceFileService.GetImageContent(InlineUriFactory(resource, url), 5000);

                    if (imageContent != null)
                    {
                        images[url] = new CssImage { Content = imageContent };
                    }
                });

            if (images.Count == 0) return;

            _cacheFileService.WriteSpriteStream(
                resource.Content.GetHashCode() + ".jpg",
                (stream, publicUrl) =>
                {
                    using (var sprite = new Sprite(images.Values.Select(image => image.Content)))
                    {
                        var imageEnumerator = images.Values.GetEnumerator();
                        foreach (var backgroundImage in sprite.Generate(stream, ImageFormat.Jpeg))
                        {
                            imageEnumerator.MoveNext();
                            imageEnumerator.Current.BackgroundImage = backgroundImage;
                            imageEnumerator.Current.BackgroundImage.Url = InlineUriFactory(resource, publicUrl);
                        }
                    }
                });

            ProcessImageUrls(
                resource,
                stylesheet,
                (ruleSet, urlTerm) =>
                {
                    var url = urlTerm.Value;

                    if (images.ContainsKey(url))
                    {
                        var backgroundImage = images[url].BackgroundImage;

                        var imageDeclaration = new Declaration
                        {
                            Name = "background-image",
                            Expression = new Expression
                            {
                                Terms = new List<Term>
                                        {
                                            new Term { Type = TermType.Url, Value = backgroundImage.Url.ToProtocolRelative() }
                                        }
                            }
                        };
                        ruleSet.Declarations.Add(imageDeclaration);

                        var bgPosition = backgroundImage.Position;
                        var positionDeclaration = new Declaration
                        {
                            Name = "background-position",
                            Expression = new Expression
                            {
                                Terms = new List<Term>
                                        {
                                            new Term { Type = TermType.Number, Value = bgPosition.X.ToString(), Unit = Unit.Px },
                                            new Term { Type = TermType.Number, Value = bgPosition.Y.ToString(), Unit = Unit.Px }
                                        }
                            }
                        };
                        ruleSet.Declarations.Add(positionDeclaration);
                    }
                });

            resource.Content = stylesheet.ToString();
        }
        public void ReplaceCssImagesWithSprite(CombinatorResource resource)
        {
            Func <RuleSet, Term, bool> noSprite =
                (ruleSet, url) =>
            {
                if (url.Value.Contains("no-sprite") ||
                    ruleSet.Selectors.Any(selector => selector.SimpleSelectors.Any(simpleSelector => simpleSelector.Class == "no-sprite")))
                {
                    return(true);
                }

                // Images with a background position are not suitable for sprite generation if they aren't top-left
                if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-position" &&
                                             (declaration.Expression.Terms.Count() != 2 ||
                                              !declaration.Expression.Terms.Any(term => term.Value == "top") ||
                                              !declaration.Expression.Terms.Any(term => term.Value == "left"))))
                {
                    return(true);
                }

                // Images with a background size are not suitable for sprite generation
                if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-size"))
                {
                    return(true);
                }

                // Images with a background repeat are not suitable for sprite generation
                if (ruleSet.Declarations.Any(declaration => declaration.Name == "background-repeat" &&
                                             (declaration.Expression.Terms.Count() != 1 || declaration.Expression.Terms.First().Value != "no-repeat")))
                {
                    return(true);
                }

                var backgroundTerms =
                    ruleSet.Declarations
                    .Where(declaration => declaration.Name == "background")
                    .SelectMany(declaration => declaration.Expression.Terms);

                // Sized backgrounds are not suitable either
                // LineHeightTerm is filled with a value when if in the shorthand background declaration there's the background size specified, e.g.:
                // background: url(url-to-img) 300px 400px / 500px 600px no-repeat;
                // Now there will be a term for 400px, having LineHeightTerm specified for 500px.
                if (backgroundTerms.Any(term => term.LineHeightTerm != null))
                {
                    return(true);
                }

                var backgroundTermValues = backgroundTerms.Select(term => term.Value);

                // Positioned backgrounds are not suitable either, except top-left ones
                if (backgroundTerms
                    .Any(term =>
                         term.Value == "center" ||
                         term.Value == "top" ||
                         term.Value == "right" ||
                         term.Value == "bottom" ||
                         term.Unit != null) &&
                    !(backgroundTermValues.Contains("top") && backgroundTermValues.Contains("left")))
                {
                    return(true);
                }

                if (backgroundTermValues.Any(value => value == "repeat-x" || value == "repeat-y" || value == "repeat"))
                {
                    return(true);
                }

                return(false);
            };

            var images     = new Dictionary <string, CssImage>();
            var stylesheet = new StylesheetParser().Parse(resource.Content);

            ProcessImageUrls(
                resource,
                stylesheet,
                (ruleSet, urlTerm) =>
            {
                var url = urlTerm.Value;

                if (noSprite(ruleSet, urlTerm))
                {
                    return;
                }

                var imageContent = _resourceFileService.GetImageContent(InlineUriFactory(resource, url));

                if (imageContent.Length / 1024 <= 5000)
                {
                    images[url] = new CssImage {
                        Content = imageContent
                    };
                }
            });

            if (images.Count == 0)
            {
                return;
            }

            _cacheFileService.WriteSpriteStream(
                resource.Content.GetHashCode() + ".jpg",
                (stream, publicUrl) =>
            {
                using (var sprite = new Sprite(images.Values.Select(image => image.Content)))
                {
                    var imageEnumerator = images.Values.GetEnumerator();
                    foreach (var backgroundImage in sprite.Generate(stream, ImageFormat.Jpeg))
                    {
                        imageEnumerator.MoveNext();
                        imageEnumerator.Current.BackgroundImage     = backgroundImage;
                        imageEnumerator.Current.BackgroundImage.Url = InlineUriFactory(resource, publicUrl);
                    }
                }
            });

            ProcessImageUrls(
                resource,
                stylesheet,
                (ruleSet, urlTerm) =>
            {
                var url = urlTerm.Value;

                if (images.ContainsKey(url))
                {
                    var backgroundImage = images[url].BackgroundImage;

                    var imageDeclaration = new Declaration
                    {
                        Name       = "background-image",
                        Expression = new Expression
                        {
                            Terms = new List <Term>
                            {
                                new Term {
                                    Type = TermType.Url, Value = backgroundImage.Url.ToStringWithoutScheme()
                                }
                            }
                        }
                    };
                    ruleSet.Declarations.Add(imageDeclaration);

                    var bgPosition          = backgroundImage.Position;
                    var positionDeclaration = new Declaration
                    {
                        Name       = "background-position",
                        Expression = new Expression
                        {
                            Terms = new List <Term>
                            {
                                new Term {
                                    Type = TermType.Number, Value = bgPosition.X.ToString(), Unit = Unit.Px
                                },
                                new Term {
                                    Type = TermType.Number, Value = bgPosition.Y.ToString(), Unit = Unit.Px
                                }
                            }
                        }
                    };
                    ruleSet.Declarations.Add(positionDeclaration);
                }
            });

            resource.Content = stylesheet.ToString();
        }