/* Copyright (C) 2006-2017 Alexander Barker.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
*/

#ifdef HAVE_CONFIG_H
	#include <config.h>
#endif

#include <inttypes.h>
#include <windows.h>

#include "../iohook.h"
#include "input.h"
// #include "logger.h"

// Thread and hook handles.
static DWORD hook_thread_id = 0;
static HHOOK keyboard_event_hhook = NULL, mouse_event_hhook = NULL;
static HWINEVENTHOOK win_event_hhook = NULL;

// The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH.
extern HINSTANCE hInst;

// Modifiers for tracking key masks.
static unsigned short int current_modifiers = 0x0000;

// Click count globals.
static unsigned short click_count = 0;
static DWORD click_time = 0;
static unsigned short int click_button = MOUSE_NOBUTTON;
static POINT last_click;

// Static event memory.
static iohook_event event;

// Event dispatch callback.
static dispatcher_t dispatcher = NULL;

IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) {
	logger(LOG_LEVEL_DEBUG,	"%s [%u]: Setting new dispatch callback to %#p.\n",
			__FUNCTION__, __LINE__, dispatch_proc);

	dispatcher = dispatch_proc;
}

// Send out an event if a dispatcher was set.
static inline void dispatch_event(iohook_event *const event) {
	if (dispatcher != NULL) {
		logger(LOG_LEVEL_DEBUG,	"%s [%u]: Dispatching event type %u.\n",
				__FUNCTION__, __LINE__, event->type);

		dispatcher(event);
	} else {
		logger(LOG_LEVEL_WARN,	"%s [%u]: No dispatch callback set!\n",
				__FUNCTION__, __LINE__);
	}
}


// Set the native modifier mask for future events.
static inline void set_modifier_mask(unsigned short int mask) {
	current_modifiers |= mask;
}

// Unset the native modifier mask for future events.
static inline void unset_modifier_mask(unsigned short int mask) {
	current_modifiers ^= mask;
}

// Get the current native modifier mask state.
static inline unsigned short int get_modifiers() {
	return current_modifiers;
}

// Initialize the modifier mask to the current modifiers.
static void initialize_modifiers() {
	current_modifiers = 0x0000;

	// NOTE We are checking the high order bit, so it will be < 0 for a singed short.
	if (GetKeyState(VK_LSHIFT)	 < 0)	{ set_modifier_mask(MASK_SHIFT_L);		}
	if (GetKeyState(VK_RSHIFT)   < 0)	{ set_modifier_mask(MASK_SHIFT_R);		}
	if (GetKeyState(VK_LCONTROL) < 0)	{ set_modifier_mask(MASK_CTRL_L);		}
	if (GetKeyState(VK_RCONTROL) < 0)	{ set_modifier_mask(MASK_CTRL_R);		}
	if (GetKeyState(VK_LMENU)    < 0)	{ set_modifier_mask(MASK_ALT_L);		}
	if (GetKeyState(VK_RMENU)    < 0)	{ set_modifier_mask(MASK_ALT_R);		}
	if (GetKeyState(VK_LWIN)     < 0)	{ set_modifier_mask(MASK_META_L);		}
	if (GetKeyState(VK_RWIN)     < 0)	{ set_modifier_mask(MASK_META_R);		}

	if (GetKeyState(VK_LBUTTON)	 < 0)	{ set_modifier_mask(MASK_BUTTON1);		}
	if (GetKeyState(VK_RBUTTON)  < 0)	{ set_modifier_mask(MASK_BUTTON2);		}
	if (GetKeyState(VK_MBUTTON)  < 0)	{ set_modifier_mask(MASK_BUTTON3);		}
	if (GetKeyState(VK_XBUTTON1) < 0)	{ set_modifier_mask(MASK_BUTTON4);		}
	if (GetKeyState(VK_XBUTTON2) < 0)	{ set_modifier_mask(MASK_BUTTON5);		}

	if (GetKeyState(VK_NUMLOCK)  < 0)	{ set_modifier_mask(MASK_NUM_LOCK);		}
	if (GetKeyState(VK_CAPITAL)  < 0)	{ set_modifier_mask(MASK_CAPS_LOCK);	}
	if (GetKeyState(VK_SCROLL)   < 0)	{ set_modifier_mask(MASK_SCROLL_LOCK);	}
}


/* Retrieves the mouse wheel scroll type. This function cannot be included as
 * part of the input.h due to platform specific calling restrictions.
 */
static unsigned short int get_scroll_wheel_type() {
	unsigned short int value;
	UINT wheel_type;

	SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_type, 0);
	if (wheel_type == WHEEL_PAGESCROLL) {
		value = WHEEL_BLOCK_SCROLL;
	} else {
		value = WHEEL_UNIT_SCROLL;
	}

	return value;
}

/* Retrieves the mouse wheel scroll amount. This function cannot be included as
 * part of the input.h due to platform specific calling restrictions.
 */
static unsigned short int get_scroll_wheel_amount() {
	unsigned short int value;
	UINT wheel_amount;

	SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_amount, 0);
	if (wheel_amount == WHEEL_PAGESCROLL) {
		value = 1;
	} else {
		value = (unsigned short int) wheel_amount;
	}

	return value;
}

void unregister_running_hooks() {
	// Stop the event hook and any timer still running.
	if (win_event_hhook != NULL) {
		UnhookWinEvent(win_event_hhook);
		win_event_hhook = NULL;
	}

	// Destroy the native hooks.
	if (keyboard_event_hhook != NULL) {
		UnhookWindowsHookEx(keyboard_event_hhook);
		keyboard_event_hhook = NULL;
	}

	if (mouse_event_hhook != NULL) {
		UnhookWindowsHookEx(mouse_event_hhook);
		mouse_event_hhook = NULL;
	}
}

void hook_start_proc() {
	// Get the local system time in UNIX epoch form.
	uint64_t timestamp = GetMessageTime();

	// Populate the hook start event.
	event.time = timestamp;
	event.reserved = 0x00;

	event.type = EVENT_HOOK_ENABLED;
	event.mask = 0x00;

	// Fire the hook start event.
	dispatch_event(&event);
}

void hook_stop_proc() {
	// Get the local system time in UNIX epoch form.
	uint64_t timestamp = GetMessageTime();

	// Populate the hook stop event.
	event.time = timestamp;
	event.reserved = 0x00;

	event.type = EVENT_HOOK_DISABLED;
	event.mask = 0x00;

	// Fire the hook stop event.
	dispatch_event(&event);
}

static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) {
	// Check and setup modifiers.
	if		(kbhook->vkCode == VK_LSHIFT)	{ set_modifier_mask(MASK_SHIFT_L);		}
	else if (kbhook->vkCode == VK_RSHIFT)	{ set_modifier_mask(MASK_SHIFT_R);		}
	else if (kbhook->vkCode == VK_LCONTROL)	{ set_modifier_mask(MASK_CTRL_L);		}
	else if (kbhook->vkCode == VK_RCONTROL)	{ set_modifier_mask(MASK_CTRL_R);		}
	else if (kbhook->vkCode == VK_LMENU)	{ set_modifier_mask(MASK_ALT_L);		}
	else if (kbhook->vkCode == VK_RMENU)	{ set_modifier_mask(MASK_ALT_R);		}
	else if (kbhook->vkCode == VK_LWIN)		{ set_modifier_mask(MASK_META_L);		}
	else if (kbhook->vkCode == VK_RWIN)		{ set_modifier_mask(MASK_META_R);		}
	else if (kbhook->vkCode == VK_NUMLOCK)	{ set_modifier_mask(MASK_NUM_LOCK);		}
	else if (kbhook->vkCode == VK_CAPITAL)	{ set_modifier_mask(MASK_CAPS_LOCK);	}
	else if (kbhook->vkCode == VK_SCROLL)	{ set_modifier_mask(MASK_SCROLL_LOCK);	}

	// Populate key pressed event.
	event.time = kbhook->time;
	event.reserved = 0x00;

	event.type = EVENT_KEY_PRESSED;
	event.mask = get_modifiers();

	event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags);
	event.data.keyboard.rawcode = kbhook->vkCode;
	event.data.keyboard.keychar = CHAR_UNDEFINED;

	logger(LOG_LEVEL_INFO,	"%s [%u]: Key %#X pressed. (%#X)\n",
			__FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);

	// Populate key pressed event.
	dispatch_event(&event);

	// If the pressed event was not consumed...
	if (event.reserved ^ 0x01) {
		// Buffer for unicode typed chars. No more than 2 needed.
		WCHAR buffer[2]; // = { WCH_NONE };

		// If the pressed event was not consumed and a unicode char exists...
		SIZE_T count = keycode_to_unicode(kbhook->vkCode, buffer, sizeof(buffer));
		unsigned int i;
		for (i = 0; i < count; i++) {
			// Populate key typed event.
			event.time = kbhook->time;
			event.reserved = 0x00;

			event.type = EVENT_KEY_TYPED;
			event.mask = get_modifiers();

			event.data.keyboard.keycode = VC_UNDEFINED;
			event.data.keyboard.rawcode = kbhook->vkCode;
			event.data.keyboard.keychar = buffer[i];

			logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X typed. (%lc)\n",
					__FUNCTION__, __LINE__, event.data.keyboard.keycode, (wint_t) event.data.keyboard.keychar);

			// Fire key typed event.
			dispatch_event(&event);
		}
	}
}

static void process_key_released(KBDLLHOOKSTRUCT *kbhook) {
	// Check and setup modifiers.
	if		(kbhook->vkCode == VK_LSHIFT)	{ unset_modifier_mask(MASK_SHIFT_L);		}
	else if (kbhook->vkCode == VK_RSHIFT)	{ unset_modifier_mask(MASK_SHIFT_R);		}
	else if (kbhook->vkCode == VK_LCONTROL)	{ unset_modifier_mask(MASK_CTRL_L);			}
	else if (kbhook->vkCode == VK_RCONTROL)	{ unset_modifier_mask(MASK_CTRL_R);			}
	else if (kbhook->vkCode == VK_LMENU)	{ unset_modifier_mask(MASK_ALT_L);			}
	else if (kbhook->vkCode == VK_RMENU)	{ unset_modifier_mask(MASK_ALT_R);			}
	else if (kbhook->vkCode == VK_LWIN)		{ unset_modifier_mask(MASK_META_L);			}
	else if (kbhook->vkCode == VK_RWIN)		{ unset_modifier_mask(MASK_META_R);			}
	else if (kbhook->vkCode == VK_NUMLOCK)	{ unset_modifier_mask(MASK_NUM_LOCK);		}
	else if (kbhook->vkCode == VK_CAPITAL)	{ unset_modifier_mask(MASK_CAPS_LOCK);		}
	else if (kbhook->vkCode == VK_SCROLL)	{ unset_modifier_mask(MASK_SCROLL_LOCK);	}

	// Populate key pressed event.
	event.time = kbhook->time;
	event.reserved = 0x00;

	event.type = EVENT_KEY_RELEASED;
	event.mask = get_modifiers();

	event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags);
	event.data.keyboard.rawcode = kbhook->vkCode;
	event.data.keyboard.keychar = CHAR_UNDEFINED;

	logger(LOG_LEVEL_INFO,	"%s [%u]: Key %#X released. (%#X)\n",
			__FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);

	// Fire key released event.
	dispatch_event(&event);
}

LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) {
	KBDLLHOOKSTRUCT *kbhook = (KBDLLHOOKSTRUCT *) lParam;
	switch (wParam) {
		case WM_KEYDOWN:
		case WM_SYSKEYDOWN:
			process_key_pressed(kbhook);
			break;

		case WM_KEYUP:
		case WM_SYSKEYUP:
			process_key_released(kbhook);
			break;

		default:
			// In theory this *should* never execute.
			logger(LOG_LEVEL_DEBUG,	"%s [%u]: Unhandled Windows keyboard event: %#X.\n",
					__FUNCTION__, __LINE__, (unsigned int) wParam);
			break;
	}

	LRESULT hook_result = -1;
	if (nCode < 0 || event.reserved ^ 0x01) {
		hook_result = CallNextHookEx(keyboard_event_hhook, nCode, wParam, lParam);
	} else {
		logger(LOG_LEVEL_DEBUG,	"%s [%u]: Consuming the current event. (%li)\n",
				__FUNCTION__, __LINE__, (long) hook_result);
	}

	return hook_result;
}


static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) {
	// uint64_t timestamp = GetMessageTime();
	uint64_t timestamp = mshook->time;

	// Track the number of clicks, the button must match the previous button.
	if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) {
		if (click_count < USHRT_MAX) {
			click_count++;
		} else {
			logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n",
					__FUNCTION__, __LINE__);
		}
	} else {
		// Reset the click count.
		click_count = 1;

		// Set the previous button.
		click_button = button;
	}

	// Save this events time to calculate the click_count.
	click_time = timestamp;

	// Store the last click point.
	last_click.x = mshook->pt.x;
	last_click.y = mshook->pt.y;

	// Populate mouse pressed event.
	event.time = timestamp;
	event.reserved = 0x00;

	event.type = EVENT_MOUSE_PRESSED;
	event.mask = get_modifiers();

	event.data.mouse.button = button;
	event.data.mouse.clicks = click_count;

	event.data.mouse.x = mshook->pt.x;
	event.data.mouse.y = mshook->pt.y;

	logger(LOG_LEVEL_INFO, "%s [%u]: Button %u  pressed %u time(s). (%u, %u)\n",
			__FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
			event.data.mouse.x, event.data.mouse.y);

	// Fire mouse pressed event.
	dispatch_event(&event);
}

static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) {
	// Populate mouse released event.
	event.time = mshook->time;
	event.reserved = 0x00;

	event.type = EVENT_MOUSE_RELEASED;
	event.mask = get_modifiers();

	event.data.mouse.button = button;
	event.data.mouse.clicks = click_count;

	event.data.mouse.x = mshook->pt.x;
	event.data.mouse.y = mshook->pt.y;

	logger(LOG_LEVEL_INFO, "%s [%u]: Button %u released %u time(s). (%u, %u)\n",
			__FUNCTION__, __LINE__, event.data.mouse.button,
			event.data.mouse.clicks,
			event.data.mouse.x, event.data.mouse.y);

	// Fire mouse released event.
	dispatch_event(&event);

	// If the pressed event was not consumed...
	if (event.reserved ^ 0x01 && last_click.x == mshook->pt.x && last_click.y == mshook->pt.y) {
		// Populate mouse clicked event.
		event.time = mshook->time;
		event.reserved = 0x00;

		event.type = EVENT_MOUSE_CLICKED;
		event.mask = get_modifiers();

		event.data.mouse.button = button;
		event.data.mouse.clicks = click_count;
		event.data.mouse.x = mshook->pt.x;
		event.data.mouse.y = mshook->pt.y;

		logger(LOG_LEVEL_INFO, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n",
				__FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
				event.data.mouse.x, event.data.mouse.y);

		// Fire mouse clicked event.
		dispatch_event(&event);
	}

	// Reset the number of clicks.
	if (button == click_button && (long int) (event.time - click_time) > hook_get_multi_click_time()) {
		// Reset the click count.
		click_count = 0;
	}
}

static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) {
	uint64_t timestamp = mshook->time;

	// We received a mouse move event with the mouse actually moving.
	// This verifies that the mouse was moved after being depressed.
	if (last_click.x != mshook->pt.x || last_click.y != mshook->pt.y) {
		// Reset the click count.
		if (click_count != 0 && (long) (timestamp - click_time) > hook_get_multi_click_time()) {
			click_count = 0;
		}

		// Populate mouse move event.
		event.time = timestamp;
		event.reserved = 0x00;

		event.mask = get_modifiers();

		// Check the modifier mask range for MASK_BUTTON1 - 5.
		bool mouse_dragged = event.mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5);
		if (mouse_dragged) {
			// Create Mouse Dragged event.
			event.type = EVENT_MOUSE_DRAGGED;
		} else {
			// Create a Mouse Moved event.
			event.type = EVENT_MOUSE_MOVED;
		}

		event.data.mouse.button = MOUSE_NOBUTTON;
		event.data.mouse.clicks = click_count;
		event.data.mouse.x = mshook->pt.x;
		event.data.mouse.y = mshook->pt.y;

		logger(LOG_LEVEL_INFO, "%s [%u]: Mouse %s to %u, %u.\n",
				__FUNCTION__, __LINE__,  mouse_dragged ? "dragged" : "moved",
				event.data.mouse.x, event.data.mouse.y);

		// Fire mouse move event.
		dispatch_event(&event);
	}
}

static void process_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) {
	// Track the number of clicks.
	// Reset the click count and previous button.
	click_count = 1;
	click_button = MOUSE_NOBUTTON;

	// Populate mouse wheel event.
	event.time = mshook->time;
	event.reserved = 0x00;

	event.type = EVENT_MOUSE_WHEEL;
	event.mask = get_modifiers();

	event.data.wheel.clicks = click_count;
	event.data.wheel.x = mshook->pt.x;
	event.data.wheel.y = mshook->pt.y;

	event.data.wheel.type = get_scroll_wheel_type();
	event.data.wheel.amount = get_scroll_wheel_amount();

	/* Delta HIWORD(mshook->mouseData)
	 * A positive value indicates that the wheel was rotated
	 * forward, away from the user; a negative value indicates that
	 * the wheel was rotated backward, toward the user. One wheel
	 * click is defined as WHEEL_DELTA, which is 120. */
	event.data.wheel.rotation = ((int16_t) HIWORD(mshook->mouseData) / WHEEL_DELTA) * -1;

	// Set the direction based on what event was received.
	event.data.wheel.direction = direction;

	logger(LOG_LEVEL_INFO, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n",
			__FUNCTION__, __LINE__, event.data.wheel.type,
			event.data.wheel.amount * event.data.wheel.rotation,
			event.data.wheel.direction,
			event.data.wheel.x, event.data.wheel.y);

	// Fire mouse wheel event.
	dispatch_event(&event);
}

LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) {
	MSLLHOOKSTRUCT *mshook = (MSLLHOOKSTRUCT *) lParam;
	switch (wParam) {
		case WM_LBUTTONDOWN:
			set_modifier_mask(MASK_BUTTON1);
			process_button_pressed(mshook, MOUSE_BUTTON1);
			break;

		case WM_RBUTTONDOWN:
			set_modifier_mask(MASK_BUTTON2);
			process_button_pressed(mshook, MOUSE_BUTTON2);
			break;

		case WM_MBUTTONDOWN:
			set_modifier_mask(MASK_BUTTON3);
			process_button_pressed(mshook, MOUSE_BUTTON3);
			break;

		case WM_XBUTTONDOWN:
		case WM_NCXBUTTONDOWN:
			if (HIWORD(mshook->mouseData) == XBUTTON1) {
				set_modifier_mask(MASK_BUTTON4);
				process_button_pressed(mshook, MOUSE_BUTTON4);
			} else if (HIWORD(mshook->mouseData) == XBUTTON2) {
				set_modifier_mask(MASK_BUTTON5);
				process_button_pressed(mshook, MOUSE_BUTTON5);
			} else {
				// Extra mouse buttons.
				uint16_t button = HIWORD(mshook->mouseData);

				// Add support for mouse 4 & 5.
				if (button == 4) {
					set_modifier_mask(MOUSE_BUTTON4);
				} else if (button == 5) {
					set_modifier_mask(MOUSE_BUTTON5);
				}

				process_button_pressed(mshook, button);
			}
			break;


		case WM_LBUTTONUP:
			unset_modifier_mask(MASK_BUTTON1);
			process_button_released(mshook, MOUSE_BUTTON1);
			break;

		case WM_RBUTTONUP:
			unset_modifier_mask(MASK_BUTTON2);
			process_button_released(mshook, MOUSE_BUTTON2);
			break;

		case WM_MBUTTONUP:
			unset_modifier_mask(MASK_BUTTON3);
			process_button_released(mshook, MOUSE_BUTTON3);
			break;

		case WM_XBUTTONUP:
		case WM_NCXBUTTONUP:
			if (HIWORD(mshook->mouseData) == XBUTTON1) {
				unset_modifier_mask(MASK_BUTTON4);
				process_button_released(mshook, MOUSE_BUTTON4);
			} else if (HIWORD(mshook->mouseData) == XBUTTON2) {
				unset_modifier_mask(MASK_BUTTON5);
				process_button_released(mshook, MOUSE_BUTTON5);
			} else {
				// Extra mouse buttons.
				uint16_t button = HIWORD(mshook->mouseData);

				// Add support for mouse 4 & 5.
				if (button == 4) {
					unset_modifier_mask(MOUSE_BUTTON4);
				} else if (button == 5) {
					unset_modifier_mask(MOUSE_BUTTON5);
				}

				process_button_released(mshook, MOUSE_BUTTON5);
			}
			break;

		case WM_MOUSEMOVE:
			process_mouse_moved(mshook);
			break;

		case WM_MOUSEWHEEL:
			process_mouse_wheel(mshook, WHEEL_VERTICAL_DIRECTION);
			break;

		/* For horizontal scroll wheel support.
		 * NOTE Windows >= Vista
		 * case 0x020E:
		 */
		case WM_MOUSEHWHEEL:
			process_mouse_wheel(mshook, WHEEL_HORIZONTAL_DIRECTION);
			break;

		default:
			// In theory this *should* never execute.
			logger(LOG_LEVEL_DEBUG,	"%s [%u]: Unhandled Windows mouse event: %#X.\n",
					__FUNCTION__, __LINE__, (unsigned int) wParam);
			break;
	}

	LRESULT hook_result = -1;
	if (nCode < 0 || event.reserved ^ 0x01) {
		hook_result = CallNextHookEx(mouse_event_hhook, nCode, wParam, lParam);
	} else {
		logger(LOG_LEVEL_DEBUG,	"%s [%u]: Consuming the current event. (%li)\n",
				__FUNCTION__, __LINE__, (long) hook_result);
	}

	return hook_result;
}


// Callback function that handles events.
void CALLBACK win_hook_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) {
	switch (event) {
		case EVENT_OBJECT_NAMECHANGE:
			logger(LOG_LEVEL_INFO, "%s [%u]: Restarting Windows input hook on window event: %#X.\n",
					__FUNCTION__, __LINE__, event);

			// Remove any keyboard or mouse hooks that are still running.
			if (keyboard_event_hhook != NULL) {
				UnhookWindowsHookEx(keyboard_event_hhook);
			}

			if (mouse_event_hhook != NULL) {
				UnhookWindowsHookEx(mouse_event_hhook);
			}

			// Restart the event hooks.
			keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0);
			mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0);

			// Re-initialize modifier masks.
			initialize_modifiers();

			// FIXME We should compare the modifier mask before and after the restart
			// to determine if we should synthesize missing events.

			// Check for event hook error.
			if (keyboard_event_hhook == NULL || mouse_event_hhook == NULL) {
				logger(LOG_LEVEL_ERROR,	"%s [%u]: SetWindowsHookEx() failed! (%#lX)\n",
						__FUNCTION__, __LINE__, (unsigned long) GetLastError());
			}
			break;

		default:
			logger(LOG_LEVEL_INFO, "%s [%u]: Unhandled Windows window event: %#X.\n",
					__FUNCTION__, __LINE__, event);
	}
}


IOHOOK_API int hook_run() {
	int status = IOHOOK_FAILURE;

	// Set the thread id we want to signal later.
	hook_thread_id = GetCurrentThreadId();

	// Spot check the hInst incase the library was statically linked and DllMain
	// did not receive a pointer on load.
	if (hInst == NULL) {
		logger(LOG_LEVEL_INFO,	"%s [%u]: hInst was not set by DllMain().\n",
				__FUNCTION__, __LINE__);

		hInst = GetModuleHandle(NULL);
		if (hInst != NULL) {
			// Initialize native input helper functions.
            load_input_helper();
		} else {
			logger(LOG_LEVEL_ERROR,	"%s [%u]: Could not determine hInst for SetWindowsHookEx()! (%#lX)\n",
					__FUNCTION__, __LINE__, (unsigned long) GetLastError());

			status = IOHOOK_ERROR_GET_MODULE_HANDLE;
		}
	}

	// Create the native hooks.
	keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0);
	mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0);

	// Create a window event hook to listen for capture change.
	win_event_hhook = SetWinEventHook(
			EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE,
			NULL,
			win_hook_event_proc,
			0, 0,
			WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);

	// If we did not encounter a problem, start processing events.
	if (keyboard_event_hhook != NULL && mouse_event_hhook != NULL) {
		if (win_event_hhook == NULL) {
			logger(LOG_LEVEL_WARN,	"%s [%u]: SetWinEventHook() failed!\n",
					__FUNCTION__, __LINE__);
		}

		logger(LOG_LEVEL_DEBUG,	"%s [%u]: SetWindowsHookEx() successful.\n",
				__FUNCTION__, __LINE__);

		// Check and setup modifiers.
		initialize_modifiers();

		// Set the exit status.
		status = IOHOOK_SUCCESS;

		// Windows does not have a hook start event or callback so we need to
		// manually fake it.
		hook_start_proc();

		// Block until the thread receives an WM_QUIT request.
		MSG message;
		while (GetMessage(&message, (HWND) NULL, 0, 0) > 0) {
			TranslateMessage(&message);
			DispatchMessage(&message);
		}
	} else {
		logger(LOG_LEVEL_ERROR,	"%s [%u]: SetWindowsHookEx() failed! (%#lX)\n",
				__FUNCTION__, __LINE__, (unsigned long) GetLastError());

		status = IOHOOK_ERROR_SET_WINDOWS_HOOK_EX;
	}


	// Unregister any hooks that may still be installed.
	unregister_running_hooks();

	// We must explicitly call the cleanup handler because Windows does not
	// provide a thread cleanup method like POSIX pthread_cleanup_push/pop.
	hook_stop_proc();

	return status;
}

IOHOOK_API int hook_stop() {
	int status = IOHOOK_FAILURE;

	// Try to exit the thread naturally.
	if (PostThreadMessage(hook_thread_id, WM_QUIT, (WPARAM) NULL, (LPARAM) NULL)) {
		status = IOHOOK_SUCCESS;
	}

	logger(LOG_LEVEL_DEBUG,	"%s [%u]: Status: %#X.\n",
			__FUNCTION__, __LINE__, status);

	return status;
}