using System.IO;
using System.Drawing;
using Microsoft.Xna.Framework.Graphics;

namespace nft.xna
{
    using SysColor = System.Drawing.Color;
    using XnaColor = Microsoft.Xna.Framework.Color;
    using nft.framework;

    /// <summary>
    /// Based on http://jakepoz.com/jake_poznanski__background_load_xna.html 
    /// </summary>
    public class TextureLoader
    {
        private static readonly BlendState BlendColorBlendState;
        private static readonly BlendState BlendAlphaBlendState;

        private readonly GraphicsDevice _graphicsDevice;
        private readonly SpriteBatch _spriteBatch;

        static TextureLoader()
        {
            BlendColorBlendState = new BlendState
            {
                ColorDestinationBlend = Blend.Zero,
                ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue,
                AlphaDestinationBlend = Blend.Zero,
                AlphaSourceBlend = Blend.SourceAlpha,
                ColorSourceBlend = Blend.SourceAlpha
            };

            BlendAlphaBlendState = new BlendState
            {
                ColorWriteChannels = ColorWriteChannels.Alpha,
                AlphaDestinationBlend = Blend.Zero,
                ColorDestinationBlend = Blend.Zero,
                AlphaSourceBlend = Blend.One,
                ColorSourceBlend = Blend.One
            };
        }

        public TextureLoader(GraphicsDevice graphicsDevice)
        {
            _graphicsDevice = graphicsDevice;
            _spriteBatch = new SpriteBatch(_graphicsDevice);
        }

        public Texture2D FromFile(string path, SysColor colorkey, string savefile)
        {
            using (Stream fileStream = File.OpenRead(path))
            {
                string ext = Path.GetExtension(path).ToLower();
                return FromStreamBmp(fileStream, colorkey, savefile);
            }
        }

        public Texture2D FromFile(string path)
        {
            using (Stream fileStream = File.OpenRead(path))
            {
                string ext = Path.GetExtension(path).ToLower();
                if ("gif".Equals(ext))
                {
                    return FromStreamGif(fileStream);
                }
                else
                {
                    return FromStreamPng(fileStream);
                }
            }
        }

        /// <summary>
        /// BMP should be converted to PNG.
        /// Use color key to make transparent.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="colorkey"></param>
        /// <returns></returns>
        public Texture2D FromStreamBmp(Stream stream, SysColor colorkey, string savefile)
        {
            Texture2D texture = ToPngAndTexture(stream, colorkey, savefile, _graphicsDevice);
            // BMP has no alpha.
            return texture;

        }

        /// <summary>
        /// Convert image to PNG, applying 'color key' to make transparent.
        /// </summary>
        /// <param name="orignalImg"></param>
        /// <param name="colorkey"></param>
        /// <param name="savefile"></param>
        public static void SaveAsPng(IFileSource orignalImg, SysColor colorkey, string savefile)
        {
            using (Stream fileStream = orignalImg.OpenRead())
            {
                ToPngAndTexture(fileStream, colorkey, savefile, null);
            }
        }

        /// <summary>
        /// Create texture making transpalent by color-key and converting to PNG.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="colorkey">if Transpalent is given, convert left-bottom pixel color into transpalent</param>
        /// <param name="savefile"></param>
        /// <param name="graphicsDevice"></param>
        /// <returns></returns>
        private static Texture2D ToPngAndTexture(Stream stream, SysColor colorkey, string savefile, GraphicsDevice graphicsDevice)
        {
            Texture2D texture = null;
            // Load image using GDI because Texture2D.FromStream doesn't support BMP
            using (Bitmap image = Bitmap.FromStream(stream) as Bitmap)
            {
                if (colorkey == SysColor.Transparent) {
                    colorkey = image.GetPixel(0, image.Height - 1);
                }
                image.MakeTransparent(colorkey);
                Stream sout;
                if (savefile != null)
                {
                    sout = new FileStream(savefile, FileMode.Create, FileAccess.ReadWrite);
                }
                else
                {
                    sout = new MemoryStream();
                }
                // Now create a MemoryStream which will be passed to Texture2D after converting to PNG internally
                using (sout)
                {
                    image.Save(sout, System.Drawing.Imaging.ImageFormat.Png);
                    if (graphicsDevice != null)
                    {
                        sout.Seek(0, SeekOrigin.Begin);
                        texture = Texture2D.FromStream(graphicsDevice, sout);
                    }
                }
            }
            // BMP has no alpha.
            return texture;
        }

        /// <summary>
        /// Load as it is. Background should be transparent.
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public Texture2D FromStreamGif(Stream stream)
        {
            // GIF has no alpha.
            return Texture2D.FromStream(_graphicsDevice, stream);
        }

        /// <summary>
        /// Load as it is. Background should be transparent.
        /// Converted to pre-multiplied alpha.
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public Texture2D FromStreamPng(Stream stream)
        {
            Texture2D texture;
            texture = Texture2D.FromStream(_graphicsDevice, stream);
            ProcessMultiplyAlpha(texture);
            return texture;

        }

        public void ProcessMultiplyAlpha(Texture2D texture){
            // Setup a render target to hold our final texture which will have premulitplied alpha values
            using (RenderTarget2D renderTarget = new RenderTarget2D(_graphicsDevice, texture.Width, texture.Height))
            {
                Viewport viewportBackup = _graphicsDevice.Viewport;
                _graphicsDevice.SetRenderTarget(renderTarget);
                _graphicsDevice.Clear(XnaColor.Black);

                // Multiply each color by the source alpha, and write in just the color values into the final texture
                _spriteBatch.Begin(SpriteSortMode.Immediate, BlendColorBlendState);
                _spriteBatch.Draw(texture, texture.Bounds, XnaColor.White);
                _spriteBatch.End();

                // Now copy over the alpha values from the source texture to the final one, without multiplying them
                _spriteBatch.Begin(SpriteSortMode.Immediate, BlendAlphaBlendState);
                _spriteBatch.Draw(texture, texture.Bounds, XnaColor.White);
                _spriteBatch.End();

                // Release the GPU back to drawing to the screen
                _graphicsDevice.SetRenderTarget(null);
                _graphicsDevice.Viewport = viewportBackup;

                // Store data from render target because the RenderTarget2D is volatile
                XnaColor[] data = new XnaColor[texture.Width * texture.Height];
                renderTarget.GetData(data);

                // Unset texture from graphic device and set modified data back to it
                _graphicsDevice.Textures[0] = null;
                texture.SetData(data);
            }
        }
    }
}
