using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection;

namespace Mix.Tool
{
    /// <summary>
    /// エディットリストボックス
    /// </summary>
    [ToolboxItem(true)]
    public class EditListBox : System.Windows.Forms.ListBox
    {
        /// <summary>
        /// アイテム描画イベントデータクラス
        /// </summary>
        public class ItemDrawingEventArgs : EventArgs
        {
            private int index = -1;
            private object value = null;
            private bool selected = false;
            private int imageIndex = -1;
            private string imageKey = "";

            internal void Reset(int _index, object _value, bool _selected)
            {
                this.index = _index;
                this.value = _value;
                this.selected = _selected;
                this.imageIndex = -1;
                this.imageKey = "";
            }

            /// <summary>
            /// 描画しようとしているアイテムのインデックスの取得
            /// </summary>
            public int Index
            {
                get { return this.index; }
            }

            /// <summary>
            /// 描画しようとしているアイテムの値の取得
            /// </summary>
            public object Value
            {
                get { return this.value; }
            }

            /// <summary>
            /// 選択されているかどうかの取得
            /// </summary>
            public bool Selected
            {
                get { return this.selected; }
            }

            /// <summary>
            /// 描画するイメージのインデックスの取得、または設定
            /// この値を設定すると直前まで設定されていたイメージのインデックスは無効になります
            /// </summary>
            public int ImageIndex
            {
                get { return this.imageIndex; }
                set
                {
                    this.imageIndex = value;
                    this.imageKey = "";
                }
            }

            /// <summary>
            /// 描画するイメージのキーの取得、または設定
            /// この値を設定すると直前まで設定されていたイメージのインデックスは無効になります
            /// </summary>
            public string ImageKey
            {
                get { return this.imageKey; }
                set
                {
                    this.imageKey = value;
                    this.imageIndex = -1;
                }
            }
        };

        /// <summary>
        /// アイテムエディットイベントデータクラス
        /// </summary>
        public class ItemEditEventArgs : EventArgs
        {
            private int index = 0;
            private string oldText = "";
            private string newText = "";

            internal ItemEditEventArgs(int _index, string _oldText, string _newText)
            {
                this.index = _index;
                this.oldText = _oldText;
                this.newText = _newText;
            }

            /// <summary>
            /// 編集したインデックスの取得
            /// </summary>
            public int Index
            {
                get { return this.index; }
            }

            /// <summary>
            /// 編集前のテキストの取得
            /// </summary>
            public string OldText
            {
                get { return this.oldText; }
            }

            /// <summary>
            /// 編集後のテキストの取得
            /// </summary>
            public string NewText
            {
                get { return this.newText; }
            }
        }

        /// <summary>
        /// アイテムを描画する直前に呼び出されるデリゲート
        /// </summary>
        /// <param name="e">イベント</param>
        public delegate void ItemDrawingEventHandler(ref ItemDrawingEventArgs e);
        /// <summary>
        /// アイテムを編集した際に呼び出されるデリゲート
        /// </summary>
        /// <param name="e"></param>
        public delegate void ItemEditEventHandler(ItemEditEventArgs e);

        private System.Windows.Forms.TextBox textBox = new TextBox();
        private System.Windows.Forms.ImageList imageList = null;
        private StringFormat strFormat = new StringFormat();
        private ItemDrawingEventArgs itemDrawingEventArgs = new ItemDrawingEventArgs();
        private Rectangle imageRect = new Rectangle(0, 0, 0, 0);
        private Rectangle textRect = new Rectangle(0, 0, 0, 0);

        [Category("イベント")]
        [Description("アイテムを描画する直前に発生します。")]
        public event ItemDrawingEventHandler ItemDrawing = null;

        [Category("イベント")]
        [Description("アイテムを編集したときに発生します。")]
        public event ItemEditEventHandler ItemEdit = null;

        [Category("表示")]
        [Description("アイテムの描画に使用するイメージリストを指定します。")]
        public System.Windows.Forms.ImageList ImageList
        {
            get { return this.imageList; }
            set { this.imageList = value; }
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override DrawMode DrawMode
        {
            get { return base.DrawMode; }
            set {}
        }

        /// <summary>
        /// アイテムの実際の値を取得します
        /// </summary>
        /// <param name="index">アイテムのインデックス</param>
        /// <returns>アイテムの実際の値</returns>
        public object GetItemValue(int index)
        {
            return this.GetItemValue(this.Items[index]);
        }

        /// <summary>
        /// アイテムの実際の値を取得します
        /// </summary>
        /// <param name="index">アイテムのインデックス</param>
        /// <returns>アイテムの実際の値</returns>
        public object GetItemValue(object item)
        {
            if (this.Items.Contains(item) == false)
            {
                throw new System.ArgumentException("引数として渡れたパラメータ item はコレクションに存在しません");
            }

            Type typeValue = item.GetType();
            PropertyInfo propInfo = typeValue.GetProperty(this.ValueMember);

            return (propInfo != null) ? propInfo.GetValue(item, null) : this.GetItemText(item);
        }

        protected override void OnCreateControl()
        {
            base.OnCreateControl();

            base.DrawMode = DrawMode.OwnerDrawFixed;

            this.strFormat.LineAlignment = StringAlignment.Center;

            this.textBox.MouseDown += new MouseEventHandler(this.textBox_MouseDown);
            this.textBox.KeyDown += new KeyEventHandler(textBox_KeyDown);
            this.textBox.BorderStyle = BorderStyle.None;
            this.textBox.Visible = false;
            this.Controls.Add(this.textBox);
        }

        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            base.OnDrawItem(e);

            if ((e.Index < 0) ||
                (this.Items.Count <= e.Index))
            {
                return;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////
            // 描画の問い合わせ
            ////////////////////////////////////////////////////////////////////////////////////////////////////

            bool selected = ((e.State & DrawItemState.Selected) == DrawItemState.Selected);

            if (this.ItemDrawing != null)
            {
                this.itemDrawingEventArgs.Reset(e.Index, this.GetItemValue(e.Index), selected);
                this.ItemDrawing.Invoke(ref this.itemDrawingEventArgs);
            }
            else
            {
                this.itemDrawingEventArgs.Reset(e.Index, null, selected);
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////
            // 背景を描画
            ////////////////////////////////////////////////////////////////////////////////////////////////////

            if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
            {
                if ((e.State & DrawItemState.Focus) == DrawItemState.Focus)
                {
                    e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds);
                }
                else
                {
                    e.Graphics.FillRectangle(SystemBrushes.Control, e.Bounds);
                }
            }
            else
            {
                using (Brush brush = new SolidBrush(e.BackColor))
                {
                    e.Graphics.FillRectangle(brush, e.Bounds);
                }
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////
            // イメージを描画
            ////////////////////////////////////////////////////////////////////////////////////////////////////

            Image image = null;

            if (this.imageList != null)
            {
                if ((this.itemDrawingEventArgs.ImageIndex >= 0) &&
                    (this.imageList.Images.Count >= this.itemDrawingEventArgs.ImageIndex))
                {
                    image = this.imageList.Images[this.itemDrawingEventArgs.ImageIndex];
                }
                else if ((this.itemDrawingEventArgs.ImageKey.Length > 0) &&
                    (this.imageList.Images.ContainsKey(this.itemDrawingEventArgs.ImageKey) == true))
                {
                    image = this.imageList.Images[this.itemDrawingEventArgs.ImageKey];
                }
            }

            if (image != null)
            {
                imageRect.X = e.Bounds.X;
                imageRect.Y = e.Bounds.Y + (e.Bounds.Height - image.Height) / 2;
                imageRect.Width = image.Width;
                imageRect.Height = image.Height;

                e.Graphics.DrawImage(image, imageRect);
            }
            else
            {
                imageRect.X = 0;
                imageRect.Y = 0;
                imageRect.Width = 0;
                imageRect.Height = 0;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////
            // テキストを描画
            ////////////////////////////////////////////////////////////////////////////////////////////////////

            textRect.X = imageRect.Right + 1;
            textRect.Y = e.Bounds.Y;
            textRect.Width = e.Bounds.Width - imageRect.Width - 1;
            textRect.Height = e.Bounds.Height;

            if ((e.State & DrawItemState.Focus) == DrawItemState.Focus)
            {
                using (Brush textBrush = new SolidBrush(e.ForeColor))
                {
                    e.Graphics.DrawString(this.GetItemText(this.Items[e.Index]), e.Font, textBrush, textRect, this.strFormat);
                }
            }
            else
            {
                e.Graphics.DrawString(this.GetItemText(this.Items[e.Index]), e.Font, SystemBrushes.GrayText, textRect, this.strFormat);
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            int index = this.SelectedIndex;

            if ((this.Items.Count > 0) &&
                (index >= 0) &&
                (e.KeyCode == Keys.F2))
            {
                this.BeginEdit(index);
            }
        }
/*
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x0202)
            {
                int index = this.SelectedIndex;
                if ((index < 0) ||
                    (this.Items.Count <= index))
                {
                    base.WndProc(ref m);
                }
                else
                {
                    Point pos = this.PointToClient(System.Windows.Forms.Cursor.Position);

                    if (index == this.IndexFromPoint(pos))
                    {
                        System.Diagnostics.Debug.WriteLine("WndProc : " + index);
                        this.BeginEdit(index);
                    }
                    else
                    {
                        base.WndProc(ref m);
                    }
                }
            }
            else
            {
                base.WndProc(ref m);
            }
        }
*/
        private void textBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                if (this.textBox.Text.Length > 0)
                {
                    this.EndEdit(true);
                }
            }
            else if (e.KeyCode == Keys.Escape)
            {
                this.EndEdit(false);
            }
        }

        private void textBox_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if ((this.SelectedIndex < 0) ||
                    (this.Items.Count <= this.SelectedIndex))
                {
                    this.EndEdit(false);
                }
                else
                {
                    Rectangle rect = this.GetItemRectangle(this.SelectedIndex);

                    if (rect.Contains(this.PointToClient(System.Windows.Forms.Cursor.Position)) == false)
                    {
                        this.EndEdit(false);
                    }
                }
            }
        }

        private void BeginEdit(int index)
        {
            Rectangle rect = this.GetItemRectangle(index);

            if ((0 > rect.Top) ||
                (this.Height <= rect.Bottom))
            {
                return;
            }

            this.textBox.Font = this.Font;
            this.textBox.Size = new Size(rect.Width - this.imageRect.Width - 6, rect.Height);
            this.textBox.Location = new Point(rect.X + this.imageRect.Width + 3, rect.Y + (rect.Height - this.textBox.Height) / 2);
            this.textBox.Text = this.GetItemText(this.Items[index]);
            this.textBox.Visible = true;
            this.textBox.SelectAll();
            this.textBox.Focus();
            this.textBox.Capture = true;
        }

        private void EndEdit(bool apply)
        {
            if (apply == true)
            {
                if ((this.SelectedIndex >= 0) &&
                    (this.Items.Count > this.SelectedIndex))
                {
                    string oldText = "";
                    string newText = "";

                    if (this.DataSource != null)
                    {
                        object item = this.Items[this.SelectedIndex];
                        Type typeValue = item.GetType();
                        PropertyInfo propInfo = typeValue.GetProperty(this.DisplayMember);

                        oldText = (string)propInfo.GetValue( item, null );
                        propInfo.SetValue(item, this.textBox.Text, null);
                        newText = (string)propInfo.GetValue(item, null);
                    }
                    else
                    {
                        oldText = (string)this.Items[this.SelectedIndex];
                        newText = this.textBox.Text;

                        this.Items[this.SelectedIndex] = newText;
                    }

                    if ((this.ItemEdit != null) &&
                        (oldText.Equals(newText) == false))
                    {
                        this.ItemEdit(new ItemEditEventArgs(this.SelectedIndex, oldText, newText));
                    }
                }
            }

            this.textBox.Capture = false;
            this.textBox.Visible = false;

            this.Focus();
        }
    }
}
