#ifndef LKEYBOARD_H
#define LKEYBOARD_H

#include <LFactoryObject.h>
#include <LKeyboardModifiersEvent.h>
#include <xkbcommon/xkbcommon.h>
#include <linux/input-event-codes.h>
#include <memory>
#include <vector>

/**
 * @brief Class for handling keyboard events.
 *
 * @anchor lkeyboard_detailed
 *
 * The LKeyboard class lets you handle keyboard events generated by the input backend,
 * redirect them to clients, and configure parameters such as the keyboard map and key repeat rate.\n
 * There is a single instance of LKeyboard, which can be accessed from LSeat::keyboard().
 *
 * ### Wayland Events
 *
 * To send keyboard events to clients, you must first assign focus to a surface using setFocus()
 * and then all subsequent keyboard events sent with sendKeyEvent() will be directed to that surface.
 */
class Louvre::LKeyboard : public LFactoryObject
{
public:

    static constexpr LFactoryObject::Type FactoryObjectType = LFactoryObject::Type::LKeyboard;

    /**
     * @brief Constructor of the LKeyboard class.
     *
     * There is a single instance of LKeyboard, which can be accessed from LSeat::keyboard().
     *
     * @param params Internal parameters provided in LCompositor::createObjectRequest().
     */
    LKeyboard(const void *params) noexcept;

    /**
     * @brief Destructor of the LKeyboard class.
     *
     * Invoked after LCompositor::onAnticipatedObjectDestruction().
     */
    ~LKeyboard();

    LCLASS_NO_COPY(LKeyboard)

    /**
     * @brief Set keyboard focus.
     *
     * This method assigns keyboard focus to the specified surface.
     * Only one surface can have keyboard focus at a time, and this method automatically removes focus from any previously focused surface.\n
     * To remove focus from all surfaces, pass `nullptr` as the argument.
     *
     * @note Calling this method while there is a surface grabbing the keyboard is a no-op.
     */
    void setFocus(LSurface *surface);

    /**
     * @brief Focused surface.
     *
     * @see setFocus().
     *
     * @returns The focused surface or `nullptr` if no surface has keyboard focus.
     */
    LSurface *focus() const noexcept;

    /**
     * @brief Sets a keyboard grab.
     *
     * Setting a keyboard grab prevents other surfaces from gain focus with setFocus().\n
     * If the surface grabbing the keyboard is destroyed the grab is automatically assigned to its parent or unset
     * if it has no parent.\n
     * For example, an LPopupRole can request to make a keyboard grab with LPopupRole::grabKeyboardRequest().
     *
     * If 'surface' is set to `nullptr`, the keyboard grab is unset.
     *
     * @param surface The surface that will grab the keyboard events or `nullptr` to disable it.
     */
    void setGrab(LSurface* surface);

    /**
     * @brief Gets the grab surface.
     *
     * @see setGrab()
     *
     * @returns The grabbing surface, or `nullptr` if unset.
     */
    LSurface *grab() const noexcept;

    /**
     * @brief Current keyboard modifiers state.
     */
    const LKeyboardModifiersEvent::Modifiers &modifiers() const noexcept;

    /**
     * @brief Keyboard map file descriptor.
     *
     * File descriptor of the XKB keyboard map assigned with setKeymap().
     */
    Int32 keymapFd() const noexcept;

    /**
     * @brief Size of the keyboard map in bytes.
     *
     * Size in bytes of the XKB keyboard map loaded with setKeymap().
     */
    Int32 keymapSize() const noexcept;

    /**
     * @brief Keymap format.
     *
     * The keymap format can be either `WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1`,
     * which indicates the presence of a valid XKB keymap set, or `WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP`,
     * indicating an issue with the XKB system configuration.
     */
    UInt32 keymapFormat() const noexcept;

    /**
     * @brief Repetition rate.
     *
     * Number of repetitions per second of a key when held down, assigned with setRepeatInfo().
     *
     * The default value is 32 repetitions per second.
     */
    Int32 repeatRate() const noexcept;

    /**
     * @brief Repetition delay.
     *
     * Delay in milliseconds before triggering the repetition of a key by holding it down, set with setRepeatInfo().
     *
     *
     * The default value is 500 ms.
     */
    Int32 repeatDelay() const noexcept;

    /**
     * @brief Set the key repeat rate and delay.
     *
     * Assigns the repeat rate and delay when holding down a key.
     *
     * @note This impacts how clients respond to a key being held down.
     *
     * @param rate Number of repetitions per second. 0 turns off repetition.
     * @param msDelay Delay in milliseconds before triggering the replay.
     */
    void setRepeatInfo(Int32 rate, Int32 msDelay) noexcept;

    /**
     * @brief Key symbol.
     *
     * Returns the symbol of a raw key code generated by the input backend according to the current keymap state.
     *
     * The complete list of symbols can be found in [`<xkbcommon/xkbcommon-keysyms.h>`](https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h).
     */
    xkb_keysym_t keySymbol(UInt32 keyCode) noexcept;

    /**
     * @brief Keymap state.
     *
     * XKB keyboard map state.
     */
    xkb_state *keymapState() const noexcept;

    /**
     * @brief Checks the state of a modifier.
     *
     * @param name Modifier name defined in `<xkbcommon/xkbcommon_names.h>` such as `XKB_MOD_NAME_SHIFT`, `XKB_LED_NAME_CAPS`, etc.
     * @param type In XKB, the `XKB_STATE_MODS_DEPRESSED`, `XKB_STATE_MODS_LATCHED`, `XKB_STATE_MODS_LOCKED`, and `XKB_STATE_MODS_EFFECTIVE` are flags used to represent different aspects of the keyboard modifier keys' states.
     *
     * **XKB_STATE_MODS_DEPRESSED:** This flag represents the currently depressed (pressed down) modifiers. When a modifier key is pressed, it falls into the depressed state. This flag helps identify which modifiers are currently held down.
     *
     * **XKB_STATE_MODS_LATCHED:** This flag represents modifiers that are latched. A latched modifier is one that will be activated with the next non-modifier key press. Once the latched modifier is used, it returns to its original state.
     *
     * **XKB_STATE_MODS_LOCKED:** This flag represents modifiers that are locked. When a modifier is locked, it stays active until explicitly unlocked. For example, if Caps Lock is activated, the Caps Lock state is locked until Caps Lock is pressed again.
     *
     * **XKB_STATE_MODS_EFFECTIVE:** This flag represents the effective state of the modifiers. It takes into account the depressed, latched, and locked states to provide a combined view of the current modifier key states.
     *
     * @returns `true` if the modifier is active and `false` otherwise.
     */
    bool isModActive(const char *name, xkb_state_component type) const noexcept;

    /**
     * @brief All currently pressed key codes.
     */
    const std::vector<UInt32> &pressedKeys() const noexcept;

    /**
     * @brief Checks if a key code is pressed.
     */
    bool isKeyCodePressed(UInt32 keyCode) const noexcept;

    /**
     * @brief Check if a key symbol is pressed.
     */
    bool isKeySymbolPressed(xkb_keysym_t keySymbol) const noexcept;

    /**
     * @brief Sets the keyboard map.
     *
     * This method finds and assigns a keyboard map that matches the parameters passed in the arguments.\n
     * The keyboard map is automatically sent to clients when they connect to the compositor and use the [wl_keyboard](https://wayland.app/protocols/wayland#wl_keyboard) interface
     * of the Wayland protocol. If a client is already connected, Louvre re-sends it the new keyboard map.\n
     *
     * @param rules Rules on how to interpret the other arguments. Can be `nullptr`. [More information](https://xkbcommon.org/doc/current/structxkb__rule__names.html#a0968f4602001f2306febd32c34bd2280).
     * @param model Keyboard model. Can be `nullptr`. [More information](https://xkbcommon.org/doc/current/structxkb__rule__names.html#a0968f4602001f2306febd32c34bd2280).
     * @param layout Keyboard layouts separated by comma (e.g. "latam"). Can be `nullptr`. [More information](https://xkbcommon.org/doc/current/structxkb__rule__names.html#a0968f4602001f2306febd32c34bd2280).
     * @param variant Variants of layouts. Can be `nullptr`. [More information](https://xkbcommon.org/doc/current/structxkb__rule__names.html#a0968f4602001f2306febd32c34bd2280).
     * @param options Preferences not related to layouts. Can be `nullptr`. [More information](https://xkbcommon.org/doc/current/structxkb__rule__names.html#a0968f4602001f2306febd32c34bd2280).
     */
    bool setKeymap(const char *rules = nullptr, const char *model = nullptr, const char *layout = nullptr, const char *variant = nullptr, const char *options = nullptr) noexcept;

    /**
     * @brief Sends a key event to the currently focused surface.
     *
     * @see setFocus() and setGrab().
     */
    void sendKeyEvent(const LKeyboardKeyEvent &event) noexcept;

    /**
     * @name Virtual Methods
     */

    ///@{

    /**
     * @brief Keyboard key event generated by the input backend.
     *
     * #### Default implementation
     * @snippet LKeyboardDefault.cpp keyEvent
     */
    virtual void keyEvent(const LKeyboardKeyEvent &event);

    /**
     * @brief Notifies when the focused surface changes.
     *
     * This method is triggered whenever the focus() property changes, either by
     * calling setFocus() or when the currently focused surface is destroyed.
     * 
     * @warning Changing the focused surface within this event using setFocus()
     *          may result in an infinite feedback loop if not managed carefully.
     * 
     * #### Default Implementation
     * @snippet LKeyboardDefault.cpp focusChanged
     */
    virtual void focusChanged();

    ///@}

    LPRIVATE_IMP_UNIQUE(LKeyboard)
};

#endif // LKEYBOARD_H
