using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mapper;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.IO;

namespace CssSpriteGenerator
{    
    /// <summary>
    /// Represents the contents of a sprite image.
    /// 
    /// Note that this doesn't hold the image itself. That only gets generated and written to disc
    /// by method WriteSpriteImage.
    /// </summary>
     
    // TODO: automatically increase pixel depth of the sprite if the constituent images all have an indexed color scheme
    // but with widely different palettes.

    public class SpriteInfoWritable : Sprite
    {
        /// <summary>
        /// Generates an image containing the individual images as described in spriteMappings.
        /// Writes the image to disc.
        /// 
        /// This method determines the actual name of the sprite image itself.
        /// It ensures that that name doesn't clash with the names of other sprite images.
        /// </summary>
        /// <param name="fileSystem">
        /// This object will be used to write the sprite image to the sprites folder.
        /// </param>
        /// <param name="spriteImageType">
        /// The image type of the sprite, such as Png or Gif.
        /// </param>
        /// <param name="pixelFormatOverride">
        /// If this is not PixelFormat.DontCare or PixelFormat.Undefined, than this pixel format is 
        /// used for the final sprite image.
        /// 
        /// Otherwise, the pixel format is based on the pixel formats of the individual images going
        /// into the sprite image - using the pixel format of the image that requires the hightest 
        /// number of bits per pixel.
        /// </param>
        /// <param name="paletteAlgorithm">
        /// Algorithm to be used when the color depth of the sprite gets changed to
        /// an indexed color format (Format1bppIndexed, Format4bppIndexed or Format8bppIndexed).
        /// 
        /// This not only happens when pixelFormatOverride is set. The sprite gets constructed using
        /// a non-indexed format. It then gets the highest pixel format of all constituing images.
        /// That may be an index format.
        /// </param>
        /// <param name="jpegQuality">
        /// If the output format of the sprite is jpg, than this parameter can be used to reduce its quality
        /// and therefore its file size.
        /// 
        /// This parameter can be between 0 and 100. 100 gives you highest quality. 0 means that the sprite gets saved
        /// as is, without changing its quality.
        /// </param>
        /// <param name="groupUniqueOutputId">
        /// Add this string to the file name of the sprite.
        /// It encodes the output properties of the group that this sprite belongs to, such as JpegQuality.
        /// A sprite with the same constituent images but with different output settings needs to have a different name and url.
        /// </param>
        /// <returns>
        /// Url of the sprite image that was written to disc.
        /// </returns>
        public string WriteSpriteImage(
            IFileSystem fileSystem, ImageType spriteImageType,
            PixelFormat pixelFormatOverride, PaletteAlgorithm paletteAlgorithm, int jpegQuality, string groupUniqueOutputId)
        {
            string urlSpriteImage = null;

            // We'll use the combined file paths of all images in the sprite as the basis of the
            // the name of the sprite image. This because the file paths identify
            // the bitmaps that go into the sprite (the bitmaps are loaded from the image files).

            StringBuilder combinedImageFilePaths = new StringBuilder();

            // We'll also work out the PixelFormat with the highest number of bits to accommodate all images.
            // The lower the PixelFormat we settle on, the smaller the sprite will be in bytes.

            PixelFormat highestPixelFormat = PixelFormat.Format1bppIndexed;

            // Create a bitmap for the entire sprite
            using (Bitmap spriteBitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb))
            {
                using (Graphics spriteGraphics = Graphics.FromImage(spriteBitmap))
                {
                    // Draw each image in turn onto the sprite
                    foreach (IMappedImageInfo mappedImageInfo in this.MappedImages)
                    {
                        try
                        {
                            ImageInfo imageInfo = mappedImageInfo.ImageInfo as ImageInfo;
                            if (imageInfo == null)
                            {
                                throw new Exception("WriteSpriteImage: mappedImageInfo contains null ImageInfo");
                            }

                            if (imageInfo.FilePathBroken) { continue; }

                            highestPixelFormat = PixelFormatUtils.HigherPixelFormat(highestPixelFormat, imageInfo.ImageBitmap.PixelFormat);

                            // Create Rectangle specifying portion of image to copy to the sprite. Ensure this rectangle
                            // takes in the entire image.

                            Rectangle imageRect = new Rectangle(0, 0, imageInfo.Width, imageInfo.Height);

                            // Create a clone of the image bitmap, and give that clone the same horizontal and vertical resolution
                            // as the sprite bitmap. This way, the image bitmap will be copied into the sprite pixel by pixel.
                            // If the resolutions are different, the same number of pixels corresponds to different physical sizes,
                            // and DrawImage takes physical size into account when drawing an image. 
                            Bitmap clonedImageBitmap = imageInfo.ImageBitmap.Clone(imageRect, spriteBitmap.PixelFormat);
                            clonedImageBitmap.SetResolution(spriteBitmap.HorizontalResolution, spriteBitmap.VerticalResolution);

                            // Draw the image onto the sprite
                            spriteGraphics.DrawImage(
                                clonedImageBitmap,
                                mappedImageInfo.X, mappedImageInfo.Y,
                                imageRect,
                                GraphicsUnit.Pixel);

                            // Add the UniqueId of the ImageInfo, consisting of file path of the image and its width and height.
                            // Need the dimensions, in case you have the image multiple times on different pages,
                            // resized at different sizes.
                            combinedImageFilePaths.Append(imageInfo.UniqueId());

                            // Now that the ImageInfo's bitmap has been used, it can be disposed
                            imageInfo.DisposeBitmap();
                        }
                        catch (FileNotFoundException)
                        {
                            // We get here if the image's bitmap could not be read from disk.

                            // Note that if ExceptionOnMissingFile had been active, this situation would have been
                            // checked by MapPath, so in that case we would never have gotten here.
                        }
                    }

                    // Create file name of the sprite

                    string spriteFilename =
                        groupUniqueOutputId +
                        UrlUtils.StringHash(combinedImageFilePaths.ToString()) +
                        UrlUtils.ExtensionForImageType(spriteImageType);

                    if ((pixelFormatOverride != PixelFormat.DontCare) && (pixelFormatOverride != PixelFormat.Undefined))
                    {
                        highestPixelFormat = pixelFormatOverride;
                    }

                    // ----------------------------------------
                    // Create bitmap with optimized pixel format (fewer bits per pixel means that the sprite takes fewer bytes).
                    // If that fails due to an exception or otherwise, just write the sprite in its current pixel format.

                    Bitmap colorAdjustedBitmap = null;

                    try
                    {
                        colorAdjustedBitmap =
                            ImageUtils.ChangePixelFormat(spriteBitmap, highestPixelFormat, paletteAlgorithm);

                        spriteBitmap.Dispose();
                    }
                    catch (Exception)
                    {
                        // Just use current bit map
                        colorAdjustedBitmap = spriteBitmap;
                    }

                    urlSpriteImage = fileSystem.WriteImageFile(spriteFilename, colorAdjustedBitmap, spriteImageType, jpegQuality);
                }
            }

            return urlSpriteImage;
        }
    }
}
