#define _WIN32_WINNT 0x500

//#include "NTKbdLites.h"

#include <windows.h>
#include <winioctl.h>

HHOOK g_hook;
HANDLE g_device;
bool g_ledOn = false;

#define IOCTL_KEYBOARD_SET_INDICATORS   CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0002, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_KEYBOARD_QUERY_INDICATORS CTL_CODE(FILE_DEVICE_KEYBOARD, 0x0010, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define KEYBOARD_CAPS_LOCK_ON   4
#define KEYBOARD_NUM_LOCK_ON    2
#define KEYBOARD_SCROLL_LOCK_ON 1

struct KEYBOARD_INDICATOR_PARAMETERS
{
    USHORT unitId;
    USHORT ledFlags;
};

void SetKeyboardLight(UINT ledFlag, bool on)
{
    KEYBOARD_INDICATOR_PARAMETERS inputBuffer;
    KEYBOARD_INDICATOR_PARAMETERS outputBuffer;
    const ULONG dataLength = sizeof(KEYBOARD_INDICATOR_PARAMETERS);
    ULONG returnedLength;

    inputBuffer.unitId = 0;
    outputBuffer.unitId = 0;

    if (!DeviceIoControl(g_device, IOCTL_KEYBOARD_QUERY_INDICATORS, &inputBuffer, dataLength, &outputBuffer, dataLength, &returnedLength, NULL))
    {
        return;
    }

    inputBuffer.ledFlags = ((outputBuffer.ledFlags & (~ledFlag)) | (ledFlag * (on ? 1 : 0)));
    DeviceIoControl(g_device, IOCTL_KEYBOARD_SET_INDICATORS, &inputBuffer, dataLength, NULL, 0, &returnedLength, NULL);
}

LRESULT CALLBACK KbdHook(int nCode,WPARAM wParam,LPARAM lParam)
{
    if (nCode < 0)
    {
        return CallNextHookEx(g_hook, nCode, wParam, lParam);
    }
    if (nCode == HC_ACTION)
    {
        KBDLLHOOKSTRUCT *pHookStruct = (KBDLLHOOKSTRUCT *)lParam;
        if (pHookStruct->vkCode == VK_CAPITAL)
        {
            if (wParam == WM_KEYDOWN)
            {
                if (GetAsyncKeyState(VK_LSHIFT) < 0)
                {
                    keybd_event(VK_CAPITAL, 0, KEYEVENTF_EXTENDEDKEY, 0);
                    keybd_event(VK_CAPITAL, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
                    SetKeyboardLight(KEYBOARD_CAPS_LOCK_ON, g_ledOn);
                }
                else
                {
                    HWND window = GetForegroundWindow();
                    if (window)
                    {
                        PostMessage(window, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)HKL_NEXT);
                    }
                    g_ledOn = !g_ledOn;
                    SetKeyboardLight(KEYBOARD_CAPS_LOCK_ON, g_ledOn);
                }
            }
            return 1;
        }
    }
    return CallNextHookEx(g_hook, nCode, wParam, lParam);
}

int main()
{
    if (!DefineDosDevice(DDD_RAW_TARGET_PATH, "Kbd", "\\Device\\KeyboardClass0"))
    {
        return 1;
    }

    g_device = CreateFile("\\\\.\\Kbd", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (g_device == INVALID_HANDLE_VALUE)
    {
        return 2;
    }

    SetKeyboardLight(KEYBOARD_CAPS_LOCK_ON, false);

    g_hook = SetWindowsHookEx(WH_KEYBOARD_LL, KbdHook, GetModuleHandle(0), 0);
    if (!g_hook)
    {
        return 3;
    }

    MSG msg;
    while (GetMessage(&msg, 0, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
  
    UnhookWindowsHookEx(g_hook);
    DefineDosDevice(DDD_REMOVE_DEFINITION, "Kbd", NULL);
    CloseHandle(g_device);
    return 0;
}
