using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;

namespace Mix.Tool
{
    /// <summary>
    /// 数字ボックス
    /// </summary>
    public class NumericBox : TextBox
    {
        #region DllImport

        // IMEハンドルの取得
        [DllImport("Imm32.dll")]
        private static extern int ImmGetContext(int hWnd);

        // IMEハンドルの解放
        [DllImport("Imm32.dll")]
        private static extern int ImmReleaseContext(int hWnd, int hIMC);

        // IMEの文字を読み取る
        [DllImport("Imm32.dll")]
        private static extern int ImmGetCompositionString(int hIMC, int dwIndex, StringBuilder lpBuf, int dwBufLen);

        // IMEへの通知
        [DllImport("Imm32.dll")]
        private static extern int ImmNotifyIME(int hIMC, int dwAction, int dwIndex, int dwValue);

        #endregion

        #region Private constant

        //メッセージ : ペースト
        private const int WM_PASTE = 0x0302;

        //メッセージ : IMEの制御
        private const int WM_IME_COMPOSITION = 0x010F;

        //IME入力の取得
        private const int GCS_COMPSTR = 0x0008;

        //IME入力のキャンセル用
        private const int NI_COMPOSITIONSTR = 0x0015;
        private const int CPS_CANCEL = 0x0004;

        #endregion

        /// <summary>
        /// 入力タイプ列挙定数
        /// </summary>
        public enum InputTypes
        {
            /// <summary>
            /// 整数
            /// </summary>
            Integer,

            /// <summary>
            /// 小数
            /// </summary>
            Float,
        };

        private InputTypes inputType = InputTypes.Integer;

        /// <summary>
        /// 入力タイプの取得、または設定
        /// </summary>
        [Category("動作")]
        [Description("入力タイプを設定します。")]
        public InputTypes InputType
        {
            get { return inputType; }
            set { inputType = value; }
        }

        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);

            int start = this.SelectionStart;
            int length = this.SelectionLength;

            this.Text = ToHalf(this.Text);

            this.SelectionStart = start;
            this.SelectionLength = length;
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            base.OnKeyPress(e);

            if (char.IsDigit(e.KeyChar) == true)
            {
                //数字
                this.InsertString(ToHalf(e.KeyChar.ToString()));

                e.Handled = true;
            }
            else if (e.KeyChar == '\b')
            {
                //バックスペース
            }
            else if (e.KeyChar == 22)
            {
                //CTRL + V による貼り付けの可能性があるので WM_PASTE で処理する
            }
            else if (e.KeyChar == ' ')
            {
                //スペース
                e.Handled = true;
            }
            else
            {
                string halfChar = ToHalf(e.KeyChar.ToString());

                if (this.CheckInsertString(halfChar) == true)
                {
                    this.InsertString(halfChar);
                }

                e.Handled = true;
            }
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);

            if (this.Text.Length > 0)
            {
                if (this.inputType == InputTypes.Integer)
                {
                    int temp;

                    if (int.TryParse(this.Text, out temp) == true)
                    {
                        this.Text = temp.ToString();
                    }
                    else
                    {
                        this.Text = "0";
                    }
                }
                else if (this.inputType == InputTypes.Float)
                {
                    float temp;

                    if (float.TryParse(this.Text, out temp) == true)
                    {
                        this.Text = temp.ToString();
                    }
                    else
                    {
                        this.Text = "0.0";
                    }
                }
            }
            else
            {
                switch (this.inputType)
                {
                    case InputTypes.Integer:
                        this.Text = "0";
                        break;
                    case InputTypes.Float:
                        this.Text = "0.0";
                        break;
                }
            }
        }

        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_PASTE:
                    if (this.CheckPaste() == false)
                    {
                        return;
                    }
                    break;

                case WM_IME_COMPOSITION:
                    this.CheckIME((uint)m.LParam);
                    break;
            }

            base.WndProc(ref m);
        }

        private void CheckIME(uint gcs)
        {
            if ((gcs & GCS_COMPSTR) == GCS_COMPSTR)
            {
                int hIMC = ImmGetContext(this.Handle.ToInt32());
                int byteSize = ImmGetCompositionString(hIMC, GCS_COMPSTR, null, 0);
                int length = byteSize / 2;

                if (length > 0)
                {
                    StringBuilder temp = new StringBuilder(length);
                    string imeStr;

                    ImmGetCompositionString(hIMC, GCS_COMPSTR, temp, byteSize);

                    imeStr = temp.ToString();
                    if (imeStr.Length > length)
                    {
                        imeStr = imeStr.Substring(0, length);
                    }

                    if (CheckInsertString(ToHalf(imeStr)) == false)
                    {
                        ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
                    }
                }

                ImmReleaseContext(this.Handle.ToInt32(), hIMC);
            }
        }

        private bool CheckPaste()
        {
            IDataObject dataObj = Clipboard.GetDataObject();
            object obj = dataObj.GetData(DataFormats.Text);

            if (obj == null)
            {
                return false;
            }

            return this.CheckInsertString(ToHalf((string)obj));
        }

        private bool CheckInsertString(string str)
        {
            int curStart = this.SelectionStart;
            int curLength = this.SelectionLength;

            string nextText = this.Text;

            if (nextText.Length == curLength)
            {
                nextText = str;
            }
            else
            {
                nextText = nextText.Remove(curStart, curLength);
                nextText = nextText.Insert(curStart, str);
            }

            if (this.inputType == InputTypes.Integer)
            {
                int temp;

                if (int.TryParse(nextText, out temp) == false)
                {
                    return false;
                }
            }
            else if (this.inputType == InputTypes.Float)
            {
                float temp;

                if (float.TryParse(nextText, out temp) == false)
                {
                    return false;
                }
            }

            return true;
        }

        private void InsertString(string str)
        {
            int start = this.SelectionStart;
            int length = this.SelectionLength;

            string temp = this.Text;

            temp = this.Text.Remove(start, length);
            this.Text = temp.Insert(start, str);

            this.SelectionStart = start + 1;
            this.SelectionLength = 0;
        }

        private static string ToHalf(string text)
        {
            return Microsoft.VisualBasic.Strings.StrConv(text, Microsoft.VisualBasic.VbStrConv.Narrow, 0x411);
        }
    }
}
