using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Hosting;
using System.Security.Permissions;
using System.Web.Caching;
using System.IO;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Configuration;
using System.Diagnostics;
using fbs.ImageResizer;
using fbs;
using System.Collections.Specialized;
using System.Drawing;
using System.Drawing.Imaging;
using PhotoshopFile;
using PhotoshopFile.Text;
namespace PsdRenderer
{
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.High)]
    public class PsdProvider : VirtualPathProvider
    {


        public PsdProvider()
            : base()
        {
        }
        /// <summary>
        /// Returns the renderer object selected in the querystring
        /// </summary>
        /// <returns></returns>
        public static IPsdRenderer GetSelectedRenderer(NameValueCollection queryString)
        {
            //Renderer object
            IPsdRenderer renderer = null;
            ////The querystring-specified renderer name
            //string sRenderer = null;
            //if (queryString["renderer"] != null) sRenderer = queryString["renderer"].ToLowerInvariant();
            ////Build the correct renderer
            //if (("graphicsmill").Equals(sRenderer))
            //    renderer = new GraphicsMillRenderer();
            //else
            //There is only ONE renderer now
            renderer = new PsdPluginRenderer();
            return renderer;
        }

        /// <summary>
        /// Creates a callback that can be used to filter layer visibility.
        /// </summary>
        /// <param name="queryString"></param>
        /// <returns></returns>
        private static ShowLayerDelegate BuildLayerCallback(NameValueCollection queryString)
        {
            //Which layers do we show?
            PsdCommandSearcher searcher = new PsdCommandSearcher(new PsdCommandBuilder(queryString));

            return delegate(int index, string name, bool visibleNow)
            {
                Nullable<bool> show = (searcher.getVisibility(name));
                if (show == null) return visibleNow;
                else return show.Value;
            };
        }
        /// <summary>
        /// Mixes the specified color (using the included alpha value as a weight) into the bitmap, leaving the bitmap's original alpha values untouched.
        /// </summary>
        /// <param name="b"></param>
        /// <param name="c"></param>
        private static void ColorBitmap(Bitmap b, Color c)
        {
            double weightedR = c.R * c.A;
            double weightedG = c.G * c.A;
            double weightedB = c.B * c.A;
            double originalWeight = 255 - c.A;

            int width = b.Width; int height = b.Height;
            BitmapData bd = b.LockBits(new Rectangle(0,0,width,height), ImageLockMode.ReadWrite, b.PixelFormat);
            unsafe
            {
                byte* pCurrRowPixel = (byte*)bd.Scan0.ToPointer();
                for (int y = 0; y < height; y++)
                {
                    int rowIndex = y * width;
                    PhotoshopFile.ImageDecoder.PixelData* pCurrPixel = (PhotoshopFile.ImageDecoder.PixelData*)pCurrRowPixel;
                    for (int x = 0; x < width; x++)
                    {
                        pCurrPixel->Red = (byte)(((double)pCurrPixel->Red * originalWeight + weightedR) / 255);
                        pCurrPixel->Green = (byte)(((double)pCurrPixel->Green * originalWeight + weightedG) / 255);
                        pCurrPixel->Blue = (byte)(((double)pCurrPixel->Blue * originalWeight + weightedB) / 255);
                        pCurrPixel += 1;
                    }
                    pCurrRowPixel += bd.Stride;
                }
            }

            b.UnlockBits(bd);
        }

        private static ComposeLayerDelegate BuildModifyLayerCallback(NameValueCollection queryString)
        {
            PsdCommandSearcher searcher = new PsdCommandSearcher(new PsdCommandBuilder(queryString));

            return delegate(Graphics g, Bitmap b, object layer)
            {
                PhotoshopFile.Layer l = (PhotoshopFile.Layer)layer;
                //See if this layer is supposed to be re-colored.
                Nullable<Color> color = searcher.getColor(l.Name);


                //See if we need to re-draw this text layer
                Nullable<bool> redraw = searcher.getRedraw(l.Name);
                if (redraw != null && redraw == true)
                {
                    //Re-draw the text directly, ignoring the bitmap
                    new TextLayerRenderer(l).Render(g, color, searcher.getReplacementText(l.Name));
                }
                else
                {
                    //Draw the existing bitmap
                    //Blend color into bitmap
                    if (color != null) ColorBitmap(b, color.Value);
                    //Draw image
                    g.DrawImage(b, l.Rect);
                }
            };
        }

        public NameValueCollection QueryString
        {
            get
            {
                if (HttpContext.Current.Items["modifiedQueryString"] != null) 
                    return (NameValueCollection)HttpContext.Current.Items["modifiedQueryString"];
                else 
                    return HttpContext.Current.Request.QueryString;
            }
        }


        public Stream getStream(string virtualPath)
        {
            return getStream(virtualPath, QueryString);
        }
        /// <summary>
        /// Returns a stream to the 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static Stream getStream(string virtualPath, NameValueCollection queryString)
        {

            Stopwatch sw = new Stopwatch();
            sw.Start();

            System.Drawing.Bitmap b = getBitmap(virtualPath, queryString);
            //Memory stream for encoding the file
            MemoryStream ms = new MemoryStream();
            //Encode image to memory stream, then seek the stream to byte 0
            using (b)
            {
                //Use whatever settings appear in the URL 
                ImageOutputSettings ios = new ImageOutputSettings(ImageOutputSettings.GetImageFormatFromExtension(System.IO.Path.GetExtension(virtualPath)),queryString);
                ios.SaveImage(ms, b);
                ms.Seek(0, SeekOrigin.Begin); //Reset stream for reading
            }

            sw.Stop();
            trace("Total time, including encoding: " + sw.ElapsedMilliseconds.ToString() + "ms");

            return ms;
        }
        public System.Drawing.Bitmap getBitmap(string virtualPath){
            return getBitmap(virtualPath,QueryString);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap getBitmap(string virtualPath, NameValueCollection queryString)
        {
            //Renderer object
            IPsdRenderer renderer = GetSelectedRenderer(queryString);

            //Bitmap we will render to
            System.Drawing.Bitmap b = null;

            MemCachedFile file = MemCachedFile.GetCachedFile(getPhysicalPath(virtualPath));
            using (Stream s = file.GetStream())
            {
                //Time just the parsing/rendering
                Stopwatch swRender = new Stopwatch();
                swRender.Start();

                IList<IPsdLayer> layers = null;
                //Use the selected renderer to parse the file and compose the layers, using this delegate callback to determine which layers to show.
                b = renderer.Render(s, out layers, BuildLayerCallback(queryString), BuildModifyLayerCallback(queryString));

                //Save layers for later use
                file.setSubkey("layers_" + renderer.ToString(), layers);


                //How fast?
                swRender.Stop();
                trace("Using encoder " + renderer.ToString() + ", rendering stream to a composed Bitmap instance took " + swRender.ElapsedMilliseconds.ToString() + "ms");
            }
            return b;
        }

 

        private static void trace(string msg)
        {
            if (HttpContext.Current == null)
                System.Diagnostics.Debug.Write(msg);
            else
                HttpContext.Current.Trace.Write(msg);
        }

        public static Size getPsdSize(string virtualPath, NameValueCollection queryString)
        {
            return getAllLayers(virtualPath, queryString)[0].Rect.Size;
        }

        public static IList<IPsdLayer> getAllLayers(string virtualPath, NameValueCollection queryString)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();

            //Renderer object
            IPsdRenderer renderer = GetSelectedRenderer(queryString);
            //File
            MemCachedFile file = MemCachedFile.GetCachedFile(getPhysicalPath(virtualPath));
            //key
            string dataKey = "layers_" + renderer.ToString();

            //Try getting from the cache first
            IList<IPsdLayer> layers = file.getSubkey(dataKey) as IList<IPsdLayer>;
            if (layers == null)
            {
                //Time just the parsing
                Stopwatch swRender = new Stopwatch();
                swRender.Start();

                layers = renderer.GetLayers(file.GetStream());
                //Save to cache for later
                file.setSubkey(dataKey, layers);

                //How fast?
                swRender.Stop();
                trace("Using decoder " + renderer.ToString() + ",parsing file and enumerating layers took " + swRender.ElapsedMilliseconds.ToString() + "ms");
            }


            sw.Stop();
            trace("Total time for enumerating, including file reading: " + sw.ElapsedMilliseconds.ToString() + "ms");
            return layers;
        }
        public static IList<IPsdLayer> getVisibleTextLayers(string virtualPath, NameValueCollection queryString)
        {
            
            //Get all layers
            IList<IPsdLayer> layers = getAllLayers(virtualPath, queryString);


            Stopwatch sw = new Stopwatch();
            sw.Start();
            //Now, time to filter layers to those that would be showing on the image right now.
            IList<IPsdLayer> filtered = new List<IPsdLayer>();
            
            //Generate a callback just like the one used in the renderer for filtering
            ShowLayerDelegate callback = BuildLayerCallback(queryString);

            for (int i = 0; i < layers.Count; i++){
                if (layers[i].IsTextLayer && callback(layers[i].Index, layers[i].Name, layers[i].Visible))
                {
                    filtered.Add(layers[i]);
                }
            }
            
            sw.Stop();
            trace("Time for filtering layers: " + sw.ElapsedMilliseconds.ToString() + "ms");
            return filtered;

        }

        /// <summary>
        /// Returns DateTime.MinValue if there are no rows, or no values on the row.
        /// Executes _modifiedDateQuery, then returns the first non-null datetime value on the first row.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public DateTime getDateModifiedUtc(string virtualPath){
            return System.IO.File.GetLastWriteTimeUtc(getPhysicalPath(virtualPath));
        }


        /// <summary>
        ///   Determines whether a specified virtual path is within
        ///   the virtual file system.
        /// </summary>
        /// <param name="virtualPath">An absolute virtual path.</param>
        /// <returns>
        ///   true if the virtual path is within the 
        ///   virtual file sytem; otherwise, false.
        /// </returns>
        bool IsPathVirtual(string virtualPath)
        {
            return (System.IO.Path.GetFileName(virtualPath).ToLowerInvariant().Contains(".psd."));
        }

        static string  getPhysicalPath(string virtualPath)
        {
            int ix = virtualPath.ToLowerInvariant().LastIndexOf(".psd");
            string str = virtualPath.Substring(0, ix + 4);
            if (HttpContext.Current != null)
            {
                return HttpContext.Current.Request.MapPath(str);
            }
            else
            {
                return str.TrimStart('~','/').Replace('/','\\');
            }
        }


        public override bool FileExists(string virtualPath)
        {
            if (IsPathVirtual(virtualPath))
            {
                return System.IO.File.Exists(getPhysicalPath(virtualPath));
            }
            else
                return Previous.FileExists(virtualPath);
        }

        bool PSDExists(string virtualPath)
        {
            return IsPathVirtual(virtualPath) && System.IO.File.Exists(getPhysicalPath(virtualPath));
        }

        public override VirtualFile GetFile(string virtualPath)
        {
            if (PSDExists(virtualPath))
                return new PsdVirtualFile(virtualPath, this);
            else
                return Previous.GetFile(virtualPath);
        }

        public override CacheDependency GetCacheDependency(
          string virtualPath,
          System.Collections.IEnumerable virtualPathDependencies,
          DateTime utcStart)
        {
            //Maybe the database is also involved? 
            return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
        }
    }


    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class PsdVirtualFile : VirtualFile, IVirtualFileWithModifiedDate, IVirtualBitmapFile
    {
  
        private PsdProvider provider;

        private Nullable<bool> _exists = null;
        private Nullable<DateTime> _fileModifiedDate = null;

        /// <summary>
        /// Returns true if the row exists. 
        /// </summary>
        public bool Exists
        {
            get {
                if (_exists == null) _exists = provider.FileExists(this.VirtualPath);
                return _exists.Value;
            }
        }

        public PsdVirtualFile(string virtualPath, PsdProvider provider)
            : base(virtualPath)
        {
            this.provider = provider;
        }

        /// <summary>
        /// Returns a stream of the encoded file bitmap using the current request querystring.
        /// </summary>
        /// <returns></returns>
        public override Stream Open(){ return provider.getStream(this.VirtualPath);}
        /// <summary>
        /// Returns a composed bitmap of the file using request querystring paramaters.
        /// </summary>
        /// <returns></returns>
        public System.Drawing.Bitmap GetBitmap() { return provider.getBitmap(this.VirtualPath); }

        /// <summary>
        /// Returns the last modified date of the row. Cached for performance.
        /// </summary>
        public DateTime ModifiedDateUTC{
            get{
                if (_fileModifiedDate == null) _fileModifiedDate = provider.getDateModifiedUtc(this.VirtualPath);
                return _fileModifiedDate.Value;
            }
        }
      
    }
}