// NOTE: This function executes on the hook thread!  If you need to block
// please do so on another thread via your own event dispatcher.
void jni_EventDispatcher(uiohook_event * const event) {
	jobject NativeInputEvent_object = NULL;

	switch (event->type) {
		/* The following start and stop functions are less than ideal for attaching JNI.
		 * TODO Consider moving threads out of the lib and into Java.
		 */
		case EVENT_THREAD_STARTED:
			if ((*jvm)->GetEnv(jvm, (void **)(&env), jvm_attach_args.version) == JNI_EDETACHED) {
				(*jvm)->AttachCurrentThread(jvm, (void **)(&env), &jvm_attach_args);
			}
			break;

		case EVENT_THREAD_STOPPED:
			// NOTE This callback may note be called from Windows under some circumstances.
			if ((*jvm)->GetEnv(jvm, (void **)(&env), jvm_attach_args.version) == JNI_OK) {
				if ((*jvm)->DetachCurrentThread(jvm) == JNI_OK) {
					env = NULL;
				}
			}
			break;

		case EVENT_HOOK_ENABLED:
		case EVENT_HOOK_DISABLED:
			break;

		case EVENT_KEY_PRESSED:
			if (env != NULL) {
				jint location;
				if (jni_ConvertToJavaLocation(event->data.keyboard.keycode, &location) == JNI_OK) {
					NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_keyboard_NativeKeyEvent->cls,
							org_jnativehook_keyboard_NativeKeyEvent->init,
							org_jnativehook_keyboard_NativeKeyEvent_NATIVE_KEY_PRESSED,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.keyboard.rawcode,
							(jint)	event->data.keyboard.keycode,
							(jchar)	org_jnativehook_keyboard_NativeKeyEvent_CHAR_UNDEFINED,
							location);
				}
			}
			break;

		case EVENT_KEY_RELEASED:
			if (env != NULL) {
				jint location;
				if (jni_ConvertToJavaLocation(event->data.keyboard.keycode, &location) == JNI_OK) {
					NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_keyboard_NativeKeyEvent->cls,
							org_jnativehook_keyboard_NativeKeyEvent->init,
							org_jnativehook_keyboard_NativeKeyEvent_NATIVE_KEY_RELEASED,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.keyboard.rawcode,
							(jint)	event->data.keyboard.keycode,
							(jchar)	org_jnativehook_keyboard_NativeKeyEvent_CHAR_UNDEFINED,
							location);
				}
			}
			break;

		case EVENT_KEY_TYPED:
			if (env != NULL) {
				jint location;
				if (jni_ConvertToJavaLocation(event->data.keyboard.keycode, &location) == JNI_OK) {
					NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_keyboard_NativeKeyEvent->cls,
							org_jnativehook_keyboard_NativeKeyEvent->init,
							org_jnativehook_keyboard_NativeKeyEvent_NATIVE_KEY_TYPED,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.keyboard.rawcode,
							(jint)	org_jnativehook_keyboard_NativeKeyEvent_VK_UNDEFINED,
							(jchar)	event->data.keyboard.keychar,
							location);
				}
			}
			break;

		case EVENT_MOUSE_PRESSED:
			if (env != NULL) {
				NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_mouse_NativeMouseEvent->cls,
							org_jnativehook_mouse_NativeMouseEvent->init,
							org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_PRESSED,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.mouse.x,
							(jint)	event->data.mouse.y,
							(jint)	event->data.mouse.clicks,
							(jint)	event->data.mouse.button);
			}
			break;

		case EVENT_MOUSE_RELEASED:
			if (env != NULL) {
				NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_mouse_NativeMouseEvent->cls,
							org_jnativehook_mouse_NativeMouseEvent->init,
							org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_RELEASED,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.mouse.x,
							(jint)	event->data.mouse.y,
							(jint)	event->data.mouse.clicks,
							(jint)	event->data.mouse.button);
			}
			break;

		case EVENT_MOUSE_CLICKED:
			if (env != NULL) {
				NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_mouse_NativeMouseEvent->cls,
							org_jnativehook_mouse_NativeMouseEvent->init,
							org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_CLICKED,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.mouse.x,
							(jint)	event->data.mouse.y,
							(jint)	event->data.mouse.clicks,
							(jint)	event->data.mouse.button);
			}
			break;

		case EVENT_MOUSE_MOVED:
			if (env != NULL) {
				NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_mouse_NativeMouseEvent->cls,
							org_jnativehook_mouse_NativeMouseEvent->init,
							org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_MOVED,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.mouse.x,
							(jint)	event->data.mouse.y,
							(jint)	event->data.mouse.clicks,
							(jint)	event->data.mouse.button);
			}
			break;

		case EVENT_MOUSE_DRAGGED:
			if (env != NULL) {
				NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_mouse_NativeMouseEvent->cls,
							org_jnativehook_mouse_NativeMouseEvent->init,
							org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_DRAGGED,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.mouse.x,
							(jint)	event->data.mouse.y,
							(jint)	event->data.mouse.clicks,
							(jint)	event->data.mouse.button);
			}
			break;

		case EVENT_MOUSE_WHEEL:
			if (env != NULL) {
				NativeInputEvent_object = (*env)->NewObject(
							env,
							org_jnativehook_mouse_NativeMouseWheelEvent->cls,
							org_jnativehook_mouse_NativeMouseWheelEvent->init,
							org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_WHEEL,
							(jlong)	event->time,
							(jint)	event->mask,
							(jint)	event->data.wheel.x,
							(jint)	event->data.wheel.y,
							(jint)	event->data.wheel.clicks,
							(jint)	event->data.wheel.type,
							(jint)	event->data.wheel.amount,
							(jint)	event->data.wheel.rotation);
			}
			break;
	}

	// NOTE It is assumed that we do not need to check env* for null because NativeInputEvent_object
	// cannot be assigned if env* is NULL.
	if (NativeInputEvent_object != NULL) {
		// Create the global screen references up front to save time in the callback.
		jobject GlobalScreen_object = (*env)->CallStaticObjectMethod(
				env,
				org_jnativehook_GlobalScreen->cls,
				org_jnativehook_GlobalScreen->getInstance);

		if (GlobalScreen_object != NULL) {
			(*env)->CallVoidMethod(
					env,
					GlobalScreen_object,
					org_jnativehook_GlobalScreen->dispatchEvent,
					NativeInputEvent_object);

			// Set the propagate flag from java.
			event->reserved = (unsigned short) (*env)->GetShortField(
					env,
					NativeInputEvent_object,
					org_jnativehook_NativeInputEvent->reserved);

			(*env)->DeleteLocalRef(env, GlobalScreen_object);
		}
		else {
			jni_Logger(env, LOG_LEVEL_ERROR,	"%s [%u]: Failed to acquire GlobalScreen singleton!\n",
					__FUNCTION__, __LINE__);

			jni_ThrowException(env, "java/lang/NullPointerException", "GlobalScreen singleton is null.");
		}

		(*env)->DeleteLocalRef(env, NativeInputEvent_object);
	}
}
// NOTE: This function executes on the hook thread!  If you need to block
// please do so on another thread via your own event dispatcher.
void jni_EventDispatcher(uiohook_event * const event) {
    JNIEnv *env;
    if ((*jvm)->GetEnv(jvm, (void **)(&env), jvm_attach_args.version) == JNI_OK) {
        jobject NativeInputEvent_obj = NULL;
        jint location = org_jnativehook_keyboard_NativeKeyEvent_LOCATION_UNKNOWN;
        switch (event->type) {
        case EVENT_HOOK_DISABLED:
        case EVENT_HOOK_ENABLED:
            notifyHookThread(env);
            return;


        case EVENT_KEY_PRESSED:
            if (jni_ConvertToJavaLocation(event->data.keyboard.keycode, &location) == JNI_OK) {
                NativeInputEvent_obj = (*env)->NewObject(
                                           env,
                                           org_jnativehook_keyboard_NativeKeyEvent->cls,
                                           org_jnativehook_keyboard_NativeKeyEvent->init,
                                           org_jnativehook_keyboard_NativeKeyEvent_NATIVE_KEY_PRESSED,
                                           (jint)	event->mask,
                                           (jint)	event->data.keyboard.rawcode,
                                           (jint)	event->data.keyboard.keycode,
                                           (jchar)	org_jnativehook_keyboard_NativeKeyEvent_CHAR_UNDEFINED,
                                           location);
            }
            break;

        case EVENT_KEY_RELEASED:
            if (jni_ConvertToJavaLocation(event->data.keyboard.keycode, &location) == JNI_OK) {
                NativeInputEvent_obj = (*env)->NewObject(
                                           env,
                                           org_jnativehook_keyboard_NativeKeyEvent->cls,
                                           org_jnativehook_keyboard_NativeKeyEvent->init,
                                           org_jnativehook_keyboard_NativeKeyEvent_NATIVE_KEY_RELEASED,
                                           (jint)	event->mask,
                                           (jint)	event->data.keyboard.rawcode,
                                           (jint)	event->data.keyboard.keycode,
                                           (jchar)	org_jnativehook_keyboard_NativeKeyEvent_CHAR_UNDEFINED,
                                           location);
            }
            break;

        case EVENT_KEY_TYPED:
            NativeInputEvent_obj = (*env)->NewObject(
                                       env,
                                       org_jnativehook_keyboard_NativeKeyEvent->cls,
                                       org_jnativehook_keyboard_NativeKeyEvent->init,
                                       org_jnativehook_keyboard_NativeKeyEvent_NATIVE_KEY_TYPED,
                                       (jint)	event->mask,
                                       (jint)	event->data.keyboard.rawcode,
                                       (jint)	org_jnativehook_keyboard_NativeKeyEvent_VC_UNDEFINED,
                                       (jchar)	event->data.keyboard.keychar,
                                       location);
            break;


        case EVENT_MOUSE_PRESSED:
            NativeInputEvent_obj = (*env)->NewObject(
                                       env,
                                       org_jnativehook_mouse_NativeMouseEvent->cls,
                                       org_jnativehook_mouse_NativeMouseEvent->init,
                                       org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_PRESSED,
                                       (jint)	event->mask,
                                       (jint)	event->data.mouse.x,
                                       (jint)	event->data.mouse.y,
                                       (jint)	event->data.mouse.clicks,
                                       (jint)	event->data.mouse.button);
            break;

        case EVENT_MOUSE_RELEASED:
            NativeInputEvent_obj = (*env)->NewObject(
                                       env,
                                       org_jnativehook_mouse_NativeMouseEvent->cls,
                                       org_jnativehook_mouse_NativeMouseEvent->init,
                                       org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_RELEASED,
                                       (jint)	event->mask,
                                       (jint)	event->data.mouse.x,
                                       (jint)	event->data.mouse.y,
                                       (jint)	event->data.mouse.clicks,
                                       (jint)	event->data.mouse.button);
            break;

        case EVENT_MOUSE_CLICKED:
            NativeInputEvent_obj = (*env)->NewObject(
                                       env,
                                       org_jnativehook_mouse_NativeMouseEvent->cls,
                                       org_jnativehook_mouse_NativeMouseEvent->init,
                                       org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_CLICKED,
                                       (jint)	event->mask,
                                       (jint)	event->data.mouse.x,
                                       (jint)	event->data.mouse.y,
                                       (jint)	event->data.mouse.clicks,
                                       (jint)	event->data.mouse.button);
            break;

        case EVENT_MOUSE_MOVED:
            NativeInputEvent_obj = (*env)->NewObject(
                                       env,
                                       org_jnativehook_mouse_NativeMouseEvent->cls,
                                       org_jnativehook_mouse_NativeMouseEvent->init,
                                       org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_MOVED,
                                       (jint)	event->mask,
                                       (jint)	event->data.mouse.x,
                                       (jint)	event->data.mouse.y,
                                       (jint)	event->data.mouse.clicks,
                                       (jint)	event->data.mouse.button);
            break;

        case EVENT_MOUSE_DRAGGED:
            NativeInputEvent_obj = (*env)->NewObject(
                                       env,
                                       org_jnativehook_mouse_NativeMouseEvent->cls,
                                       org_jnativehook_mouse_NativeMouseEvent->init,
                                       org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_DRAGGED,
                                       (jint)	event->mask,
                                       (jint)	event->data.mouse.x,
                                       (jint)	event->data.mouse.y,
                                       (jint)	event->data.mouse.clicks,
                                       (jint)	event->data.mouse.button);
            break;

        case EVENT_MOUSE_WHEEL:
            NativeInputEvent_obj = (*env)->NewObject(
                                       env,
                                       org_jnativehook_mouse_NativeMouseWheelEvent->cls,
                                       org_jnativehook_mouse_NativeMouseWheelEvent->init,
                                       org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_WHEEL,
                                       (jint)	event->mask,
                                       (jint)	event->data.wheel.x,
                                       (jint)	event->data.wheel.y,
                                       (jint)	event->data.wheel.clicks,
                                       (jint)	event->data.wheel.type,
                                       (jint)	event->data.wheel.amount,
                                       (jint)	event->data.wheel.rotation,
                                       (jint)	event->data.wheel.direction);
            break;

        default:
            jni_Logger(env, LOG_LEVEL_WARN,	"%s [%u]: Unknown native event type: %#X\n",
                       __FUNCTION__, __LINE__, event->type);
            break;
        }

        if (NativeInputEvent_obj != NULL) {
            // Set the private when field to the native event time.
            (*env)->SetShortField(
                env,
                NativeInputEvent_obj,
                org_jnativehook_NativeInputEvent->when,
                (jlong)	event->time);

            // Dispatch the event.
            (*env)->CallStaticVoidMethod(
                env,
                org_jnativehook_GlobalScreen->cls,
                org_jnativehook_GlobalScreen->dispatchEvent,
                NativeInputEvent_obj);

            // Set the propagate flag from java.
            event->reserved = (unsigned short) (*env)->GetShortField(
                                  env,
                                  NativeInputEvent_obj,
                                  org_jnativehook_NativeInputEvent->reserved);

            // Make sure our object is garbage collected.
            (*env)->DeleteLocalRef(env, NativeInputEvent_obj);
        }
    }
}