using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

using System.Diagnostics;
using System.Runtime.InteropServices;

using System.ComponentModel;
using System.Reflection;

namespace KeyHookLib
{
    #region Hook code defination
    /// <summary>
    /// フックタイプ
    /// </summary>
    public class HookType
    {
        public const int WH_MSGFILTER = (-1);
        public const int WH_JOURNALRECORD = 0;
        public const int WH_JOURNALPLAYBACK = 1;
        public const int WH_KEYBOARD = 2;
        public const int WH_GETMESSAGE = 3;
        public const int WH_CALLWNDPROC = 4;
        public const int WH_CBT = 5;
        public const int WH_SYSMSGFILTER = 6;
        public const int WH_MOUSE = 7;
        public const int WH_HARDWARE = 8;
        // defined(_WIN32_WINDOWS)
        public const int WH_DEBUG = 9;
        public const int WH_SHELL = 10;
        public const int WH_FOREGROUNDIDLE = 11;
        public const int WH_CALLWNDPROCRET = 12;
        // (WINVER >= 0x0400)
        public const int WH_KEYBOARD_LL = 13;
        // (_WIN32_WINNT >= 0x0400)
        public const int WH_MOUSE_LL = 14;
        // (_WIN32_WINNT >= 0x0400)
    }

    /// <summary>
    /// フックコード
    /// </summary>
    public class HookCodes
    {
        public const int HC_ACTION = 0;
        public const int HC_GETNEXT = 1;
        public const int HC_SKIP = 2;
        public const int HC_NOREMOVE = 3;
        public const int HC_NOREM = HC_NOREMOVE;
        public const int HC_SYSMODALON = 4;
        public const int HC_SYSMODALOFF = 5;
    }

    #endregion

    /// <summary>
    /// キーボードフックのステータス
    /// </summary>
    internal struct KeyboardHookStruct
    {
        public Keys vkey;
        public int scanCode;
        public KeyboardStateFlag flags;
        public int time;
        public int dwExtraInfo;
    }

    /// <summary>
    /// キーボードのウィンドウメッセージ
    /// </summary>
    internal enum HookMessage
    {
        None            = -1,
        CopyData		= 0x04A,
        MouseMove		= 0x200,
        LButtonDown		= 0x201,
        RButtonDown		= 0x204,
        MButtonDown		= 0x207,
        LButtonUp		= 0x202,
        RButtonUp		= 0x205,
        MButtonUp		= 0x208,
        LButtonDblClick	= 0x203,
        RButtonDblClick	= 0x206,
        MButtonDblClick	= 0x209,
        MouseWheel		= 0x20A,
        KeyDown			= 0x100,
        KeyUp			= 0x101,
        SysKeyDown		= 0x104,
        SysKeyUp		= 0x105,
    }

    /// <summary>
    /// キーボード情報の付加情報する
    /// </summary>
    internal struct KeyboardStateFlag
    {
        private int Flags;

        /// <summary>
        /// フラグの値
        /// </summary>
        internal enum FlagValue : int
        {
            Extended            = 0x01,
            Injected            = 0x10,
            AltDown             = 0x20,
            IsUp                = 0x80,
        }

        /// <summary>
        ///  フラグのチェック
        /// </summary>
        /// <param name="value">チェック値</param>
        /// <returns>フラグが立っている場合にtrue</returns>
        private bool IsFlagging(FlagValue value)
        {
            return (Flags & (int)value) != 0;
        }
        /// <summary>
        /// フラグの操作
        /// </summary>
        /// <param name="value">立てる場合true、落とす場合false</param>
        /// <param name="digit">立てる値</param>
        private void Flag(bool value, FlagValue digit)
        {
            Flags = value ? (Flags | (int)digit) : (Flags & ~(int)digit);
        }

        /// <summary>
        /// 拡張キーが押された場合
        /// </summary>
        public bool IsExtended
        {
            get{
                return IsFlagging(FlagValue.Extended);
            }
            set
            {
                Flag(value, FlagValue.Extended);
            }
        }

        ///<summary>イベントがインジェクトされたかどうかを表す。</summary>
        public bool IsInjected
        {
            get{
                return IsFlagging(FlagValue.Injected);
            }
            set
            {
                Flag(value, FlagValue.Injected);
            }
        }

        ///<summary>ALTキーが押されているかどうかを表す。</summary>
        public bool AltDown
        {
            get
            {
                return IsFlagging(FlagValue.AltDown);
            }
            set
            {
                Flag(value, FlagValue.AltDown);
            }
        }

        ///<summary>キーが放されたどうかを表す。</summary>
        public bool IsUp
        {
            get{
                return IsFlagging(FlagValue.IsUp);
            }
            set
            {
                Flag(value, FlagValue.IsUp);
            }
        }
    }

    /// <summary>
    /// キーボードフックのイベントハンドラ
    /// </summary>
    /// <param name="sender">イベント元</param>
    /// <param name="e">パラメータ</param>
    internal delegate void KeyboardHookEventHandler(object sender, KeyboardHookEventArgs e);

    /// <summary>
    /// キーボードイベントのパラメータ
    /// </summary>
    public class KeyboardHookEventArgs
        : EventArgs
    {
        /// <summary>
        /// ステータスの格納
        /// </summary>
        private HookMessage Message;
        /// <summary>
        /// ステータス
        /// </summary>
        private KeyboardHookStruct State;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="message"></param>
        /// <param name="state"></param>
        internal KeyboardHookEventArgs(HookMessage message, ref KeyboardHookStruct state)
        {
            this.Message = message;
            this.State = state;
        }

        /// <summary>
        /// キーダウン時の判定
        /// </summary>
        public bool KeyDown
        {
            get
            {
                return (Message == HookMessage.KeyDown || Message == HookMessage.SysKeyDown);
            }
        }

        /// <summary>
        /// キーアップ時の判定
        /// </summary>
        public bool KeyUp
        {
            get
            {
                return (Message == HookMessage.KeyUp || Message == HookMessage.SysKeyUp);
            }
        }

        /// <summary>
        /// キーコードの取得
        /// </summary>
        public Keys getKey
        {
            get
            {
                return this.State.vkey;
            }
        }

        /// <summary>
        /// Altキーが押されている場合true
        /// </summary>
        public bool IsAltDown
        {
            get
            {
                return this.State.flags.AltDown;
            }
        }
    }

    internal class KeyboardHook
    {
        #region Function imports
        //This is the Import for the SetWindowsHookEx function.
        //Use this function to install a thread-specific hook.
        [DllImport("user32.dll", CharSet = CharSet.Auto,
         CallingConvention = CallingConvention.StdCall)]
        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn,
        IntPtr hInstance, int threadId);

        //This is the Import for the UnhookWindowsHookEx function.
        //Call this function to uninstall the hook.
        [DllImport("user32.dll", CharSet = CharSet.Auto,
         CallingConvention = CallingConvention.StdCall)]
        private static extern bool UnhookWindowsHookEx(int idHook);

        //This is the Import for the CallNextHookEx function.
        //Use this function to pass the hook information to the next hook procedure in chain.
        [DllImport("user32.dll", CharSet = CharSet.Auto,
         CallingConvention = CallingConvention.StdCall)]
        private static extern int CallNextHookEx(int idHook, int nCode,
        HookMessage message, ref KeyboardHookStruct khs);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
        #endregion

        /// <summary>
        /// フック制御のためのコールバック
        /// </summary>
        private delegate int HookProc(int nCode, HookMessage message, ref KeyboardHookStruct khs);

        /// <summary>
        /// フック制御のハンドル
        /// </summary>
        private int hKeyboardHook = 0;

        /// <summary>
        /// キーボードフックを行うためのコールバック
        /// </summary>
        private HookProc KeyboardHookProcedure;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="staet">フック処理を開始する場合true</param>
        /// <param name="handler">イベントハンドラ</param>
        public KeyboardHook(bool staet, KeyboardHookEventHandler handler)
        {
            // イベントハンドラが指定されていたら追加
            if (null != handler)
            {
                this.KeyboardHooked += handler;
            }

            // フック処理を追加
            this.KeyboardHookProcedure = new HookProc(KeyboardHookProc);

            Hook(true);

            // フック制御を設定
            this.Enabled = staet;
        }

        /// <summary>
        /// キーボードが操作されたときに発生させるイベント
        /// </summary>
        public event KeyboardHookEventHandler KeyboardHooked = null;

        /// <summary>
        /// フック状態
        /// </summary>
        private bool Enabled_;
        /// <summary>
        /// フック状態の変更と取得
        /// </summary>
        public bool Enabled
        {
            set
            {
                Enabled_ = value;
            }
            get
            {
                return Enabled_;
            }
        }

        /// <summary>
        /// キーフックの状態切り替え
        /// </summary>
        /// <param name="enable">true:有効 / false:無効</param>
        public void Hook(bool enable)
        {
            if (enable)
            {
                hKeyboardHook =
                    SetWindowsHookEx(
                        (int)HookType.WH_KEYBOARD_LL,
                        KeyboardHookProcedure,
                        GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
                        0
                        );

                if (hKeyboardHook == 0)
                {
                    throw new Exception();
                }
            }
            else
            {
                bool ret = UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
            }
        }

        /// <summary>
        /// キーボードフック処理
        /// </summary>
        private int KeyboardHookProc(int code, HookMessage message, ref KeyboardHookStruct khs)
        {
            if (code >= 0)
            {
                if (Enabled_ && KeyboardHooked != null)
                    KeyboardHooked.BeginInvoke(this, new KeyboardHookEventArgs(message, ref khs),
                        new AsyncCallback(HandlerCallback), KeyboardHooked);
            }
            return CallNextHookEx(hKeyboardHook, code, message, ref khs);
        }

        private void HandlerCallback(IAsyncResult ar)
        {
            KeyboardHookEventHandler caller = (KeyboardHookEventHandler)ar.AsyncState;
            caller.EndInvoke(ar);
        }


        ///<summary>ファイナライザ。</summary>
        ~KeyboardHook()
        {
            Enabled = false;
            Hook(false);
        }
    }

    /// <summary>
    /// ショートカットキー検出のハンドラ
    /// </summary>
    public delegate void ShortcutkeyPlessedHandler();

    public class ShortcutKey
    {        
        public event ShortcutkeyPlessedHandler ShortcutkeyPlessed = null;

        static KeyboardHook KeyboardHook_ = null;

        // コンストラクタ
        public ShortcutKey(ShortcutkeyPlessedHandler handler)
        {
            if (handler != null)
            {
                this.ShortcutkeyPlessed += handler;
            }

            // キーボードフックのイベントハンドラを登録
            KeyboardHook_ = new KeyboardHook(true, new KeyboardHookEventHandler(KeyboardHookProc));
        }

        /// <summary>
        /// フック制御の有効無効切り替え
        /// </summary>
        public bool HookEnabled
        {
            set { KeyboardHook_.Enabled = value; }
            get { return KeyboardHook_.Enabled; }
        }

        public bool KeyShift = false;
        public bool KeyCtrl = false;
        public bool KeyAlt = false;
        public Keys Key = Keys.None;


        private bool IsRCtrl = false;
        private bool IsLCtrl = false;
        private bool IsRMenu = false;
        private bool IsLMenu = false;
        private bool IsRShift = false;
        private bool IsLShift = false;



        /// <summary>
        /// キーボードフックハンドラ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void KeyboardHookProc(object sender, KeyboardHookEventArgs e)
        {
            if (e.KeyUp)
            {
                switch (e.getKey)
                {
                    case Keys.RShiftKey:
                        this.IsRShift = false;
                        break;
                    case Keys.LShiftKey:
                        this.IsLShift = false;
                        break;
                    case Keys.RControlKey:
                        this.IsRCtrl = false;
                        break;
                    case Keys.LControlKey:
                        this.IsLCtrl = false;
                        break;
                    case Keys.RMenu:
                        this.IsRMenu = false;
                        break;
                    case Keys.LMenu:
                        this.IsLMenu = false;
                        break;
                    default:
                        break;
                }
            }
            if (e.KeyDown)
            {
                switch (e.getKey)
                {
                    case Keys.RShiftKey:
                        this.IsRShift = true;
                        break;
                    case Keys.LShiftKey:
                        this.IsLShift = true;
                        break;
                    case Keys.RControlKey:
                        this.IsRCtrl = true;
                        break;
                    case Keys.LControlKey:
                        this.IsLCtrl = true;
                        break;
                    case Keys.RMenu:
                        this.IsRMenu = true;
                        break;
                    case Keys.LMenu:
                        this.IsLMenu = true;
                        break;
                    default:
                        break;
                }


                if ((!this.KeyAlt || (this.IsRMenu | this.IsLMenu)) &&
                    (!this.KeyCtrl || (this.IsRCtrl | this.IsLCtrl)) &&
                    (!this.KeyShift || (this.IsRShift | this.IsLShift)))
                {
                    if(this.Key == e.getKey)
                    {
                        if (this.ShortcutkeyPlessed != null)
                        {
                            Thread th = new Thread(new ThreadStart(EventCaller));
                            th.SetApartmentState(ApartmentState.STA);
                            th.Start();
                            th.Join();
                        }
                    }
                }
            }
        }

        private void EventCaller()
        {
            this.ShortcutkeyPlessed();
        }
    }
}
