/*
 *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifndef __KIS_SHORTCUT_MATCHER_H
#define __KIS_SHORTCUT_MATCHER_H


#include <QList>
#include "kis_single_action_shortcut.h"
#include "KisInputActionGroup.h"
#include <functional>

class QEvent;
class QWheelEvent;
class QTouchEvent;
class QNativeGestureEvent;
class QString;
class QPointF;

class KisStrokeShortcut;
class KisTouchShortcut;
class KisNativeGestureShortcut;


/**
 * The class that manages connections between shortcuts and actions.
 *
 * It processes input events and generates state transitions for the
 * actions basing on the data, represented by the shortcuts.
 *
 * The class works with two types of actions: long running
 * (represented by KisStrokeShortcuts) and "atomic"
 * (KisSingleActionShortcut). The former one involves some long
 * interaction with the user by means of a mouse cursor or a tablet,
 * the latter one simple action like "Zoom 100%" or "Reset Rotation".
 *
 * The single action shortcuts are handled quite easily. The matcher
 * listens to the events coming, manages two lists of the pressed keys
 * and buttons and when their content corresponds to some single
 * action shortcut it just runs this shortcut once.
 *
 * The strategy for handling the stroke shortcuts is a bit more
 * complex.  Each such action may be in one of the three states:
 *
 * Idle <-> Ready <-> Running
 *
 * In "Idle" state the action is completely inactive and has no access
 * to the user
 *
 * When the action is in "Ready" state, it means that all the
 * modifiers for the action are already pressed and we are only
 * waiting for a user to press the mouse button and start a stroke. In
 * this state the action can show the user its Cursor to notify the user
 * what is going to happen next.
 *
 * In the "Running" state, the action has full access to the user
 * input and is considered to perform all the work it was created for.
 *
 * To implement such state transitions for the actions,
 * KisShortcutMatcher first forms a list of the actions which can be
 * moved to a ready state (m_d->readyShortcuts), then chooses the one
 * with the highest priority to be the only shortcut in the "Ready"
 * state and activates it (m_d->readyShortcut).  Then when the user
 * presses the mouse button, the matcher looks through the list of
 * ready shortcuts, chooses which will be running now, deactivates (if
 * needed) currently activated action and starts the chosen one.
 *
 * \see KisSingleActionShortcut
 * \see KisStrokeShortcut
 */
class KRITAUI_EXPORT KisShortcutMatcher
{
public:
    KisShortcutMatcher();
    ~KisShortcutMatcher();

    bool hasRunningShortcut() const;

    void addShortcut(KisSingleActionShortcut *shortcut);
    void addShortcut(KisStrokeShortcut *shortcut);
    void addShortcut(KisTouchShortcut *shortcut);
    void addShortcut(KisNativeGestureShortcut *shortcut);

    /**
     * Returns true if the currently running shortcut supports
     * processing hi resolution flow of events from the tablet
     * device. In most of the cases (except of the painting itself)
     * too many events make the execution of the action too slow, so
     * the action can decide whether it needs it.
     */
    bool supportsHiResInputEvents();

    /**
     * Handles a key press event.
     * No autorepeat events should be passed to this method.
     *
     * \return whether the event has been handled successfully and
     * should be eaten by the events filter
     */
    bool keyPressed(Qt::Key key);

    /**
     * Handles a key press event that has been generated by the
     * autorepeat.
     *
     * \return whether the event has been handled successfully and
     * should be eaten by the events filter
     */
    bool autoRepeatedKeyPressed(Qt::Key key);

    /**
     * Handles a key release event.
     * No autorepeat events should be passed to this method.
     *
     * \return whether the event has been handled successfully and
     * should be eaten by the events filter
     */
    bool keyReleased(Qt::Key key);

    /**
     * Handles button presses from a tablet or mouse.
     *
     * \param event the event that caused this call.
     * Must be of type QTabletEvent or QMouseEvent.
     *
     * \return whether the event has been handled successfully and
     * should be eaten by the events filter
     */
    bool buttonPressed(Qt::MouseButton button, QEvent *event);

    /**
     * Handles the mouse button release event
     *
     * \param event the event that caused this call.
     * Must be of type QTabletEvent or QMouseEvent.
     *
     * \return whether the event has been handled successfully and
     * should be eaten by the events filter
     */
    bool buttonReleased(Qt::MouseButton button, QEvent *event);

    /**
     * Handles the mouse wheel event
     *
     * \return whether the event has been handled successfully and
     * should be eaten by the events filter
     */
    bool wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event);

    /**
     * Handles tablet and mouse move events.
     *
     * \param event the event that caused this call
     *
     * \return whether the event has been handled successfully and
     * should be eaten by the events filter
     */
    bool pointerMoved(QEvent *event);

    /**
     * Handle cursor's Enter event.
     * We never eat it because it might be used by someone else
     */
    void enterEvent();

    /**
     * Handle cursor's Leave event.
     * We never eat it because it might be used by someone else
     */
    void leaveEvent();

    bool touchBeginEvent(QTouchEvent *event);
    bool touchUpdateEvent(QTouchEvent *event);
    bool touchEndEvent(QTouchEvent *event);


    bool nativeGestureBeginEvent(QNativeGestureEvent *event);
    bool nativeGestureEvent(QNativeGestureEvent *event);
    bool nativeGestureEndEvent(QNativeGestureEvent *event);

    /**
     * Resets the internal state of the matcher and activates the
     * prepared action if possible.
     *
     * This should be done when the window has lost the focus for
     * some time, so that several events could be lost
     */
    void reinitialize();

    /**
     * Resets the internal state of the matcher, tries to resync it to the state
     * passed via argument and activates the prepared action if possible.
     *
     * This synchronization should happen when the user hovers Krita windows,
     * **without** having keyboard focus set to it (therefore matcher cannot
     * get key press and release events).
     */
    void recoveryModifiersWithoutFocus(const QVector<Qt::Key> &keys);

    /**
     * Kirta lost focus, it means that all the running actions should be ended
     * forcefully.
     */
    void lostFocusEvent(const QPointF &localPos);

    /**
     * Disables the start of any actions.
     *
     * WARNING: the actions that has been started before this call
     * will *not* be ended. They will be ended in their usual way,
     * when the mouse button will be released.
     */
    void suppressAllActions(bool value);

    /**
     * Remove all shortcuts that have been registered.
     */
    void clearShortcuts();

    void setInputActionGroupsMaskCallback(std::function<KisInputActionGroupsMask()> func);

private:
    friend class KisInputManagerTest;

    void reset();
    void reset(QString msg);

    bool tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event);
    template<typename T, typename U> bool tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState);

    void prepareReadyShortcuts();

    bool tryRunReadyShortcut( Qt::MouseButton button, QEvent* event );
    void tryActivateReadyShortcut();
    bool tryEndRunningShortcut( Qt::MouseButton button, QEvent* event );
    void forceEndRunningShortcut(const QPointF &localPos);
    void forceDeactivateAllActions();

    bool tryRunTouchShortcut(QTouchEvent *event);
    bool tryEndTouchShortcut(QTouchEvent *event);

    bool tryRunNativeGestureShortcut(QNativeGestureEvent *event);
    bool tryEndNativeGestureShortcut(QNativeGestureEvent *event);

private:
    class Private;
    Private * const m_d;
};

#endif /* __KIS_SHORTCUT_MATCHER_H */
