//! \~\file pikbdlistener.h
//! \~\ingroup Console
//! \~\brief
//! \~english Console keyboard and mouse input listener
//! \~russian Слушатель клавиатурного и мышиного консольного ввода
/*
    PIP - Platform Independent Primitives
    Keyboard grabber for console
    Ivan Pelipenko peri4ko@yandex.ru

    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.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIKBDLISTENER_H
#define PIKBDLISTENER_H

#include "pithread.h"
#include "pitime.h"

//! \relatesalso PIKbdListener
//! \~\brief
//! \~english Waits until the active listener captures the configured exit key and then stops it.
//! \~russian Ожидает, пока активный слушатель перехватит настроенную клавишу выхода, и затем останавливает его.
#define WAIT_FOR_EXIT                                                                                                      \
	while (!PIKbdListener::exiting)                                                                                        \
		piMSleep(PIP_MIN_MSLEEP * 5);                                                                                      \
	if (PIKbdListener::instance()) {                                                                                       \
		if (!PIKbdListener::instance()->stopAndWait(PISystemTime::fromSeconds(1))) PIKbdListener::instance()->terminate(); \
	}


//! \~\ingroup Console
//! \~\brief
//! \~english Console input listener for keyboard and mouse events.
//! \~russian Слушатель консольного ввода для событий клавиатуры и мыши.
class PIP_EXPORT PIKbdListener: public PIThread {
	PIOBJECT_SUBCLASS(PIKbdListener, PIThread);
	friend class PIConsole;
	friend class PITerminal;

public:
	//! \~english Keyboard keys reported as non-character codes.
	//! \~russian Клавиши, передаваемые как несимвольные коды.
	enum SpecialKey {
		Tab        = 0x09 /** \~english Tab key \~russian Клавиша Tab */,
		Return     = 0x0a /** \~english Enter key \~russian Клавиша Enter */,
		Esc        = 0x1b /** \~english Escape key \~russian Клавиша Escape */,
		Space      = 0x20 /** \~english Space key \~russian Клавиша пробела */,
		Backspace  = 0x7f /** \~english Backspace key \~russian Клавиша Backspace */,
		UpArrow    = -1 /** \~english Up arrow key \~russian Стрелка вверх */,
		DownArrow  = -2 /** \~english Down arrow key \~russian Стрелка вниз */,
		RightArrow = -3 /** \~english Right arrow key \~russian Стрелка вправо */,
		LeftArrow  = -4 /** \~english Left arrow key \~russian Стрелка влево */,
		Home       = -5 /** \~english Home key \~russian Клавиша Home */,
		End        = -6 /** \~english End key \~russian Клавиша End */,
		PageUp     = -7 /** \~english Page Up key \~russian Клавиша Page Up */,
		PageDown   = -8 /** \~english Page Down key \~russian Клавиша Page Down */,
		Insert     = -9 /** \~english Insert key \~russian Клавиша Insert */,
		Delete     = -10 /** \~english Delete key \~russian Клавиша Delete */,
		F1         = -11 /** \~english F1 key \~russian Клавиша F1 */,
		F2         = -12 /** \~english F2 key \~russian Клавиша F2 */,
		F3         = -13 /** \~english F3 key \~russian Клавиша F3 */,
		F4         = -14 /** \~english F4 key \~russian Клавиша F4 */,
		F5         = -15 /** \~english F5 key \~russian Клавиша F5 */,
		F6         = -16 /** \~english F6 key \~russian Клавиша F6 */,
		F7         = -17 /** \~english F7 key \~russian Клавиша F7 */,
		F8         = -18 /** \~english F8 key \~russian Клавиша F8 */,
		F9         = -19 /** \~english F9 key \~russian Клавиша F9 */,
		F10        = -20 /** \~english F10 key \~russian Клавиша F10 */,
		F11        = -21 /** \~english F11 key \~russian Клавиша F11 */,
		F12        = -22 /** \~english F12 key \~russian Клавиша F12 */
	};

	//! \~english Keyboard modifier bit flags.
	//! \~russian Битовые флаги модификаторов клавиатуры.
	enum KeyModifier {
		Ctrl  = 0x1 /** \~english Control key \~russian Клавиша Control */,
		Shift = 0x2 /** \~english Shift key \~russian Клавиша Shift */,
		Alt   = 0x4 /** \~english Alt key \~russian Клавиша Alt */,
		// Meta = 0x8 /** Meta (windows) key */
	};

	//! \~english Combination of \a KeyModifier flags.
	//! \~russian Комбинация флагов \a KeyModifier.
	typedef PIFlags<KeyModifier> KeyModifiers;

	//! \~english Information about one keyboard event.
	//! \~russian Информация об одном событии клавиатуры.
	struct PIP_EXPORT KeyEvent {
		//! \~english Constructs an empty event or initializes it with key and modifiers.
		//! \~russian Создает пустое событие или инициализирует его клавишей и модификаторами.
		KeyEvent(int k = 0, KeyModifiers m = 0) {
			key       = k;
			modifiers = m;
		}

		//! \~english Pressed key code. It can be a character code or one of \a SpecialKey values.
		//! \~russian Код нажатой клавиши. Это может быть код символа или одно из значений \a SpecialKey.
		int key;

		//! \~english Active keyboard modifiers as a combination of \a KeyModifier flags.
		//! \~russian Активные модификаторы клавиатуры как комбинация флагов \a KeyModifier.
		KeyModifiers modifiers;
	};

	//! \~english Mouse button bit flags.
	//! \~russian Битовые флаги кнопок мыши.
	enum MouseButton {
		MouseLeft   = 0x01 /** \~english Left button \~russian Левая кнопка */,
		MouseRight  = 0x02 /** \~english Right button \~russian Правая кнопка */,
		MouseMiddle = 0x04 /** \~english Middle button \~russian Средняя кнопка */
	};

	//! \~english Mouse action kind.
	//! \~russian Вид действия мыши.
	enum MouseAction {
		MouseButtonPress /** \~english Mouse button pressed \~russian Нажатие кнопки мыши */,
		MouseButtonRelease /** \~english Mouse button released \~russian Отпускание кнопки мыши */,
		MouseButtonDblClick /** \~english Mouse button double-click \~russian Двойной щелчок кнопкой мыши */,
		MouseMove /** \~english Mouse moved \~russian Перемещение мыши */,
		MouseWheel /** \~english Mouse wheel rotated \~russian Прокрутка колеса мыши */
	};

	//! \~english Combination of pressed \a MouseButton flags.
	//! \~russian Комбинация нажатых флагов \a MouseButton.
	typedef PIFlags<MouseButton> MouseButtons;

	//! \~english Information about one mouse event.
	//! \~russian Информация об одном событии мыши.
	struct PIP_EXPORT MouseEvent {
		//! \~english Constructs an event with coordinates at the origin.
		//! \~russian Создает событие с координатами в начале области.
		MouseEvent(MouseAction a = MouseButtonPress, MouseButtons b = 0, KeyModifiers m = 0) {
			x = y     = 0;
			action    = a;
			buttons   = b;
			modifiers = m;
		}

		//! \~english Event X coordinate in screen space, starting from zero.
		//! \~russian Координата X события в экранном пространстве, начиная с нуля.
		int x;

		//! \~english Event Y coordinate in screen space, starting from zero.
		//! \~russian Координата Y события в экранном пространстве, начиная с нуля.
		int y;

		//! \~english Mouse action kind.
		//! \~russian Вид действия мыши.
		MouseAction action;

		//! \~english Pressed mouse buttons as a combination of \a MouseButton flags.
		//! \~russian Нажатые кнопки мыши как комбинация флагов \a MouseButton.
		MouseButtons buttons;

		//! \~english Active keyboard modifiers as a combination of \a KeyModifier flags.
		//! \~russian Активные модификаторы клавиатуры как комбинация флагов \a KeyModifier.
		KeyModifiers modifiers;
	};

	//! \~english Information about one mouse wheel event.
	//! \~russian Информация об одном событии колеса мыши.
	struct PIP_EXPORT WheelEvent: public MouseEvent {
		//! \~english Constructs a wheel event with downward direction by default.
		//! \~russian Создает событие колеса мыши; по умолчанию направление вниз.
		WheelEvent(): MouseEvent() { direction = false; }

		//! \~english Wheel direction: \b true for up, \b false for down.
		//! \~russian Направление прокрутки: \b true вверх, \b false вниз.
		bool direction;
	};

	//! \~english Callback receiving a key event and user data.
	//! \~russian Обратный вызов, принимающий событие клавиши и пользовательские данные.
	typedef std::function<void(KeyEvent, void *)> KBFunc;

	//! \~english Constructs a listener with optional callback, user data, and auto-start mode.
	//! \~russian Создает слушатель с необязательным обратным вызовом, пользовательскими данными и автозапуском.
	explicit PIKbdListener(KBFunc slot = 0, void * data = 0, bool startNow = true);

	//! \~english Stops the listener and restores the console state.
	//! \~russian Останавливает слушатель и восстанавливает состояние консоли.
	~PIKbdListener();


	//! \~english Returns the user data passed back with callbacks and events.
	//! \~russian Возвращает пользовательские данные, передаваемые обратно в обратные вызовы и события.
	void * data() { return kbddata_; }

	//! \~english Sets the user data passed back with callbacks and events.
	//! \~russian Задает пользовательские данные, возвращаемые в обратных вызовах и событиях.
	void setData(void * _data) { kbddata_ = _data; }

	//! \~english Sets the callback receiving both key event and user data.
	//! \~russian Устанавливает обратный вызов, получающий событие клавиши и пользовательские данные.
	void setSlot(KBFunc slot) { ret_func = std::move(slot); }

	//! \~english Sets the callback that only receives the key event and ignores user data.
	//! \~russian Устанавливает обратный вызов, получающий только событие клавиши и игнорирующий пользовательские данные.
	void setSlot(std::function<void(KeyEvent)> slot) {
		ret_func = [slot](KeyEvent e, void *) { slot(e); };
	}

	//! \~english Returns whether the exit key is currently being captured.
	//! \~russian Возвращает, включен ли сейчас перехват клавиши выхода.
	bool exitCaptured() const { return exit_enabled; }

	//! \~english Returns the configured exit key. The default is \c 'Q'.
	//! \~russian Возвращает настроенную клавишу выхода. По умолчанию это \c 'Q'.
	int exitKey() const { return exit_key; }

	//! \~english Returns the double-click interval in milliseconds.
	//! \~russian Возвращает интервал двойного щелчка в миллисекундах.
	double doubleClickInterval() const { return dbl_interval; }

	//! \~english Sets the mouse double-click interval in milliseconds.
	//! \~russian Задает интервал двойного щелчка мыши в миллисекундах.
	void setDoubleClickInterval(double v) { dbl_interval = v; }

	//! \~english Performs one low-level polling cycle and dispatches decoded input events.
	//! \~russian Выполняет один цикл низкоуровневого опроса и отправляет декодированные события ввода.
	void readKeyboard();

	//! \~english Requests listener shutdown and interrupts a pending wait for console input.
	//! \~russian Запрашивает остановку слушателя и прерывает текущее ожидание консольного ввода.
	void stop();

	//! \~english Requests shutdown and waits until console capture is restored or the timeout expires.
	//! \~russian Запрашивает остановку и ждет восстановления режима консоли до истечения таймаута.
	bool stopAndWait(PISystemTime timeout = {});

	//! \~english Returns whether low-level console capture is currently enabled.
	//! \~russian Возвращает, включен ли сейчас низкоуровневый захват консольного ввода.
	bool isActive() { return is_active; }


	//! \handlers
	//! \{

	//! \fn void enableExitCapture(int key = 'Q')
	//! \~english Enables capture of exit key \a key.
	//! \~russian Включает перехват клавиши выхода \a key.
	EVENT_HANDLER(void, enableExitCapture) { enableExitCapture('Q'); }
	EVENT_HANDLER1(void, enableExitCapture, int, key) {
		exit_enabled = true;
		exit_key     = key;
	}

	//! \fn void disableExitCapture()
	//! \~english Disables exit key capture.
	//! \~russian Выключает перехват клавиши выхода.
	EVENT_HANDLER(void, disableExitCapture) { exit_enabled = false; }

	//! \fn void setActive(bool yes = true)
	//! \~english Enables or disables low-level console input capture.
	//! \~russian Включает или выключает низкоуровневый захват консольного ввода.
	EVENT_HANDLER(void, setActive) { setActive(true); }
	EVENT_HANDLER1(void, setActive, bool, yes);

	//! \}
	//! \events
	//! \{

	//! \fn void keyPressed(PIKbdListener::KeyEvent key, void * data)
	//! \~english Raised when a key event is decoded. \a data is the user data pointer.
	//! \~russian Вызывается, когда декодировано событие клавиши. \a data содержит указатель на пользовательские данные.
	EVENT2(keyPressed, PIKbdListener::KeyEvent, key, void *, data);

	//! \fn void mouseEvent(PIKbdListener::MouseEvent mouse, void * data)
	//! \~english Raised when a mouse button or move event is decoded. \a data is the user data pointer.
	//! \~russian Вызывается, когда декодировано событие кнопки мыши или перемещения. \a data содержит указатель на пользовательские данные.
	EVENT2(mouseEvent, PIKbdListener::MouseEvent, mouse, void *, data);

	//! \fn void wheelEvent(PIKbdListener::WheelEvent wheel, void * data)
	//! \~english Raised when a mouse wheel event is decoded. \a data is the user data pointer.
	//! \~russian Вызывается, когда декодировано событие колеса мыши. \a data содержит указатель на пользовательские данные.
	EVENT2(wheelEvent, PIKbdListener::WheelEvent, wheel, void *, data);

	//! \}


	//! \~english Returns the listener instance currently registered by the console subsystem.
	//! \~russian Возвращает экземпляр слушателя, который сейчас зарегистрирован консольной подсистемой.
	static PIKbdListener * instance() { return _object; }

	//! \~english Becomes \b true after the configured exit key is captured.
	//! \~russian Становится \b true после перехвата настроенной клавиши выхода.
	static bool exiting;

private:
	void begin() override;
	void run() override { readKeyboard(); }
	void end() override;

#ifndef WINDOWS
	struct PIP_EXPORT EscSeq {
		const char * seq;
		int key;
		int mod;
		int vt;
		int flags;
		// 1 - shift
		// 2 - alt
		// 4 - ctrl
	};

	enum VTType {
		vt_none,
		vt_xterm = 0x1,
		vt_linux = 0x2,
		vt_all   = 0xFF
	};

	static const EscSeq esc_seq[];
#endif

	PRIVATE_DECLARATION(PIP_EXPORT)
	KBFunc ret_func;
	int exit_key;
	bool exit_enabled = false, is_active = true;
	void * kbddata_;
	double dbl_interval;
	PITimeMeasurer tm_dbl;
	KeyEvent ke;
	MouseEvent me, prev_me, prev_p_me;
	WheelEvent we;
	static PIKbdListener * _object;
};


//! \relatesalso PIBinaryStream
//! \~english Store operator for MouseEvent.
//! \~russian Оператор сохранения для MouseEvent.
BINARY_STREAM_WRITE(PIKbdListener::MouseEvent) {
	s << v.x << v.y << v.action << v.buttons << v.modifiers;
	return s;
}

//! \relatesalso PIBinaryStream
//! \~english Restore operator for MouseEvent.
//! \~russian Оператор извлечения для MouseEvent.
BINARY_STREAM_READ(PIKbdListener::MouseEvent) {
	s >> v.x >> v.y >> v.action >> v.buttons >> v.modifiers;
	return s;
}


//! \relatesalso PIBinaryStream
//! \~english Store operator for WheelEvent.
//! \~russian Оператор сохранения для WheelEvent.
BINARY_STREAM_WRITE(PIKbdListener::WheelEvent) {
	s << (*(PIKbdListener::MouseEvent *)&v) << v.direction;
	return s;
}

//! \relatesalso PIBinaryStream
//! \~english Restore operator for WheelEvent.
//! \~russian Оператор извлечения для WheelEvent.
BINARY_STREAM_READ(PIKbdListener::WheelEvent) {
	s >> (*(PIKbdListener::MouseEvent *)&v) >> v.direction;
	return s;
}


REGISTER_PIVARIANTSIMPLE(PIKbdListener::KeyEvent)
REGISTER_PIVARIANTSIMPLE(PIKbdListener::MouseEvent)
REGISTER_PIVARIANTSIMPLE(PIKbdListener::WheelEvent)

#endif // PIKBDLISTENER_H
