#pragma once

#if defined(WITH_QT5) || defined(DOXYGEN_GENERATING_OUTPUT)

#include <qevent.h>
#include "../Input/IInputManager.h"

#if defined(WITH_QT5GAMEPAD)
class QGamepadManager;
class QGamepad;
#endif

class QKeyEvent;
class QMouseEvent;
class QTouchEvent;
class QWheelEvent;

namespace nCine::Backends
{
	class Qt5Widget;

	/**
		@brief Utility functions to convert between engine key enumerations and Qt5 ones
	*/
	class Qt5Keys
	{
	public:
		/** @brief Maps a Qt5 key symbol to the matching engine @ref Keys value */
		static Keys keySymValueToEnum(int keysym);
		/** @brief Maps Qt5 keyboard modifier flags to the engine modifier mask */
		static int keyModMaskToEnumMask(Qt::KeyboardModifiers keymod);
	};

	/**
		@brief Information about Qt5 mouse state
		
		Wraps the `Qt::MouseButtons` flags last reported by the widget.
	*/
	class Qt5MouseState : public MouseState
	{
	public:
		Qt5MouseState()
			: buttons_(Qt::NoButton) {}

		inline bool isLeftButtonDown() const override {
			return buttons_ & Qt::LeftButton;
		}
		inline bool isMiddleButtonDown() const override {
			return buttons_ & Qt::MiddleButton;
		}
		inline bool isRightButtonDown() const override {
			return buttons_ & Qt::RightButton;
		}
		inline bool isFourthButtonDown() const override {
			return buttons_ & Qt::BackButton;
		}
		inline bool isFifthButtonDown() const override {
			return buttons_ & Qt::ForwardButton;
		}

	private:
		Qt::MouseButtons buttons_;

		friend class Qt5InputManager;
	};

	/**
		@brief Information about a Qt5 mouse event
		
		Wraps the single `Qt::MouseButton` that triggered the event.
	*/
	class Qt5MouseEvent : public MouseEvent
	{
	public:
		Qt5MouseEvent()
			: button_(Qt::NoButton) {}

		inline bool isLeftButton() const override {
			return button_ & Qt::LeftButton;
		}
		inline bool isMiddleButton() const override {
			return button_ & Qt::MiddleButton;
		}
		inline bool isRightButton() const override {
			return button_ & Qt::RightButton;
		}
		inline bool isFourthButton() const override {
			return button_ & Qt::BackButton;
		}
		inline bool isFifthButton() const override {
			return button_ & Qt::ForwardButton;
		}

	private:
		Qt::MouseButton button_;

		friend class Qt5InputManager;
	};

	/**
		@brief Information about a Qt5 scroll event
	*/
	class Qt5ScrollEvent : public ScrollEvent
	{
	public:
		Qt5ScrollEvent() {}

		friend class Qt5InputManager;
	};

	/**
		@brief Simulated information about Qt5 keyboard state
		
		Qt5 delivers key press and release events rather than exposing a pollable
		keyboard array, so the down state of every key is tracked in a simulated
		array updated from those events.
	*/
	class Qt5KeyboardState : public KeyboardState
	{
	public:
		Qt5KeyboardState()
		{
			for (unsigned int i = 0; i < NumKeys; i++)
				keys_[i] = 0;
		}

		inline bool isKeyDown(Keys key) const override
		{
			if (key == Keys::Unknown)
				return false;
			else
				return keys_[static_cast<unsigned int>(key)] != 0;
		}

	private:
		static const unsigned int NumKeys = static_cast<unsigned int>(Keys::COUNT);
		unsigned char keys_[NumKeys];

		friend class Qt5InputManager;
	};

	/**
		@brief Information about Qt5 joystick state
		
		Backed by a `QGamepad` when @ref WITH_QT5GAMEPAD is enabled; otherwise a
		no-op stub that always reports no input.
	*/
#ifdef WITH_QT5GAMEPAD
	class Qt5JoystickState : public JoystickState
	{
	public:
		Qt5JoystickState();

		bool isButtonPressed(int buttonId) const override;
		unsigned char hatState(int hatId) const override;
		float axisValue(int axisId) const override;

	private:
		static const unsigned int MaxNameLength = 256;
		static const unsigned int NumButtons = 12;
		static const unsigned int NumAxes = 6;
		/** @brief Minimum difference between two axis readings in order to trigger an event */
		static const float AxisEventTolerance;
		bool buttonState_[NumButtons];
		unsigned char hatState_;
		float axesValuesState_[NumAxes];

		char name_[MaxNameLength];
		std::unique_ptr<QGamepad> gamepad_;

		friend class Qt5InputManager;
	};
#else
	class Qt5JoystickState : public JoystickState
	{
	public:
		Qt5JoystickState() {}

		inline bool isButtonPressed(int buttonId) const override {
			return false;
		}
		inline unsigned char hatState(int hatId) const override {
			return HatState::CENTERED;
		}
		inline float axisValue(int axisId) const override {
			return 0.0f;
		}

	private:
		friend class Qt5InputManager;
	};
#endif

	/**
		@brief Parses and dispatches Qt5 input events
		
		Translates the Qt5 events forwarded by @ref Qt5Widget into engine input
		state and events. Joystick support is provided through Qt5Gamepad when
		@ref WITH_QT5GAMEPAD is enabled.
	*/
	class Qt5InputManager : public IInputManager
	{
	public:
		/** @brief The constructor takes care of opening available joysticks */
		Qt5InputManager(Qt5Widget& widget);
		/** @brief The destructor releases every opened joystick */
		~Qt5InputManager();

#ifdef WITH_QT5GAMEPAD
		/** @brief Polls every connected gamepad and emits state-change events */
		void updateJoystickStates();
#endif

		/** @brief Returns `true` if the application should quit on a close request */
		bool shouldQuitOnRequest();
		/** @brief Handles a generic Qt5 event, returning `true` if it was consumed */
		bool event(QEvent* event);
		/** @brief Handles a Qt5 key press event */
		void keyPressEvent(QKeyEvent* event);
		/** @brief Handles a Qt5 key release event */
		void keyReleaseEvent(QKeyEvent* event);
		/** @brief Handles a Qt5 mouse button press event */
		void mousePressEvent(QMouseEvent* event);
		/** @brief Handles a Qt5 mouse button release event */
		void mouseReleaseEvent(QMouseEvent* event);
		/** @brief Handles a Qt5 mouse move event */
		void mouseMoveEvent(QMouseEvent* event);
		/** @brief Handles the start of a Qt5 touch sequence */
		void touchBeginEvent(QTouchEvent* event);
		/** @brief Handles an update within a Qt5 touch sequence */
		void touchUpdateEvent(QTouchEvent* event);
		/** @brief Handles the end of a Qt5 touch sequence */
		void touchEndEvent(QTouchEvent* event);
		/** @brief Handles a Qt5 mouse wheel event */
		void wheelEvent(QWheelEvent* event);

		inline const MouseState& mouseState() const override {
			return mouseState_;
		}
		inline const KeyboardState& keyboardState() const override {
			return keyboardState_;
		}

#ifdef WITH_QT5GAMEPAD
		bool isJoyPresent(int joyId) const override;
		const char* joyName(int joyId) const override;
		const JoystickGuid joyGuid(int joyId) const override {
			return { };
		}
		inline int joyNumButtons(int joyId) const override {
			return Qt5JoystickState::NumButtons;
		}
		inline int joyNumHats(int joyId) const override {
			return 1;
		}
		inline int joyNumAxes(int joyId) const override {
			return Qt5JoystickState::NumAxes;
		}
		const JoystickState& joystickState(int joyId) const override;
#else
		inline bool isJoyPresent(int joyId) const override {
			return false;
		}
		inline const char* joyName(int joyId) const override {
			return nullptr;
		}
		inline const JoystickGuid joyGuid(int joyId) const override {
			return { };
		}
		inline int joyNumButtons(int joyId) const override {
			return 0;
		}
		inline int joyNumHats(int joyId) const override {
			return 0;
		}
		inline int joyNumAxes(int joyId) const override {
			return 0;
		}
		inline const JoystickState& joystickState(int joyId) const override {
			return nullJoystickState_;
		}
#endif

		void setCursor(Cursor cursor) override;

	private:
		static const int MaxNumJoysticks = 4;

		static TouchEvent touchEvent_;
		static Qt5MouseState mouseState_;
		static Qt5MouseEvent mouseEvent_;
		static Qt5ScrollEvent scrollEvent_;
		static Qt5KeyboardState keyboardState_;
		static KeyboardEvent keyboardEvent_;
		static TextInputEvent textInputEvent_;
		static Qt5JoystickState nullJoystickState_;
#ifdef WITH_QT5GAMEPAD
		static Qt5JoystickState joystickStates_[MaxNumJoysticks];
		static JoyButtonEvent joyButtonEvent_;
		static JoyHatEvent joyHatEvent_;
		static JoyAxisEvent joyAxisEvent_;
		static JoyConnectionEvent joyConnectionEvent_;
#endif

		Qt5Widget& widget_;

		void updateTouchEvent(const QTouchEvent* event);

		/** @brief Deleted copy constructor */
		Qt5InputManager(const Qt5InputManager&) = delete;
		/** @brief Deleted assignment operator */
		Qt5InputManager& operator=(const Qt5InputManager&) = delete;
	};

}

#endif
