using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;

namespace MyScreenCap.Ctrl {

    /// <summary>
    /// Imageの保存
    /// </summary>
    public partial class SaveImage : UserControl {

        public enum SaveImageFormat {
            Bmp = 0,
            Jpg = 1,
            Gif = 2,
            Png = 3,
        }

        public enum SaveImageColor {
            Color2 = 2,
            Color16 = 16,
            Color256 = 256,
        }

        /// <summary>
        /// Format
        /// </summary>
        public SaveImageFormat ImgFormat {
            get {
                return (SaveImageFormat)int.Parse(this.cmbFormat.SelectedValue.ToString());
            }
            set {
                if (Enum.IsDefined(typeof(SaveImageFormat), value)) {
                    this.cmbFormat.SelectedValue = (int)value;
                }
            }
        }

        /// <summary>
        /// Quality
        /// </summary>
        [DefaultValue(80)]
        public uint ImgQuality {
            get {
                return this.txtQuality.Value;
            }
            set {
                this.txtQuality.Value = value;
            }
        }

        /// <summary>
        /// Gif Color Palette Index
        /// </summary>
        public SaveImageColor ImgColor {
            get {
                return (SaveImageColor)int.Parse(this.cmbColor.SelectedValue.ToString());
            }
            set {
                if (Enum.IsDefined(typeof(SaveImageColor), value)) {
                    this.cmbColor.SelectedValue = (int)value;
                }
            }
        }

        /// <summary>
        /// Gif Save Mode : RGB/GrayScale
        /// </summary>
        public bool GifSaveMode {
            get {
                return this.checkMode.Checked;
            }
            set {
                this.checkMode.Checked = value;
                if (value) {
                    this.checkMode.Text = "RGB";
                } else {
                    this.checkMode.Text = "GrayScale";
                }
            }
        }

        private string[] mimeType = {
                    "image/bmp",
                    "image/jpeg",
                    "image/gif",
                    "image/png",
        };

        private readonly object[][] IMAGE_FORMAT = new object[][]{
            new object[] { (int)SaveImageFormat.Bmp, ".bmp" },
            new object[] { (int)SaveImageFormat.Jpg, ".jpg" },
            new object[] { (int)SaveImageFormat.Gif, ".gif" },
            new object[] { (int)SaveImageFormat.Png, ".png" },
        };

        /// <summary>
        /// InputAreaのLeft Position
        /// </summary>
        public int LeftPosition {
            get {
                return this.cmbFormat.Left;
            }
            set {
                this.cmbFormat.Left = value;
                this.txtQuality.Left = value;
                this.cmbColor.Left = value;
                this.checkMode.Left = this.cmbColor.Right + 5;
                this.Width = this.checkMode.Right + 5;
            }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public SaveImage() {
            InitializeComponent();
            InitializeCmbFormat();
            InitializeCmbColor();
            this.checkMode.Checked = true;
            SetEnabled();
        }

        /// <summary>
        /// cmbFormat SelectedIndexChanged
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbFormat_SelectedIndexChanged(object sender, EventArgs e) {
            SetEnabled();
        }

        /// <summary>
        /// cmbFormatの初期化
        /// </summary>
        private void InitializeCmbFormat() {
            DataTable dt;

            this.cmbFormat.Items.Clear();

            dt = new DataTable();
            dt.Columns.Add("Value");
            dt.Columns.Add("Disp");
            foreach (object[] format in IMAGE_FORMAT) {
                dt.Rows.Add(format);
            }
            this.cmbFormat.DataSource = dt;
            this.cmbFormat.ValueMember = dt.Columns[0].ColumnName;
            this.cmbFormat.DisplayMember = dt.Columns[1].ColumnName;
            this.cmbFormat.DropDownStyle = ComboBoxStyle.DropDownList;
            this.cmbFormat.SelectedIndexChanged += new EventHandler(cmbFormat_SelectedIndexChanged);
        }

        /// <summary>
        /// cmbColorの初期化
        /// </summary>
        private void InitializeCmbColor() {
            DataTable dt;

            this.cmbColor.Items.Clear();

            dt = new DataTable();
            dt.Columns.Add("Value");
            dt.Columns.Add("Disp");
            foreach (int val in Enum.GetValues(typeof(SaveImageColor))) {
                dt.Rows.Add(new object[] { val, val });
            }
            this.cmbColor.DataSource = dt;
            this.cmbColor.ValueMember = dt.Columns[0].ColumnName;
            this.cmbColor.DisplayMember = dt.Columns[1].ColumnName;
            this.cmbColor.DropDownStyle = ComboBoxStyle.DropDownList;
            this.cmbColor.SelectedIndex = 2;
        }

        /// <summary>
        /// imageの保存
        /// </summary>
        /// <param name="dirPath"></param>
        /// <param name="img"></param>
        /// <returns></returns>
        public bool SaveImageToFile(string dirPath, string baseFileName, Image img) {
            try {
                string filePath = Path.Combine(dirPath, baseFileName + DateTime.Now.ToString("yyyyMMddHHmmssfff") + this.cmbFormat.Text);

                if ((SaveImageFormat)int.Parse(this.cmbFormat.SelectedValue.ToString()) == SaveImageFormat.Gif) {
                    // Gif
                    SaveGIFWithNewColorTable(img, filePath, uint.Parse(this.cmbColor.Text), this.checkMode.Checked);
                } else {
                    // Gif以外
                    EncoderParameters encParams = new EncoderParameters(1);
                    EncoderParameter encParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, int.Parse(this.txtQuality.Text));
                    encParams.Param[0] = encParam;
                    ImageCodecInfo imageCodecInfo = GetEncoderInfo(mimeType[int.Parse(this.cmbFormat.SelectedValue.ToString())]);

                    img.Save(filePath, imageCodecInfo, encParams);
                }
                return true;
            } catch {
                return false;
            }
        }

        /// <summary>
        /// MimeTypeで指定されたImageCodecInfoを探して返す
        /// </summary>
        /// <param name="mineType"></param>
        /// <returns></returns>
        private static ImageCodecInfo GetEncoderInfo(string mimeType) {
            //GDI+ に組み込まれたイメージ エンコーダに関する情報をすべて取得
            ImageCodecInfo[] encs = ImageCodecInfo.GetImageEncoders();
            //指定されたMimeTypeを探して見つかれば返す
            foreach (ImageCodecInfo enc in encs)
                if (enc.MimeType == mimeType)
                    return enc;
            return null;
        }

        /// <summary>
        /// ColorPaletteのインスタンス取得
        /// </summary>
        /// <param name="nColors">色数</param>
        /// <returns>ColorPalette</returns>
        protected ColorPalette GetColorPalette(uint nColors) {
            // Assume monochrome image.
            PixelFormat bitscolordepth = PixelFormat.Format1bppIndexed;
            ColorPalette palette;    // The Palette we are stealing
            Bitmap bitmap;     // The source of the stolen palette

            // Determine number of colors.
            if (nColors > 2)
                bitscolordepth = PixelFormat.Format4bppIndexed;
            if (nColors > 16)
                bitscolordepth = PixelFormat.Format8bppIndexed;

            // Make a new Bitmap object to get its Palette.
            bitmap = new Bitmap(1, 1, bitscolordepth);

            palette = bitmap.Palette;   // Grab the palette

            bitmap.Dispose();           // cleanup the source Bitmap

            return palette;             // Send the palette back
        }

        /// <summary>
        /// Gifの保存
        /// </summary>
        /// <param name="image">保存対象イメージ</param>
        /// <param name="filename">保存先</param>
        /// <param name="nColors">色数</param>
        /// <param name="isRGB">true:RGB,false:GrayScale</param>
        protected void SaveGIFWithNewColorTable(
                Image image,
                string filename,
                uint nColors,
                bool isRGB
                ) {
            Dictionary<Color, List<Point>> colorPointDic = null;

            // 不正値チェック
            if (nColors > 256) {
                nColors = 256;
            } else if (nColors < 2) {
                nColors = 2;
            }

            Bitmap bitmap = new Bitmap(
                image.Width,
                image.Height,
                PixelFormat.Format8bppIndexed
                );

            ColorPalette palette = GetColorPalette(nColors);

            // カラーパレットの作成

            if (isRGB) {
                // MedianCut
                colorPointDic = MedianCut.SetColorPalette(image, palette);
            } else {
                // GrayScale
                for (uint i = 0; i < nColors; i++) {
                    uint Intensity = i * 0xFF / (nColors - 1);

                    palette.Entries[i] = Color.FromArgb(
                        (int)Intensity,
                        (int)Intensity,
                        (int)Intensity
                        );
                }
            }

            // ColorPaletteを設定
            bitmap.Palette = palette;

            // imageをbitmapに複製
            Bitmap bmpCopy = new Bitmap(
                image.Width,
                image.Height,
                PixelFormat.Format32bppArgb);
            {
                Graphics g = Graphics.FromImage(bmpCopy);

                g.PageUnit = GraphicsUnit.Pixel;

                g.DrawImage(image, 0, 0, image.Width, image.Height);

                g.Dispose();
            }

            BitmapData bitmapData;
            Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);

            // bitmapをメモリにロック
            bitmapData = bitmap.LockBits(
                rect,
                ImageLockMode.WriteOnly,
                PixelFormat.Format8bppIndexed);

            IntPtr pixels = bitmapData.Scan0;

            unsafe {
                byte* pBits;
                if (bitmapData.Stride > 0)
                    pBits = (byte*)pixels.ToPointer();
                else
                    pBits = (byte*)pixels.ToPointer() + bitmapData.Stride * (Height - 1);
                uint stride = (uint)Math.Abs(bitmapData.Stride);


                if (isRGB) {
                    // 最も近い色のIndexをColorPaletteから取得
                    Dictionary<Color, int> colorIndexDic = new Dictionary<Color, int> { };
                    foreach (Color color in colorPointDic.Keys) {
                        int minLength = int.MaxValue;
                        int index = 0;
                        for (int i = 0; i < palette.Entries.Length; i++) {
                            int r = palette.Entries[i].R - color.R;
                            int g = palette.Entries[i].G - color.G;
                            int b = palette.Entries[i].B - color.B;
                            int length = r * r + g * g + b * b;
                            if (minLength > length) {
                                minLength = length;
                                index = i;
                            }
                        }
                        colorIndexDic.Add(color, index);
                    }

                    // PixcelにColorPaletteのIndexを設定
                    foreach (KeyValuePair<Color, int> colorIndex in colorIndexDic) {
                        Color color = colorIndex.Key;
                        byte index = (byte)colorIndex.Value;
                        foreach (Point point in colorPointDic[color]) {
                            byte* p8bppPixel = pBits + point.Y * stride + point.X;
                            *p8bppPixel = index;
                        }
                    }
                } else {
                    // GrayScaleを設定
                    for (int row = 0; row < image.Height; row++) {
                        for (int col = 0; col < image.Width; col++) {
                            Color pixel;

                            byte* p8bppPixel = pBits + row * stride + col;

                            pixel = bmpCopy.GetPixel(col, row);

                            double luminance = (pixel.R * 0.299) +
                                (pixel.G * 0.587) +
                                (pixel.B * 0.114);

                            *p8bppPixel = (byte)(luminance * (nColors - 1) / 255 + 0.5);

                        }
                    }
                }
            }

            // メモリロックの解除
            bitmap.UnlockBits(bitmapData);

            // gifの保存
            bitmap.Save(filename, ImageFormat.Gif);

            bmpCopy.Dispose();
            bitmap.Dispose();

        }

        /// <summary>
        /// ControlのEnabled制御
        /// </summary>
        private void SetEnabled() {
            switch ((SaveImageFormat)int.Parse(this.cmbFormat.SelectedValue.ToString())) {
                case SaveImageFormat.Bmp:
                    this.txtQuality.Enabled = false;
                    this.cmbColor.Enabled = false;
                    this.checkMode.Enabled = false;
                    break;
                case SaveImageFormat.Jpg:
                    this.txtQuality.Enabled = true;
                    this.cmbColor.Enabled = false;
                    this.checkMode.Enabled = false;
                    break;
                case SaveImageFormat.Gif:
                    this.txtQuality.Enabled = false;
                    this.cmbColor.Enabled = true;
                    this.checkMode.Enabled = true;
                    break;
                case SaveImageFormat.Png:
                    this.txtQuality.Enabled = false;
                    this.cmbColor.Enabled = false;
                    this.checkMode.Enabled = false;
                    break;
            }
        }
    }
}
