#ifndef LDNDMANAGER_H
#define LDNDMANAGER_H

#include <LPointerButtonEvent.h>
#include <LFactoryObject.h>
#include <memory>

/**
 * @brief Class for handling drag & drop sessions.
 *
 * @anchor ldnd_detailed
 *
 * The LDND class provides control over drag & drop sessions, and its unique instance can be accessed via LSeat::dnd().\n
 * Clients initiate a DND session by triggering startDragRequest(). Within that method, the compositor analyzes
 * the triggeringEvent(), which could be, for example, a pointer button press on the origin() surface,
 * and decides whether to accept the request or not. If the request is not accepted, the cancel() method should be called to end the session.
 *
 * If the session continues, the compositor notifies clients when the dragged data is over their surfaces
 * through the setFocus() and sendMoveEvent() methods, and finally notifies them when it's dropped with drop().\n
 * Clients usually create an LDNDIconRole (icon()) to be displayed behind the cursor or touch point.
 *
 * @note Louvre internally acts as middleware to coordinate the data exchange between clients.
 *
 * During the session, clients can negotiate different actions such as @ref Move or @ref Copy .\n
 * The compositor can also set its preferred action with setPreferredAction().
 */
class Louvre::LDND : public LFactoryObject
{
public:

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

    /**
     * @brief Action flags for drag & drop sessions.
     *
     * Clients who start drag & drop sessions or receive a data offer notify which actions they support.\n
     * For example, when dragging a file from a file manager window into another, the desired action might be to
     * move or copy the file.\n
     * Louvre by default performs a match of actions supported by the source and destination client, giving preference
     * to the first one listed in the enum, except for @ref NoAction .\n
     * The compositor can also notify its preferred action using the setPreferredAction() method as exemplified in the
     * default implementation of LKeyboard::keyEvent().
     */
    enum Action : UInt32
    {
        /// No preferred action
        NoAction = 0,

        /// The preferred action is to copy
        Copy = 1,

        /// The preferred action is to move
        Move = 2,

        /// The destination client asks the source client the preferred action
        Ask = 4
    };

    /**
     * @brief Constructor of the LDND class.
     *
     * @param params Internal parameters provided in LCompositor::createObjectRequest().
     */
    LDND(const void *params) noexcept;

    LCLASS_NO_COPY(LDND)

    /**
     * @brief Destructor of the LDND class.
     *
     * Invoked after LCompositor::onAnticipatedObjectDestruction().
     */
    ~LDND() { notifyDestruction(); };

    /**
     * @brief Drag & drop session icon.
     *
     * LDNDIconRole of the surface used as drag & drop icon.
     *
     * @note Not all drag & drop sessions use an icon.
     *
     * @returns `nullptr` if there is no session going on, or if the source client did not assign an icon.
     */
    LDNDIconRole *icon() const noexcept;

    /**
     * @brief Surface from which the drag & drop session originates.
     *
     * @returns `nullptr` if there is no active session.
     */
    LSurface *origin() const noexcept;

    /**
     * @brief Focused surface.
     *
     * Surface to which the data offer is being presented to.
     *
     * @returns `nullptr` if there is no active session or if no surface has DND focus.
     */
    LSurface *focus() const noexcept;

    /**
     * @brief Set DND focus.
     *
     * Notifies the client that the dragged data is over one of their surfaces.
     *
     * @note If there is no active session, this is a no-op.
     *
     * @param surface The surface to which the data is being offered, or `nullptr` to remove focus.
     * @param localPos The local position within the surface where the drag point is located.
     */
    void setFocus(LSurface *surface, const LPointF &localPos) noexcept;

    /**
     * @brief Send a DND move event.
     *
     * Notifies the focused surface that the dragged point has moved.\n
     *
     * @note If no surface is focused, or there is no active session, this is a no-op.
     *
     * @param localPos The local position within the surface where the drag point is located.
     * @param ms The millisecond timestamp of the move event.
     */
    void sendMoveEvent(const LPointF &localPos, UInt32 ms) noexcept;

    /**
     * @brief Indicates which input event triggered the drag & drop session.
     *
     * This should be used, for example, to detect if the dragged element should follow the pointer
     * or a touch point.
     *
     * @note If the event was generated by an event like LKeyboardEnterEvent, the default implementation treats it as a pointer session.
     *
     * @return The input event that triggered the drag & drop session.
     */
    const LEvent &triggeringEvent() const noexcept;

    /**
     * @brief Check if a drag & drop session is currently in progress.
     *
     * @return `true` if there is an ongoing drag & drop session, `false` otherwise.
     */
    bool dragging() const noexcept;

    /**
     * @brief Cancels the session.
     *
     * Cancels the current drag & drop session.\n
     * If there is no session going on, calling this method is a no-op.
     */
    void cancel() noexcept;

    /**
     * @brief Drop the data offer.
     *
     * Drop the data offer on the surface with active focus and ends the session.\n
     * The destination client then exchanges the information with the source client, using the specified action.\n
     * If there is no focused surface, the session is cancelled.
     */
    void drop() noexcept;

    /**
     * @brief Preferred action of the compositor.
     *
     * Returns the preferred actions of the compositor set whith setPreferredAction().
     */
    Action preferredAction() const noexcept;

    /**
     * @brief Current action.
     */
    Action action() const noexcept;

    /**
     * @brief Assigns the preferred action.
     *
     * Assigns the compositor's preferred action for the session.
     * Louvre by default assigns the @ref Move action when holding down the `Shift`
     * key and the @ref Copy action when holding down the `Ctrl` key
     * (check the default implementation of LKeyboard::keyEvent()).\n
     * If the specified action is not supported by the source and destination client, calling this method is a no-op.
     */
    void setPreferredAction(Action action) noexcept;

    /// @name Virtual Methods
    /// @{

    /**
     * @brief Request to start a drag & drop session
     *
     * Override this virtual method if you want to be notified when a client wants to start a drag & drop session.\n
     * This method is invoked only when there is no session in progress, which means only a single session can be active
     * at a time.
     *
     * #### Default implementation
     * @snippet LDNDDefault.cpp startDragRequest
     */
    virtual void startDragRequest();

    /**
     * @brief Notifies the cancellation of a session
     *
     * Reimplement this virtual method if you want to be notified when a drag & drop session is cancelled.\n
     * The default implementation repaints outputs where the drag & drop icon was visible.
     *
     * #### Default implementation
     * @snippet LDNDDefault.cpp cancelled
     */
    virtual void cancelled();

    /**
     * @brief Triggered after drop()
     *
     * #### Default implementation
     * @snippet LDNDDefault.cpp dropped
     */
    virtual void dropped();

    /// @}
private:
    friend class Protocols::Wayland::RDataDevice;
    friend class LDNDSession;
    void sendLeaveEvent() noexcept;
    std::unique_ptr<LEvent> m_triggeringEvent { std::make_unique<LPointerButtonEvent>() };
    std::shared_ptr<LDNDSession> m_session;
    UInt32 m_compositorAction { NoAction };
};

#endif // LDNDMANAGER_H
