using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using HandyControl.Data;
using HandyControl.Tools.Interop;

namespace HandyControl.Tools
{
    public class KeyboardHook
    {
        public static event EventHandler<KeyboardHookEventArgs> KeyDown;

        public static event EventHandler<KeyboardHookEventArgs> KeyUp;

        private static IntPtr HookId = IntPtr.Zero;

        private static readonly InteropValues.HookProc Proc = HookCallback;

        private static int VirtualKey;

        private static readonly IntPtr KeyDownIntPtr = (IntPtr) InteropValues.WM_KEYDOWN;

        private static readonly IntPtr KeyUpIntPtr = (IntPtr) InteropValues.WM_KEYUP;

        private static readonly IntPtr SyskeyDownIntPtr = (IntPtr) InteropValues.WM_SYSKEYDOWN;

        private static readonly IntPtr SyskeyUpIntPtr = (IntPtr) InteropValues.WM_SYSKEYUP;

        private static int Count;

        public static void Start()
        {
            if (HookId == IntPtr.Zero)
            {
                HookId = SetHook(Proc);
            }

            if (HookId != IntPtr.Zero)
            {
                Count++;
            }
        }

        public static void Stop()
        {
            Count--;
            if (Count < 1)
            {
                InteropMethods.UnhookWindowsHookEx(HookId);
                HookId = IntPtr.Zero;
            }
        }

        private static IntPtr SetHook(InteropValues.HookProc proc)
        {
            using var curProcess = Process.GetCurrentProcess();
            using var curModule = curProcess.MainModule;

            if (curModule != null)
            {
                return InteropMethods.SetWindowsHookEx((int) InteropValues.HookType.WH_KEYBOARD_LL, proc,
                    InteropMethods.GetModuleHandle(curModule.ModuleName), 0);
            }
            return IntPtr.Zero;
        }

        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                if (wParam == KeyDownIntPtr)
                {
                    var virtualKey = Marshal.ReadInt32(lParam);
                    if (VirtualKey != virtualKey)
                    {
                        VirtualKey = virtualKey;
                        KeyDown?.Invoke(null, new KeyboardHookEventArgs(virtualKey, false));
                    }
                }
                else if (wParam == SyskeyDownIntPtr)
                {
                    var virtualKey = Marshal.ReadInt32(lParam);
                    if (VirtualKey != virtualKey)
                    {
                        VirtualKey = virtualKey;
                        KeyDown?.Invoke(null, new KeyboardHookEventArgs(virtualKey, true));
                    }
                }
                else if (wParam == KeyUpIntPtr)
                {
                    var virtualKey = Marshal.ReadInt32(lParam);
                    VirtualKey = -1;
                    KeyUp?.Invoke(null, new KeyboardHookEventArgs(virtualKey, false));
                }
                else if (wParam == SyskeyUpIntPtr)
                {
                    var virtualKey = Marshal.ReadInt32(lParam);
                    VirtualKey = -1;
                    KeyUp?.Invoke(null, new KeyboardHookEventArgs(virtualKey, true));
                }
            }
            return InteropMethods.CallNextHookEx(HookId, nCode, wParam, lParam);
        }
    }
}
