// Copyright (C) 2015 Jérôme Leclercq
// This file is part of the "Nazara Engine - Utility module"
// For conditions of distribution and use, see copyright notice in Config.hpp

#include <Nazara/Utility/X11/InputImpl.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Utility/Window.hpp>
#include <Nazara/Utility/X11/Display.hpp>
#include <X11/keysym.h>
#include <X11/XF86keysym.h>
#include <X11/Xlib.h>
#include <xcb/xcb_keysyms.h>
#include <Nazara/Utility/Debug.hpp>

namespace Nz
{
	namespace
	{
		KeySym GetKeySym(Keyboard::Key key)
		{
			// X11 keysym correspondant
			KeySym keysym = 0;
			switch (key)
			{
				// Lettres
				case Keyboard::A: keysym = XK_A; break;
				case Keyboard::B: keysym = XK_B; break;
				case Keyboard::C: keysym = XK_C; break;
				case Keyboard::D: keysym = XK_D; break;
				case Keyboard::E: keysym = XK_E; break;
				case Keyboard::F: keysym = XK_F; break;
				case Keyboard::G: keysym = XK_G; break;
				case Keyboard::H: keysym = XK_H; break;
				case Keyboard::I: keysym = XK_I; break;
				case Keyboard::J: keysym = XK_J; break;
				case Keyboard::K: keysym = XK_K; break;
				case Keyboard::L: keysym = XK_L; break;
				case Keyboard::M: keysym = XK_M; break;
				case Keyboard::N: keysym = XK_N; break;
				case Keyboard::O: keysym = XK_O; break;
				case Keyboard::P: keysym = XK_P; break;
				case Keyboard::Q: keysym = XK_Q; break;
				case Keyboard::R: keysym = XK_R; break;
				case Keyboard::S: keysym = XK_S; break;
				case Keyboard::T: keysym = XK_T; break;
				case Keyboard::U: keysym = XK_U; break;
				case Keyboard::V: keysym = XK_V; break;
				case Keyboard::W: keysym = XK_W; break;
				case Keyboard::X: keysym = XK_X; break;
				case Keyboard::Y: keysym = XK_Y; break;
				case Keyboard::Z: keysym = XK_Z; break;

				// Touches de fonction
				case Keyboard::F1: keysym = XK_F1; break;
				case Keyboard::F2: keysym = XK_F2; break;
				case Keyboard::F3: keysym = XK_F3; break;
				case Keyboard::F4: keysym = XK_F4; break;
				case Keyboard::F5: keysym = XK_F5; break;
				case Keyboard::F6: keysym = XK_F6; break;
				case Keyboard::F7: keysym = XK_F7; break;
				case Keyboard::F8: keysym = XK_F8; break;
				case Keyboard::F9: keysym = XK_F9; break;
				case Keyboard::F10: keysym = XK_F10; break;
				case Keyboard::F11: keysym = XK_F11; break;
				case Keyboard::F12: keysym = XK_F12; break;
				case Keyboard::F13: keysym = XK_F13; break;
				case Keyboard::F14: keysym = XK_F14; break;
				case Keyboard::F15: keysym = XK_F15; break;

				// Flèches directionnelles
				case Keyboard::Down: keysym = XK_Down; break;
				case Keyboard::Left: keysym = XK_Left; break;
				case Keyboard::Right: keysym = XK_Right; break;
				case Keyboard::Up: keysym = XK_Up; break;

				// Pavé numérique
				case Keyboard::Add: keysym = XK_KP_Add; break;
				case Keyboard::Decimal: keysym = XK_KP_Decimal; break;
				case Keyboard::Divide: keysym = XK_KP_Divide; break;
				case Keyboard::Multiply: keysym = XK_KP_Multiply; break;
				case Keyboard::Numpad0: keysym = XK_KP_0; break;
				case Keyboard::Numpad1: keysym = XK_KP_1; break;
				case Keyboard::Numpad2: keysym = XK_KP_2; break;
				case Keyboard::Numpad3: keysym = XK_KP_3; break;
				case Keyboard::Numpad4: keysym = XK_KP_4; break;
				case Keyboard::Numpad5: keysym = XK_KP_5; break;
				case Keyboard::Numpad6: keysym = XK_KP_6; break;
				case Keyboard::Numpad7: keysym = XK_KP_7; break;
				case Keyboard::Numpad8: keysym = XK_KP_8; break;
				case Keyboard::Numpad9: keysym = XK_KP_9; break;
				case Keyboard::Subtract: keysym = XK_KP_Subtract; break;

				// Divers
				case Keyboard::Backslash: keysym = XK_backslash; break;
				case Keyboard::Backspace: keysym = XK_BackSpace; break;
				case Keyboard::Clear: keysym = XK_Clear; break;
				case Keyboard::Comma: keysym = XK_comma; break;
				case Keyboard::Dash: keysym = XK_minus; break;
				case Keyboard::Delete: keysym = XK_Delete; break;
				case Keyboard::End: keysym = XK_End; break;
				case Keyboard::Equal: keysym = XK_equal; break;
				case Keyboard::Escape: keysym = XK_Escape; break;
				case Keyboard::Home: keysym = XK_Home; break;
				case Keyboard::Insert: keysym = XK_Insert; break;
				case Keyboard::LAlt: keysym = XK_Alt_L; break;
				case Keyboard::LBracket: keysym = XK_bracketleft; break;
				case Keyboard::LControl: keysym = XK_Control_L; break;
				case Keyboard::LShift: keysym = XK_Shift_L; break;
				case Keyboard::LSystem: keysym = XK_Super_L; break;
				case Keyboard::Num0: keysym = XK_0; break;
				case Keyboard::Num1: keysym = XK_1; break;
				case Keyboard::Num2: keysym = XK_2; break;
				case Keyboard::Num3: keysym = XK_3; break;
				case Keyboard::Num4: keysym = XK_4; break;
				case Keyboard::Num5: keysym = XK_5; break;
				case Keyboard::Num6: keysym = XK_6; break;
				case Keyboard::Num7: keysym = XK_7; break;
				case Keyboard::Num8: keysym = XK_8; break;
				case Keyboard::Num9: keysym = XK_9; break;
				case Keyboard::PageDown: keysym = XK_Page_Down; break;
				case Keyboard::PageUp: keysym = XK_Page_Up; break;
				case Keyboard::Pause: keysym = XK_Pause; break;
				case Keyboard::Period: keysym = XK_period; break;
				case Keyboard::Print: keysym = XK_Print; break;
				case Keyboard::PrintScreen: keysym = XK_Sys_Req; break;
				case Keyboard::Quote: keysym = XK_quotedbl; break;
				case Keyboard::RAlt: keysym = XK_Alt_R; break;
				case Keyboard::RBracket: keysym = XK_bracketright; break;
				case Keyboard::RControl: keysym = XK_Control_R; break;
				case Keyboard::Return: keysym = XK_Return; break;
				case Keyboard::RShift: keysym = XK_Shift_R; break;
				case Keyboard::RSystem: keysym = XK_Super_R; break;
				case Keyboard::Semicolon: keysym = XK_semicolon; break;
				case Keyboard::Slash: keysym = XK_slash; break;
				case Keyboard::Space: keysym = XK_space; break;
				case Keyboard::Tab: keysym = XK_Tab; break;
				case Keyboard::Tilde: keysym = XK_grave; break;

				// Touches navigateur
				case Keyboard::Browser_Back: keysym = XF86XK_Back; break;
				case Keyboard::Browser_Favorites: keysym = XF86XK_Favorites; break;
				case Keyboard::Browser_Forward: keysym = XF86XK_Forward; break;
				case Keyboard::Browser_Home: keysym = XF86XK_HomePage; break;
				case Keyboard::Browser_Refresh: keysym = XF86XK_Refresh; break;
				case Keyboard::Browser_Search: keysym = XF86XK_Search; break;
				case Keyboard::Browser_Stop: keysym = XF86XK_Stop; break;

				// Touches de contrôle
				case Keyboard::Media_Next: keysym = XF86XK_AudioNext; break;
				case Keyboard::Media_Play: keysym = XF86XK_AudioPlay; break;
				case Keyboard::Media_Previous: keysym = XF86XK_AudioPrev; break;
				case Keyboard::Media_Stop: keysym = XF86XK_AudioStop; break;

				// Touches de contrôle du volume
				case Keyboard::Volume_Down: keysym = XF86XK_AudioLowerVolume; break;
				case Keyboard::Volume_Mute: keysym = XF86XK_AudioMute; break;
				case Keyboard::Volume_Up: keysym = XF86XK_AudioRaiseVolume; break;

				// Touches à verrouillage
				case Keyboard::CapsLock: keysym = XK_Caps_Lock; break;
				case Keyboard::NumLock: keysym = XK_Num_Lock; break;
				case Keyboard::ScrollLock: keysym = XK_Scroll_Lock; break;

				default: break;
			}

			 // Sanity checks
			if (key < 0 || key >= Keyboard::Count || keysym == 0)
				NazaraWarning("Key " + String::Number(key) + " is not handled in Keyboard");

			return keysym;
		}
	}

	String EventImpl::GetKeyName(Keyboard::Key key)
	{
		KeySym keySym = GetKeySym(key);

		// XKeysymToString returns a static area.
		return XKeysymToString(keySym);
	}

	Vector2i EventImpl::GetMousePosition()
	{
		ScopedXCBConnection connection;

		ScopedXCB<xcb_generic_error_t> error(nullptr);

		ScopedXCB<xcb_query_pointer_reply_t> pointer(
			xcb_query_pointer_reply(
				connection,
				xcb_query_pointer(
					connection,
					X11::XCBDefaultRootWindow(connection)
				),
				&error
			)
		);

		if (error)
		{
			NazaraError("Failed to query pointer");
			return Vector2i(-1, -1);
		}

		return Vector2i(pointer->root_x, pointer->root_y);
	}

	Vector2i EventImpl::GetMousePosition(const Window& relativeTo)
	{
		WindowHandle handle = relativeTo.GetHandle();
		if (handle)
		{
			// Open a connection with the X server
			ScopedXCBConnection connection;

			ScopedXCB<xcb_generic_error_t> error(nullptr);

			ScopedXCB<xcb_query_pointer_reply_t> pointer(
				xcb_query_pointer_reply(
					connection,
					xcb_query_pointer(
						connection,
						handle
					),
					&error
				)
			);

			if (error)
			{
				NazaraError("Failed to query pointer");
				return Vector2i(-1, -1);
			}

			return Vector2i(pointer->win_x, pointer->win_y);
		}
		else
		{
			NazaraError("No window handle");
			return Vector2i(-1, -1);
		}
	}

	bool EventImpl::IsKeyPressed(Keyboard::Key key)
	{
		ScopedXCBConnection connection;

		xcb_keysym_t keySym = GetKeySym(key);

		xcb_key_symbols_t* keySymbols = X11::XCBKeySymbolsAlloc(connection);
		if (!keySymbols)
		{
			NazaraError("Failed to alloc key symbols");
			return false;
		}

		ScopedXCB<xcb_keycode_t> keyCode = xcb_key_symbols_get_keycode(keySymbols, keySym);
		if (!keyCode)
		{
			NazaraError("Failed to get key code");
			return false;
		}
		X11::XCBKeySymbolsFree(keySymbols);

		ScopedXCB<xcb_generic_error_t> error(nullptr);

		// Get the whole keyboard state
		ScopedXCB<xcb_query_keymap_reply_t> keymap(
			xcb_query_keymap_reply(
				connection,
				xcb_query_keymap(connection),
				&error
			)
		);

		if (error)
		{
			NazaraError("Failed to query keymap");
			return false;
		}

		// Check our keycode
		return (keymap->keys[*keyCode.get() / 8] & (1 << (*keyCode.get() % 8))) != 0;
	}

	bool EventImpl::IsMouseButtonPressed(Mouse::Button button)
	{
		ScopedXCBConnection connection;

		ScopedXCB<xcb_generic_error_t> error(nullptr);

		// Get pointer mask
		ScopedXCB<xcb_query_pointer_reply_t> pointer(
			xcb_query_pointer_reply(
				connection,
				xcb_query_pointer(
					connection,
					X11::XCBDefaultRootWindow(connection)
				),
				&error
			)
		);

		if (error)
		{
			NazaraError("Failed to query pointer");
			return false;
		}

		uint16_t buttons = pointer->mask;

		switch (button)
		{
			case Mouse::Left:     return buttons & XCB_BUTTON_MASK_1;
			case Mouse::Right:    return buttons & XCB_BUTTON_MASK_3;
			case Mouse::Middle:   return buttons & XCB_BUTTON_MASK_2;
			case Mouse::XButton1: return false; // not supported by X
			case Mouse::XButton2: return false; // not supported by X
		}

		NazaraError("Mouse button not supported.");
		return false;
	}

	void EventImpl::SetMousePosition(int x, int y)
	{
		ScopedXCBConnection connection;

		xcb_window_t root = X11::XCBDefaultRootWindow(connection);

		if (!X11::CheckCookie(
			connection,
			xcb_warp_pointer(
				connection,
				None, // Source window
				root, // Destination window
				0, 0, // Source position
				0, 0, // Source size
				x, y  // Destination position
			))
		)
			NazaraError("Failed to set mouse position");

		xcb_flush(connection);
	}

	void EventImpl::SetMousePosition(int x, int y, const Window& relativeTo)
	{
		ScopedXCBConnection connection;

		WindowHandle handle = relativeTo.GetHandle();
		if (handle)
		{
			if (!X11::CheckCookie(
				connection,
				xcb_warp_pointer(
					connection,
					None,   // Source window
					handle, // Destination window
					0, 0,   // Source position
					0, 0,   // Source size
					x, y    // Destination position
				))
			)
				NazaraError("Failed to set mouse position relative to window");

			xcb_flush(connection);
		}
		else
			NazaraError("No window handle");
	}
}
