//! \file       Image.cs
//! \date       Tue Jul 01 11:29:52 2014
//! \brief      image class.
//
// Copyright (C) 2014 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//

using System;
using System.IO;
using System.Linq;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace GameRes
{
    public class ImageMetaData
    {
        public uint Width { get; set; }
        public uint Height { get; set; }
        public int OffsetX { get; set; }
        public int OffsetY { get; set; }
        public int BPP { get; set; }
        public string FileName { get; set; }
    }

    public class ImageEntry : Entry
    {
        public override string Type { get { return "image"; } }
    }

    /// <summary>
    /// Enumeration representing possible palette serialization formats.
    /// </summary>
    public enum PaletteFormat
    {
        Rgb     = 1,
        Bgr     = 2,
        RgbX    = 5,
        BgrX    = 6,
        RgbA    = 9,
        BgrA    = 10,
    }

    public class ImageData
    {
        private BitmapSource m_bitmap;

        public BitmapSource Bitmap { get { return m_bitmap; } }
        public uint Width { get { return (uint)m_bitmap.PixelWidth; } }
        public uint Height { get { return (uint)m_bitmap.PixelHeight; } }
        public int OffsetX { get; set; }
        public int OffsetY { get; set; }
        public int BPP { get { return m_bitmap.Format.BitsPerPixel; } }

        public static double DefaultDpiX { get; set; }
        public static double DefaultDpiY { get; set; }

        static ImageData ()
        {
            SetDefaultDpi (96, 96);
        }

        public static void SetDefaultDpi (double x, double y)
        {
            DefaultDpiX = x;
            DefaultDpiY = y;
        }

        public ImageData (BitmapSource data, ImageMetaData meta)
        {
            m_bitmap = data;
            OffsetX = meta.OffsetX;
            OffsetY = meta.OffsetY;
        }

        public ImageData (BitmapSource data, int x = 0, int y = 0)
        {
            m_bitmap = data;
            OffsetX = x;
            OffsetY = y;
        }

        public static ImageData Create (ImageMetaData info, PixelFormat format, BitmapPalette palette,
                                        byte[] pixel_data, int stride)
        {
            var bitmap = BitmapSource.Create ((int)info.Width, (int)info.Height, DefaultDpiX, DefaultDpiY,
                                              format, palette, pixel_data, stride);
            bitmap.Freeze();
            return new ImageData (bitmap, info);
        }

        public static ImageData Create (ImageMetaData info, PixelFormat format, BitmapPalette palette,
                                        byte[] pixel_data)
        {
            return Create (info, format, palette, pixel_data, (int)info.Width*((format.BitsPerPixel+7)/8));
        }

        public static ImageData CreateFlipped (ImageMetaData info, PixelFormat format, BitmapPalette palette,
                                        byte[] pixel_data, int stride)
        {
            var bitmap = BitmapSource.Create ((int)info.Width, (int)info.Height, DefaultDpiX, DefaultDpiY,
                                              format, palette, pixel_data, stride);
            var flipped = new TransformedBitmap (bitmap, new ScaleTransform { ScaleY = -1 });
            flipped.Freeze();
            return new ImageData (flipped, info);
        }
    }

    public abstract class ImageFormat : IResource
    {
        public override string Type { get { return "image"; } }

        public abstract ImageMetaData ReadMetaData (IBinaryStream file);

        public abstract ImageData Read (IBinaryStream file, ImageMetaData info);
        public abstract void Write (Stream file, ImageData bitmap);

        public static ImageData Read (IBinaryStream file)
        {
            var format = FindFormat (file);
            if (null == format)
                return null;
            file.Position = 0;
            return format.Item1.Read (file, format.Item2);
        }

        public static System.Tuple<ImageFormat, ImageMetaData> FindFormat (IBinaryStream file)
        {
            foreach (var impl in FormatCatalog.Instance.FindFormats<ImageFormat> (file.Name, file.Signature))
            {
                try
                {
                    file.Position = 0;
                    ImageMetaData metadata = impl.ReadMetaData (file);
                    if (null != metadata)
                    {
                        metadata.FileName = file.Name;
                        return Tuple.Create (impl, metadata);
                    }
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch { }
            }
            return null;
        }

        public bool IsBuiltin
        {
            get { return this.GetType().Assembly == typeof(ImageFormat).Assembly; }
        }

        public static ImageFormat FindByTag (string tag)
        {
            return FormatCatalog.Instance.ImageFormats.FirstOrDefault (x => x.Tag == tag);
        }

        static readonly Lazy<ImageFormat> s_JpegFormat = new Lazy<ImageFormat> (() => FindByTag ("JPEG"));
        static readonly Lazy<ImageFormat> s_PngFormat  = new Lazy<ImageFormat> (() => FindByTag ("PNG"));
        static readonly Lazy<ImageFormat> s_BmpFormat  = new Lazy<ImageFormat> (() => FindByTag ("BMP"));
        static readonly Lazy<ImageFormat> s_TgaFormat  = new Lazy<ImageFormat> (() => FindByTag ("TGA"));

        public static ImageFormat Jpeg { get { return s_JpegFormat.Value; } }
        public static ImageFormat  Png { get { return s_PngFormat.Value; } }
        public static ImageFormat  Bmp { get { return s_BmpFormat.Value; } }
        public static ImageFormat  Tga { get { return s_TgaFormat.Value; } }

        /// <summary>
        /// Desereialize color map from <paramref name="input"/> stream, consisting of specified number of
        /// <paramref name="colors"/> stored in specified <paramref name="format"/>.
        /// Default number of colors is 256 and format is 4-byte BGRX (where X is an unsignificant byte).
        /// </summary>
        public static Color[] ReadColorMap (Stream input, int colors = 0x100, PaletteFormat format = PaletteFormat.BgrX)
        {
            int bpp = PaletteFormat.Rgb == format || PaletteFormat.Bgr == format ? 3 : 4;
            var palette_data = new byte[bpp * colors];
            if (palette_data.Length != input.Read (palette_data, 0, palette_data.Length))
                throw new EndOfStreamException();
            int src = 0;
            var color_map = new Color[colors];
            Func<int, Color> get_color;
            if (PaletteFormat.Bgr == format || PaletteFormat.BgrX == format)
                get_color = x => Color.FromRgb (palette_data[x+2], palette_data[x+1], palette_data[x]);
            else if (PaletteFormat.BgrA == format)
                get_color = x => Color.FromArgb (palette_data[x+3], palette_data[x+2], palette_data[x+1], palette_data[x]);
            else if (PaletteFormat.RgbA == format)
                get_color = x => Color.FromArgb (palette_data[x+3], palette_data[x], palette_data[x+1], palette_data[x+2]);
            else
                get_color = x => Color.FromRgb (palette_data[x],   palette_data[x+1], palette_data[x+2]);

            for (int i = 0; i < colors; ++i)
            {
                color_map[i] = get_color (src);
                src += bpp;
            }
            return color_map;
        }

        public static BitmapPalette ReadPalette (Stream input, int colors = 0x100, PaletteFormat format = PaletteFormat.BgrX)
        {
            return new BitmapPalette (ReadColorMap (input, colors, format));
        }

        public static BitmapPalette ReadPalette (ArcView file, long offset, int colors = 0x100, PaletteFormat format = PaletteFormat.BgrX)
        {
            using (var input = file.CreateStream (offset, (uint)(4 * colors))) // largest possible size for palette
                return ReadPalette (input, colors, format);
        }
    }
}
