
#include <string.h>
#include "X11Keyboard.h"
#include "X11Display.h"
#include "Xlib.h"
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <Spectre/System/Log.h>

namespace sp {

static Keyboard::Key KeySymToKeyboardKey(KeySym key) {

	switch(key) {
	// Letters
	case XK_A: case XK_a:
		return Keyboard::Key::A;
	case XK_B: case XK_b:
		return Keyboard::Key::B;
	case XK_C: case XK_c:
		return Keyboard::Key::C;
	case XK_D: case XK_d:
		return Keyboard::Key::D;
	case XK_E: case XK_e:
		return Keyboard::Key::E;
	case XK_F: case XK_f:
		return Keyboard::Key::F;
	case XK_G: case XK_g:
		return Keyboard::Key::G;
	case XK_H: case XK_h:
		return Keyboard::Key::H;
	case XK_I: case XK_i:
		return Keyboard::Key::I;
	case XK_J: case XK_j:
		return Keyboard::Key::J;
	case XK_K: case XK_k:
		return Keyboard::Key::K;
	case XK_L: case XK_l:
		return Keyboard::Key::L;
	case XK_M: case XK_m:
		return Keyboard::Key::M;
	case XK_N: case XK_n:
		return Keyboard::Key::N;
	case XK_O: case XK_o:
		return Keyboard::Key::O;
	case XK_P: case XK_p:
		return Keyboard::Key::P;
	case XK_Q: case XK_q:
		return Keyboard::Key::Q;
	case XK_R: case XK_r:
		return Keyboard::Key::R;
	case XK_S: case XK_s:
		return Keyboard::Key::S;
	case XK_T: case XK_t:
		return Keyboard::Key::T;
	case XK_U: case XK_u:
		return Keyboard::Key::U;
	case XK_V: case XK_v:
		return Keyboard::Key::V;
	case XK_W: case XK_w:
		return Keyboard::Key::W;
	case XK_X: case XK_x:
		return Keyboard::Key::X;
	case XK_Y: case XK_y:
		return Keyboard::Key::Y;
	case XK_Z: case XK_z:
		return Keyboard::Key::Z;

	// Numbers
	case XK_1:
		return Keyboard::Key::One;
	case XK_2:
		return Keyboard::Key::Two;
	case XK_3:
		return Keyboard::Key::Three;
	case XK_4:
		return Keyboard::Key::Four;
	case XK_5:
		return Keyboard::Key::Five;
	case XK_6:
		return Keyboard::Key::Six;
	case XK_7:
		return Keyboard::Key::Seven;
	case XK_8:
		return Keyboard::Key::Eight;
	case XK_9:
		return Keyboard::Key::Nine;
	case XK_0:
		return Keyboard::Key::Zero;

	case XK_period :
		return Keyboard::Key::Period;
	case XK_comma :
		return Keyboard::Key::Comma;
	case XK_Return :
		return Keyboard::Key::Enter;
	case XK_BackSpace :
		return Keyboard::Key::Backspace;
	case XK_Escape :
		return Keyboard::Key::Escape;
	case XK_space :
		return Keyboard::Key::Space;
	case XK_Caps_Lock :
		return Keyboard::Key::Capslock;

	// Arrows
	case XK_Up :
		return Keyboard::Key::Up;
	case XK_Down :
		return Keyboard::Key::Down;
	case XK_Left :
		return Keyboard::Key::Left;
	case XK_Right :
		return Keyboard::Key::Right;

	// Numpad

	case XK_KP_1 : case XK_KP_End :
		return Keyboard::Key::Numpad1;
	case XK_KP_2 : case XK_KP_Down :
		return Keyboard::Key::Numpad2;
	case XK_KP_3 : case XK_KP_Page_Down :
		return Keyboard::Key::Numpad3;
	case XK_KP_4 : case XK_KP_Left :
		return Keyboard::Key::Numpad4;
	case XK_KP_5 : case XK_KP_Begin :
		return Keyboard::Key::Numpad5;
	case XK_KP_6 : case XK_KP_Right :
		return Keyboard::Key::Numpad6;
	case XK_KP_7 : case XK_KP_Home :
		return Keyboard::Key::Numpad7;
	case XK_KP_8 : case XK_KP_Up :
		return Keyboard::Key::Numpad8;
	case XK_KP_9 : case XK_KP_Page_Up :
		return Keyboard::Key::Numpad9;
	case XK_KP_0 : case XK_KP_Insert :
		return Keyboard::Key::Numpad0;

	case XK_Home :
		return Keyboard::Key::Home;
	case XK_End :
		return Keyboard::Key::End;
	case XK_Insert :
		return Keyboard::Key::Insert;
	case XK_Delete :
		return Keyboard::Key::Delete;
	case XK_Page_Up :
		return Keyboard::Key::PageUp;
	case XK_Page_Down :
		return Keyboard::Key::PageDown;
	case XK_Pause :
		return Keyboard::Key::Pause;


	// Function keys
	case XK_F1 :
		return Keyboard::Key::F1;
	case XK_F2 :
		return Keyboard::Key::F2;
	case XK_F3 :
		return Keyboard::Key::F3;
	case XK_F4 :
		return Keyboard::Key::F4;
	case XK_F5 :
		return Keyboard::Key::F5;
	case XK_F6 :
		return Keyboard::Key::F6;
	case XK_F7 :
		return Keyboard::Key::F7;
	case XK_F8 :
		return Keyboard::Key::F8;
	case XK_F9 :
		return Keyboard::Key::F9;
	case XK_F10 :
		return Keyboard::Key::F10;
	case XK_F11 :
		return Keyboard::Key::F11;
	case XK_F12 :
		return Keyboard::Key::F12;

	case XK_Tab :
		return Keyboard::Key::Tab;
	case XK_Shift_L :
		return Keyboard::Key::LShift;
	case XK_Shift_R :
		return Keyboard::Key::RShift;
	case XK_Control_L :
		return Keyboard::Key::LCtrl;
	case XK_Control_R :
		return Keyboard::Key::RCtrl;
	case XK_Alt_L :
		return Keyboard::Key::LAlt;
	case XK_Alt_R :
#ifdef XK_XKB_KEYS
	case XK_ISO_Level3_Shift :
#endif /* XK_XKB_KEYS */
		return Keyboard::Key::RAlt;

	default:
		sp::Log::debug("X11Keyboard - Unknown keycode: 0x%X", key);
		return Keyboard::Key::Unknown;
	};
}

X11Keyboard::X11Keyboard() :
m_disp(NULL),
m_win(0)
{
	memset(m_key_state, 0, sizeof(m_key_state) / sizeof(m_key_state[0]));
}

X11Keyboard::~X11Keyboard()
{
}

void X11Keyboard::init()
{
}

bool X11Keyboard::isKeyDown(Keyboard::Key key)
{
	KeySym sym;

	// Only signal that a button is down
	// if we have focus on a window.
	if (!m_win) {
		return false;
	}

	switch(key) {
	// Letters
	case Keyboard::Key::A : sym = XK_a; break;
	case Keyboard::Key::B : sym = XK_b; break;
	case Keyboard::Key::C : sym = XK_c; break;
	case Keyboard::Key::D : sym = XK_d; break;
	case Keyboard::Key::E : sym = XK_e; break;
	case Keyboard::Key::F : sym = XK_f; break;
	case Keyboard::Key::G : sym = XK_g; break;
	case Keyboard::Key::H : sym = XK_h; break;
	case Keyboard::Key::I : sym = XK_i; break;
	case Keyboard::Key::J : sym = XK_j; break;
	case Keyboard::Key::K : sym = XK_k; break;
	case Keyboard::Key::L : sym = XK_l; break;
	case Keyboard::Key::M : sym = XK_m; break;
	case Keyboard::Key::N : sym = XK_n; break;
	case Keyboard::Key::O : sym = XK_o; break;
	case Keyboard::Key::P : sym = XK_p; break;
	case Keyboard::Key::Q : sym = XK_q; break;
	case Keyboard::Key::R : sym = XK_r; break;
	case Keyboard::Key::S : sym = XK_s; break;
	case Keyboard::Key::T : sym = XK_t; break;
	case Keyboard::Key::U : sym = XK_u; break;
	case Keyboard::Key::V : sym = XK_v; break;
	case Keyboard::Key::W : sym = XK_w; break;
	case Keyboard::Key::X : sym = XK_x; break;
	case Keyboard::Key::Y : sym = XK_y; break;
	case Keyboard::Key::Z : sym = XK_z; break;

	// Numbers
	case Keyboard::Key::One   : sym = XK_1; break;
	case Keyboard::Key::Two   : sym = XK_2; break;
	case Keyboard::Key::Three : sym = XK_3; break;
	case Keyboard::Key::Four  : sym = XK_4; break;
	case Keyboard::Key::Five  : sym = XK_5; break;
	case Keyboard::Key::Six   : sym = XK_6; break;
	case Keyboard::Key::Seven : sym = XK_7; break;
	case Keyboard::Key::Eight : sym = XK_8; break;
	case Keyboard::Key::Nine  : sym = XK_9; break;
	case Keyboard::Key::Zero  : sym = XK_0; break;

	case Keyboard::Key::Period    : sym = XK_period; break;
	case Keyboard::Key::Comma     : sym = XK_comma; break;
	case Keyboard::Key::Enter  	  : sym = XK_Return; break;
	case Keyboard::Key::Backspace : sym = XK_BackSpace; break;
	case Keyboard::Key::Escape    : sym = XK_Escape; break;
	case Keyboard::Key::Space     : sym = XK_space; break;
	case Keyboard::Key::Capslock  : sym = XK_Caps_Lock; break;

	// Arrows
	case Keyboard::Key::Up    : sym = XK_Up;    break;
	case Keyboard::Key::Down  : sym = XK_Down;  break;
	case Keyboard::Key::Left  : sym = XK_Left;  break;
	case Keyboard::Key::Right : sym = XK_Right; break;

	// Numpad
	case Keyboard::Key::Numpad1 : sym = XK_KP_1; break;
	case Keyboard::Key::Numpad2 : sym = XK_KP_2; break;
	case Keyboard::Key::Numpad3 : sym = XK_KP_3; break;
	case Keyboard::Key::Numpad4 : sym = XK_KP_4; break;
	case Keyboard::Key::Numpad5 : sym = XK_KP_5; break;
	case Keyboard::Key::Numpad6 : sym = XK_KP_6; break;
	case Keyboard::Key::Numpad7 : sym = XK_KP_7; break;
	case Keyboard::Key::Numpad8 : sym = XK_KP_8; break;
	case Keyboard::Key::Numpad9 : sym = XK_KP_9; break;
	case Keyboard::Key::Numpad0 : sym = XK_KP_0; break;

	case Keyboard::Key::Home     : sym = XK_Home;      break;
	case Keyboard::Key::End      : sym = XK_End;       break;
	case Keyboard::Key::Insert   : sym = XK_Insert;    break;
	case Keyboard::Key::Delete   : sym = XK_Delete;    break;
	case Keyboard::Key::PageUp   : sym = XK_Page_Up;   break;
	case Keyboard::Key::PageDown : sym = XK_Page_Down; break;
	case Keyboard::Key::Pause 	 : sym = XK_Pause;     break;

	// Function keys
	case Keyboard::Key::F1  : sym = XK_F1;  break;
	case Keyboard::Key::F2  : sym = XK_F2;  break;
	case Keyboard::Key::F3  : sym = XK_F3;  break;
	case Keyboard::Key::F4  : sym = XK_F4;  break;
	case Keyboard::Key::F5  : sym = XK_F5;  break;
	case Keyboard::Key::F6  : sym = XK_F6;  break;
	case Keyboard::Key::F7  : sym = XK_F7;  break;
	case Keyboard::Key::F8  : sym = XK_F8;  break;
	case Keyboard::Key::F9  : sym = XK_F9;  break;
	case Keyboard::Key::F10 : sym = XK_F10; break;
	case Keyboard::Key::F11 : sym = XK_F11; break;
	case Keyboard::Key::F12 : sym = XK_F12; break;

	case Keyboard::Key::Tab    : sym = XK_Tab;       break;
	case Keyboard::Key::LShift : sym = XK_Shift_L;   break;
	case Keyboard::Key::RShift : sym = XK_Shift_R;   break;
	case Keyboard::Key::LCtrl  : sym = XK_Control_L; break;
	case Keyboard::Key::RCtrl  : sym = XK_Control_R; break;
	case Keyboard::Key::LAlt   : sym = XK_Alt_L;     break;
#ifdef XK_XKB_KEYS
	case Keyboard::Key::RAlt   : sym = XK_ISO_Level3_Shift; break;
#else
	case Keyboard::Key::RAlt   : sym = XK_Alt_R;     break;
#endif /* XK_XKB_KEYS */
	default : sym = 0; break;
	}

	KeyCode keycode = ::XKeysymToKeycode(Xlib::getDisplay(), sym);

	if (keycode) {
		return m_key_state[keycode / 8] & (1 << (keycode % 8));
	}
	return false;
}

bool X11Keyboard::handleMessage(XKeyEvent* xkeyevent, Event& event)
{
	KeySym sym = ::XLookupKeysym(xkeyevent, 0);
	Keyboard::Key code = KeySymToKeyboardKey(sym);

	if (code != Keyboard::Key::Unknown) {
		event.type = Event::Key;
		event.key.code = code;
		event.key.pressed = xkeyevent->type == KeyPress;
		return true;
	}
	return false;
}

void X11Keyboard::update(InputModule *input)
{
	X11Display *focus = X11Display::getFocused();
	m_win = focus ? (::Window) focus->getHandle() : 0;

	// If we have focus.
	if (m_win) {

		// Query keyboard state.
		::XQueryKeymap(Xlib::getDisplay(), m_key_state);
	}
}

} // namespace sp
