package me.coley.simplejna.hook.key;

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.LPARAM;
import com.sun.jna.platform.win32.WinDef.LRESULT;
import com.sun.jna.platform.win32.WinDef.WPARAM;
import com.sun.jna.platform.win32.WinUser.KBDLLHOOKSTRUCT;
import com.sun.jna.platform.win32.WinUser.LowLevelKeyboardProc;

import me.coley.simplejna.hook.DeviceEventReceiver;

/**
 * A simplified representation of JNA's LowLevelKeyboardProc.
 * 
 * @author Matt
 */
public abstract class KeyEventReceiver extends DeviceEventReceiver<KeyHookManager> implements LowLevelKeyboardProc {
	private static final int WM_KEYDOWN = 256, WM_KEYUP = 257;
	private static final int WM_SYSKEYDOWN = 260, WM_SYSKEYUP = 261;

	public KeyEventReceiver(KeyHookManager hookManager) {
		super(hookManager);
	}

	@Override
	public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT info) {
		int code = wParam.intValue();
		boolean cancel = onKeyUpdate(SystemState.from(code), PressState.from(code), info.time, info.vkCode);
		if (cancel) {
			return new LRESULT(1);
		}
		Pointer ptr = info.getPointer();
		long peer = Pointer.nativeValue(ptr);
		return User32.INSTANCE.CallNextHookEx(getHookManager().getHhk(this), nCode, wParam, new LPARAM(peer));
	}

	/**
	 * Called when a key-state is updated.
	 * 
	 * @param sysState
	 *            System <i>(alt key)</i> or standard state when event fired.
	 * @param pressState
	 *            Up or down state of event.
	 * @param time
	 *            Time of event.
	 * @param vkCode
	 *            Key-code.
	 * @return {@code true} if event is to be cancelled <i>(not handled by
	 *         windows)</i>. {@code false} if event is to be handled normally.
	 */
	public abstract boolean onKeyUpdate(SystemState sysState, PressState pressState, int time, int vkCode);

	/**
	 * System-key status.
	 * 
	 * @author Matt
	 */
	public enum SystemState {
		SYSTEM, STANDARD;

		public static SystemState from(int code) {
			return (code == WM_SYSKEYDOWN || code == WM_SYSKEYUP) ? SYSTEM : STANDARD;
		}
	}

	/**
	 * Up or down status.
	 * 
	 * @author Matt
	 */
	public enum PressState {
		UP, DOWN, UNKNOWN;

		public static PressState from(int code) {
			boolean up = code == WM_SYSKEYUP || code == WM_KEYUP;
			boolean down = code == WM_SYSKEYDOWN || code == WM_KEYDOWN;
			return (up) ? UP : (down) ? DOWN : UNKNOWN;
		}
	}
}