using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.Adapters;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Text.RegularExpressions;
using System.Web;
using System.IO;
using System.Collections;

// TODO: Make sure that image tags within comments are not processed
// TODO: if generated css is small, make it inline to save a file load. 
// with inlineSpriteStyles you can inline styles for sprites that replace img tags, but if you use cssImage element, it alwasys generates a .css

namespace CssSpriteGenerator
{
    public class CSSSpriteGenerator_PageAdapter : PageAdapter
    {
        protected override void Render(HtmlTextWriter writer)
        {
            ConfigSection cs = ConfigSection.CurrentConfigSection();

            if (ConfigSection.OptionIsActive(cs.Active))
            {

                // -----------------
                // Resolve the SubsetOf relationships in the groups.
                //
                // Note that if you pass cs.Groups into ResolveSubsets, 
                // rather than groups, the changes made by ResolveSubsets
                // are not persisted!

                List<Group> groups = cs.Groups();
                Group.ResolveSubsets(groups);

                // -------------
                // Generate the sprites and update the page.

                Generator generator = new Generator(cs, groups);

                if (ConfigSection.OptionIsActive(cs.ProcessPageImages))
                {
                    AddPageImageReferences(generator, Control.Page.Controls, cs);
                }

                if (ConfigSection.OptionIsActive(cs.ProcessCssImages))
                {
                    AddCSSImageReferences(generator, cs.CssImages, cs);
                }

                if (ConfigSection.OptionIsActive(cs.ProcessFolderImages))
                {
                    string siteRootFolder = HttpContext.Current.Server.MapPath(HttpContext.Current.Request.ApplicationPath);
                    AddFolderImageReferences(generator, siteRootFolder, cs.ImageFolderPathMatch, cs.GeneratedFolder);
                }

                IFileSystem fileSystem = new FileSystem(cs.GeneratedFolder);
                string cssFileUrl;
                generator.GenerateSprites(fileSystem, out cssFileUrl);

                // Add the stylesheet to the header

                if (!string.IsNullOrEmpty(cssFileUrl))
                {
                    HtmlHead htmlHead = Control.Page.Header;
                    if (htmlHead == null)
                    {
                        throw new Exception("The page has to have a head tag with runat=\"server\".");
                    }

                    htmlHead.Controls.Add(new LiteralControl(Stylesheet.CssLink(cssFileUrl)));
                }
            }

            // -------------
            // Render the page to the writer, to keep the framework happy

            base.Render(writer);
        }

        /// <summary>
        /// Goes through the page, visits all images, generates an IimageReference for each
        /// and adds them to the generator.
        /// </summary>
        /// <param name="generator">
        /// The generator to add the IimageReference to
        /// </param>
        /// <param name="cc">
        /// Control collection that will be checked for images. You would call this
        /// method with the collection of child controls of the Page itself.
        /// </param>
        private void AddPageImageReferences(Generator generator, ControlCollection cc, ConfigSection cs)
        {
            foreach (Control c in cc)
            {
                if (c is LiteralControl)
                {
                    AddLiteralImageReferences(generator, c as LiteralControl, cs);
                }
                else if (c is HtmlImage)
                {
                    HtmlImage_ImageReference htmlImage_ImageReference = new HtmlImage_ImageReference(c as HtmlImage, cs);
                    generator.AddImageReference(htmlImage_ImageReference);
                }
                else if (c is HyperLink)
                {
                    HyperLink hl = (HyperLink)c;
                    if (!string.IsNullOrEmpty(hl.ImageUrl))
                    {
                        HyperLink_ImageReference hyperLink_ImageReference = new HyperLink_ImageReference(hl, cs);
                        generator.AddImageReference(hyperLink_ImageReference);
                    }
                }
                else if (c is Image)
                {
                    Image_ImageReference image_ImageReference = new Image_ImageReference(c as Image, cs);
                    generator.AddImageReference(image_ImageReference);
                }
                else
                {
                    AddPageImageReferences(generator, c.Controls, cs);
                }
            }
        }

        /// <summary>
        /// Goes through the contents of a literal, visits all images, generates an IimageReference for each
        /// and adds them to the generator.
        /// </summary>
        /// <param name="generator">
        /// The generator to add the IimageReference to
        /// </param>
        private void AddLiteralImageReferences(Generator generator, LiteralControl lc, ConfigSection cs)
        {
            string html = lc.Text;
            IList<ImageTag> imageTags = HtmlUtils.ImagesInHtml(html);

            foreach (ImageTag imageTag in imageTags)
            {
                LiteralControl_ImageReference literalControl_ImageReference =
                    new LiteralControl_ImageReference(lc, imageTag, cs);

                generator.AddImageReference(literalControl_ImageReference);
            }
        }

        /// <summary>
        /// Generates an IImageReference for each images in cssImages and adds them
        /// to the generator.
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="cssImages"></param>
        private void AddCSSImageReferences(Generator generator, List<CssImage> cssImages, ConfigSection cs)
        {
            foreach (CssImage cssImage in cssImages)
            {
                Css_ImageReference css_ImageReference = new Css_ImageReference(cssImage, cs);
                generator.AddImageReference(css_ImageReference);
            }
        }

        /// <summary>
        /// Generates an IImageReference for each images found in the root folder of the site and its 
        /// sub folders and adds them to the generator. However, only images whose paths match 
        /// ImageFolderPathMatch will have an IImageReference created.
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="cssImages"></param>
        private void AddFolderImageReferences(Generator generator, string rootFolder, string imageFolderPathMatch, string generatedFolder)
        {
            string cacheKey = rootFolder + imageFolderPathMatch + generatedFolder;

            List<string> folderImagePaths =
                CacheUtils.Entry<List<string>>(CacheUtils.CacheEntryId.folderImagePaths, cacheKey);

            if (folderImagePaths == null)
            {
                folderImagePaths = new List<string>();

                Regex imageFolderPathMatchRegex = new Regex(imageFolderPathMatch, RegexOptions.Compiled | RegexOptions.IgnoreCase);

                // -------------------------------------------------------
                // Build regex to exclude sprite folder from the found paths

                string cleanedSpriteFolder = generatedFolder.Trim(new char[] { '\\', '/' });

                string spriteFolderRegexPatternForFiles = UrlUtils.EscapedForRegex(@"\" + cleanedSpriteFolder + @"\");
                Regex spriteFolderRegexForFiles = new Regex(spriteFolderRegexPatternForFiles, RegexOptions.Compiled | RegexOptions.IgnoreCase);

                string spriteFolderRegexPatternForFolders = UrlUtils.EscapedForRegex(@"\" + cleanedSpriteFolder) + "$";
                Regex spriteFolderRegexForFolders = new Regex(spriteFolderRegexPatternForFolders, RegexOptions.Compiled | RegexOptions.IgnoreCase);

                // -------------------------------------------------------

                string[] searchExtensions = { "png", "gif", "jpg" };
                foreach (string searchExtension in searchExtensions)
                {
                    // Even if the searchPattern is ".png", Directory.GetFiles still matches filed ending in ".png-original"!
                    // So use not just the search pattern, but a regex as well.

                    string searchPattern = "*." + searchExtension;
                    string searchRegexPattern = @"\." + searchExtension + "$";
                    Regex searchRegex = new Regex(searchRegexPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);

                    string[] imagePaths = Directory.GetFiles(rootFolder, searchPattern, SearchOption.AllDirectories);

                    foreach (string imagePath in imagePaths)
                    {
                        if (imageFolderPathMatchRegex.IsMatch(imagePath) &&
                            searchRegex.IsMatch(imagePath) &&
                            (!spriteFolderRegexForFiles.IsMatch(imagePath)))
                        {
                            folderImagePaths.Add(imagePath);
                        }
                    }
                }

                // -------------------------
                // Create cache dependency on root directory and all its sub directories, except for the sprites directory.
                // Than cache the found folder image paths.

                string[] folders = Directory.GetDirectories(rootFolder, "*", SearchOption.AllDirectories);

                // Create a list of the folders, without the sprite folder (if there is one).
                // This code is not very efficient, but it should run only seldom
                List<string> cleanedFolders = folders.Where(f => !spriteFolderRegexForFolders.IsMatch(f)).ToList<string>();

                // Add the rootfolder (Directory.GetDirectories only returns the sub dirs of the root folder).
                cleanedFolders.Add(rootFolder);

                // Insert cache entry
                CacheUtils.InsertEntry<List<string>>(CacheUtils.CacheEntryId.folderImagePaths, cacheKey, folderImagePaths, cleanedFolders.ToArray<string>());
            }

            // ------------------

            foreach (string imagePath in folderImagePaths)
            {
                Folder_ImageReference folder_ImageReference = new Folder_ImageReference(imagePath);
                generator.AddImageReference(folder_ImageReference);
            }
        }
    }
}


