using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;

//TODO: use locking when calculating cache entry

//TODO: consider caching the original bitmap as it lives on disk (provided it is small, eg. use width/height restriction).
// Must dispose the bitmap if it disappears from cache (use handler)
// Don't cache the bitmap itself, convert to memory stream, than cache that. See:
// http://stackoverflow.com/questions/3958131/getting-a-bitmap-from-the-cache


namespace CssSpriteGenerator
{
    /// <summary>
    /// Helps you maintain information about an image with a given url
    /// </summary>
    public class StoredImage
    {
        // Url of the image
        private string _originalImageFilePath = null;

        private long _originalImageFileSize = -1;

        // Holds the bitmap of the image. This bitmap may have been resized from the original on disk.
        private Bitmap _bitmap = null;

        // The size of the image as it lives on disk
        private Size _originalImageSize = new Size(-1, -1);

        private bool _filePathBroken = false;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="url"></param>
        public StoredImage()
        {
        }

        /// <summary>
        /// True if it is known that the OriginalImageFilePath is broken
        /// (that is, doesn't point to a file, or the file cannot be read into a bitmap).
        /// </summary>
        /// <param name="url"></param>
        public bool FilePathBroken
        {
            get { return _filePathBroken; }
        }

        /// <summary>
        /// The file path of the image.
        /// 
        /// Not much can be done with the image until you set the file path.
        /// </summary>
        /// <param name="url"></param>
        public string OriginalImageFilePath 
        {
            get { return _originalImageFilePath; }
            set { _originalImageFilePath = value; }
        }

        /// <summary>
        /// Returns the bitmap.
        /// </summary>
        /// <param name="resizedSize">
        /// The bitmap needs to be resized to this.
        /// If the resizedSize is greater than the original size of the bitmap, than the original bitmap
        /// is returned - this method won't scale up images.
        /// 
        /// If both width and height are -1, the original bitmap from disk is returned.
        /// 
        /// If only width or only height are -1, you get an exception.
        /// 
        /// Throws an FileNotFoundException exception if the image file could not be found.
        /// </param>
        /// <returns></returns>
        public Bitmap ImageBitmap(Size resizedSize)
        {
            if (SizeUtils.IsEmptySize(resizedSize))
            {
                EnsureOriginalBitmap();
                return _bitmap;
            }

            if (SizeUtils.IsCompleteSize(resizedSize))
            {
                // If you already have a bitmap of the required size, return it.
                if ((_bitmap != null) && (_bitmap.Size == resizedSize))
                {
                    return _bitmap;
                }

                // If you don't, make sure you have the original bitmap and then resize it.
                // You want the original so you don't resize one bitmap multiple times (would look bad).

                EnsureOriginalBitmap();
                ImageUtils.ResizeBitmap(ref _bitmap, resizedSize);
                return _bitmap;
            }

            throw new Exception(
                string.Format("StoredImage.ImageBitmap - resizedSize={0}, but you cannot pass a Size with only width or height set to this method", resizedSize));
        }

        /// <summary>
        /// Returns the aspect ratio of the image on disk.
        /// 
        /// Aspect ratio is width / height.
        /// </summary>
        /// <returns></returns>
        public float OriginalAspectRatio()
        {
            Size originalImageSize = OriginalImageSize();
            float aspectRatio = (((float)originalImageSize.Width) / originalImageSize.Height);

            return aspectRatio;
        }

        /// <summary>
        /// Returns the width and height of the image on disk.
        /// </summary>
        /// <returns></returns>
        public Size OriginalImageSize()
        {
            if (!SizeUtils.IsEmptySize(_originalImageSize)) { return _originalImageSize; }

            _originalImageSize = CacheUtils.FileBasedEntry<Size>(CacheUtils.CacheEntryId.OriginalImageSize, _originalImageFilePath);
            if (_originalImageSize == default(Size))
            {
                // EnsureOriginalBitmap sets _originalImageSize and stores it in cache,
                // so other pages can use it immediately.
                EnsureOriginalBitmap();
            }

            return _originalImageSize;
        }

        /// <summary>
        /// Disposes the bitmap related to the image.
        /// Does nothing if there is no bitmap.
        /// </summary>
        public void DisposeBitmap()
        {
            if (_bitmap == null) { return; }

            _bitmap.Dispose();

            _bitmap = null;
        }

        /// <summary>
        /// Gets the image size in bytes.
        /// 
        /// If the image was resized, than this method tries to approximate the size as it would be
        /// if the resized image had been on disk.
        /// </summary>
        public long GetImageFileSize() 
        {
            if (_originalImageFileSize == -1)
            {
                _originalImageFileSize = CacheUtils.FileBasedEntry<long>(CacheUtils.CacheEntryId.ImageFileSize, _originalImageFilePath);
                if (_originalImageFileSize == default(long))
                {
                    try
                    {
                        FileInfo fi = new FileInfo(_originalImageFilePath);
                        _originalImageFileSize = fi.Length;
                        CacheUtils.InsertFileBasedEntry<long>(CacheUtils.CacheEntryId.ImageFileSize, _originalImageFilePath, _originalImageFileSize);
                    }
                    catch (Exception e)
                    {
                        _filePathBroken = true;

                        throw new FileNotFoundException(
                            string.Format("File \"{0}\" not found. See inner exception.", _originalImageFilePath), _originalImageFilePath, e);
                    }
                }
            }

            // --------------
            // If image was resized, adjust returned size

            if (_bitmap != null)
            {
                Size bitmapSize = _bitmap.Size;
                Size originalImageSize = OriginalImageSize();

                if (bitmapSize != originalImageSize)
                {
                    long bitmapArea = SizeUtils.Area(bitmapSize);
                    long originalImageArea = SizeUtils.Area(originalImageSize);
                    long estimatedImageSize = (long)Math.Round(((float)_originalImageFileSize * bitmapArea) / originalImageArea);

                    return estimatedImageSize;
                }
            }

            return _originalImageFileSize;
        }

        /// <summary>
        /// Returns true if the original bitmap (as it lives on disk) is present in _bitmap.
        /// </summary>
        /// <returns></returns>
        private bool OriginalBitmapLoaded()
        {
            if (_bitmap == null) { return false; }

            // If the size of the present bitmap is that of the bitmap on disk, than you've got the original.
            return (_bitmap.Size == OriginalImageSize());
        }

        /// <summary>
        /// If the original bitmap is not loaded, loads the bitmap from disk and puts it in _bitmap.
        /// Sets _originalImageSize as well.
        /// </summary>
        /// <returns></returns>
        private void EnsureOriginalBitmap()
        {
            if (OriginalBitmapLoaded()) { return; }

            if (_bitmap != null) { _bitmap.Dispose(); }

            try
            {
                _bitmap = new Bitmap(_originalImageFilePath);
            }
            catch (Exception e)
            {
                _filePathBroken = true;

                throw new FileNotFoundException(
                    string.Format("File \"{0}\" not found. See inner exception.", _originalImageFilePath), _originalImageFilePath, e);
            }

            _originalImageSize = _bitmap.Size;

            // Cache original image size, so other pages can use it right away
            CacheUtils.InsertFileBasedEntry<Size>(CacheUtils.CacheEntryId.OriginalImageSize, _originalImageFilePath, _originalImageSize);
        }
    }
}
