/**
    05Robot - Eyetracker Module

    @author P.Kaczmarek,J.Tomczynski,T.Mankowski
    @version 1.0 2017
*/
#include <QObject>
#include <QVector>
#include <QPointF>
#include <QRectF>
#include <QEvent>
#include <QElapsedTimer>
#include <QAbstractButton>
#include "cxmlio.h"
#include "common.h"

/**
 * \defgroup EyetrackerModule Eyetracker module
 * \brief Contains routines for handing pointing devices such as eyetracker.
 *
 * Contains routines related to handing pointing devices such as eyetracker.
 *
 * The \ref cEyeTrackerFilter lets to analyze mouse Dires and it overrides default QObject::eventFilter, and thus it can be easily attached to any QWidget object receiving MouseEvent notification.
 *
 * It let to define and recognize a set of complex gestures performed by pointing devices. Each complex gestures consists of chain of atom gestures (\ref sAtomGesture::eGestureType). The complex gesture signatures chain and the behavior form are defined by cGestureDirectoryEntry.
 *
 * The following code presents using the module
 *
 * \code
 * QVector<cGestureDirectoryEntry> directory = {cGestureDirectoryEntry(0,"horizontal move left right",{sAtomGesture::H_LINE_LEFT,sAtomGesture::H_LINE_RIGHT},\
 *                cGestureDirectoryEntry::SAME_BUTTON_END, Qt::LeftButton, 'a', Qt::ControlModifier, 5000),\
 *      cGestureDirectoryEntry(1,"vertical down up",{sAtomGesture::V_LINE_DOWN,sAtomGesture::V_LINE_UP},\
 *                cGestureDirectoryEntry::EQUAL_START_END, Qt::LeftButton, 0, Qt::NoModifier, 5000),\
 *      cGestureDirectoryEntry(2,"rectangle clockwise",{sAtomGesture::H_LINE_RIGHT,sAtomGesture::V_LINE_DOWN,sAtomGesture::H_LINE_LEFT, sAtomGesture::V_LINE_UP},\
 *                cGestureDirectoryEntry::EQUAL_START_END, Qt::NoButton, 'z',Qt::ControlModifier)};
 * cEyeTrackerFilter* filter= new cEyeTrackerFilter(directory,this,0);
 * qApp->installEventFilter(filter);
 * \endcode
 */
/**@{*/
#ifndef CEYETRACKERFILTER_H
#define CEYETRACKERFILTER_H
class cEyeTrackerFilter;
/**
 * @brief The structure represents a point of mouse cursor trace
 */
struct sMousePos
{
    QPoint point; //!<Cursor position in pixels in main widget coordinates
    Qt::KeyboardModifiers modifiers; //!<The keyboard modifiers (ie. shift, ctrl, alt)
    int64_t time;//!<Time of event occurrence in (ms)
    QAbstractButton *button; //!<Pointer to top QAbstractButton under mouse cursor at \ref point. If no button was found it is nullptr
};
/**
 * @brief The structure represents an atom gesture data and defines eGestureType enumerator
 */
struct sAtomGesture
{
    /**
     * @brief enumerator representing gesturesTypes
     *
     * The sAtomGesture structure is returned by \ref cEyeTrackerFilter::atomGesturesClassifier function
     * \see cEyeTrackerFilter::atomGesturesClassifier
     */
    enum eGestureType{IN_PROGRESS=0, //!<Informs that atomic or complex gesture cannot be unambiguously determined
                      OTHER, //!<Informs that no gesture can be recognized
                      V_LINE_UP,//!< Vertical line was detected
                      V_LINE_DOWN,//!< Vertical line was detected
                      H_LINE_LEFT, //!< Horizontal line was detected
                      H_LINE_RIGHT,//!< Horizontal line was detected
                      GAZE, //!< Gaze event detected
                      EYE_BLINK,//!< Eye blink - not implemented yet
                      GENERIC_CLICK,//!< Mouse clicked the value can be passed by cEyeTrackerFilter::eventFilter function
                      COMPLEX_GESTURE_DETECTED//!<Value returned by \ref cEyeTrackerFilter::detectGesture and \ref cEyeTrackerFilter::detectComplexGesture functions
                     };
    eGestureType gestureType;//!<Type of gesture performed (\ref eGestureType)
    QRect boundingBox;//!<The bounding box of mouse cursor trace
    sMousePos start;//!<The initial point of mouse cursor trace
    sMousePos end;//!<The last point of mouse cursor trace

};
/**
 * @brief The structure stores descriptors of mouse cursor trace
 *
 * The structure is returned by \ref cEyeTrackerFilter::boundingBox function, and is used by \ref cEyeTrackerFilter::atomGesturesClassifier routine
 *
 *\see cEyeTrackerFilter::boundingBox
 * \see cEyeTrackerFilter::tangential_coefs
 * \see  cEyeTrackerFilter::gaze_tolerance
 */
struct boundingBoxInfo
{
    QRect region;//!< Bounding box of mouse cursor trace
    int64_t delta_t; //!<Trace duration in (ms)
    int isMonotonicX;//!<0 - the X coordinates were not monotonic in horizontal direction, -1 - monotonically decreasing in horizontal direction, 1 - monotonically increasing (the maximum deviation from horizontal line is determined by \ref cEyeTrackerFilter::tangential_coefs)
    int isMonotonicY;//!<0 - the Y coordinates were not monotonic in vertical direction, -1 - monotonically decreasing in horizontal direction, 1 - monotonically increasing
    bool gazeDetection;//!< The field informs whether the trajectory is restricted by bounding box of size smaller than  property \ref cEyeTrackerFilter::gaze_tolerance
    QVector<sMousePos> trace;//!< Mouse cursor trace, used only for debugging purposes
};

/**
 * @brief The class represents single entry describing complex gesture
 *
 * The QVector<cGestureDirectoryEntry> can be passed to cEyeTrackerFilter constructor.
 *
 * \code
 * QVector<cGestureDirectoryEntry> v = {cGestureDirectoryEntry(0,"horizontal move left right",{sAtomGesture::H_LINE_LEFT,sAtomGesture::H_LINE_RIGHT},cGestureDirectoryEntry::SAME_BUTTON_END, Qt::LeftButton, 'a', Qt::ControlModifier, 5000),\
 *        cGestureDirectoryEntry(1,"vertical down up",{sAtomGesture::V_LINE_DOWN,sAtomGesture::V_LINE_UP},cGestureDirectoryEntry::EQUAL_START_END, Qt::LeftButton, 0, Qt::NoModifier, 5000),\
 *        cGestureDirectoryEntry(2,"rectangle clockwise",{sAtomGesture::H_LINE_RIGHT,sAtomGesture::V_LINE_DOWN,sAtomGesture::H_LINE_LEFT, sAtomGesture::V_LINE_UP},cGestureDirectoryEntry::EQUAL_START_END, Qt::NoButton, 'z',Qt::ControlModifier)};
 * cEyeTrackerFilter* flt= new cEyeTrackerFilter(v,pMainWindow);
 * \endcode
 */
class cGestureDirectoryEntry{
public:
    /**
     * @brief The enumerator of additional condition checked when proper atom gestures chain is detected.
     */
    enum eConditionType{NONE=0, //!< No additional condition defined (only chain of atom gestures signature has to match). The center of bounding box is assumed to be characteristic point
                        EQUAL_SIZE, //!< All segments (bounding boxes) has to have similar length. The maximum difference has to be lower than tolerance (\ref cEyeTrackerFilter::gesture_tolerance). The center of bounding box is used as characteristic pint
                        EQUAL_START_END,//!< The distance between first point and the last point of the complex gesture should be below be lower than tolerance (\ref cEyeTrackerFilter::gesture_tolerance). The start point is used as characteristic point
                        SAME_BUTTON_END//!< The distance between first point and the last point of the complex gesture should be in the same QAbstractButtonWidget* (\ref sMousePos::button). The start point is used as characteristic point
                       };
private:
    QVector<sAtomGesture::eGestureType> chain;
    QString name_;
    int id_;
    Qt::MouseButton mouseButton_;
    int keyboardShortcut_;
    Qt::KeyboardModifier keyboardModifiers_;
    int max_duration_;
    eConditionType conditionType;
    QRect boundigBox_;
    QPoint clickPoint_;

public:
    /**
     * @brief The default constructor, should not be used, it sets event ID to -1. IDs<0 are ignored
     */
    cGestureDirectoryEntry():id_(-1){}
    /**
     * @brief The preferred constructor for cGestureDirectoryEntry
     * @param _ID The complex gesture ID, if value <0 the entry will be ignored. The number should be unique, otherwise several gesture will generate the same notification. This value is returned by \ref cEyeTrackerFilter::complexGesture signal and \ref cComplexGestureEvent::gestureID())
     * @param _name The descriptive name of gesture. It is returned by \ref cEyeTrackerFilter::complexGesture signal and \ref cComplexGestureEvent::gestureName())
     * @param _chain The vector of atom gesture signatures (\ref sAtomGesture::eGestureType)
     * @param _conditopnType The additional condition which has to be meet to recognize complex gesture as matching (\ref eConditionType)
     * @param clickEvent The QMouseEvent type to be generated when gesture match. If set to Qt::NoButton no mouse event is generated. To generate mouse click can be set to Qt::LeftButton or Qt::RightButton
     * @param keyboardShortcut The Key (character) to send by QKeyEvent. If set to 0 no event is generated. can be ASCII symbol or Qt defines such as Qt::Key_F1,Qt::Key_Home etc.
     * @param keyboardModifiers The Key modifier to send in QKeyEvent (can be i.e Qt::NoModifier, Qt::Qt::ShiftModifier, Qt::ControlModifier)
     * @param max_duration The maximum complex gesture duration in (ms). If negative the condition is skipped
     *
     * \code
     * cGestureDirectoryEntry(1,"vertical down up",{sAtomGesture::V_LINE_DOWN,sAtomGesture::V_LINE_UP},cGestureDirectoryEntry::EQUAL_START_END, Qt::LeftButton, 0, Qt::NoModifier, 5000)
     * \endcode
     */
    cGestureDirectoryEntry(int _ID, const QString& _name, const QVector<sAtomGesture::eGestureType>& _chain, eConditionType _conditopnType, Qt::MouseButton clickEvent, int keyboardShortcut, Qt::KeyboardModifier keyboardModifiers=Qt::NoModifier, int max_duration=-1);
    /**
     * @brief Compares set of atom gestures with cGestureDirectoryEntry
     * @param atomGestures Set of atom gestures
     * @param tolerance Value in (px) used to check additional condition (\ref eConditionType)
     * @return Return true if the pattern match atomGestures subset
     */
    bool compare(QVector<sAtomGesture> atomGestures, double tolerance);
    /**
     * @brief Gets the complex gesture name
     */
    QString getName() const;
    /**
     * @brief Gets the complex gesture ID
     */
    int getID() const;
    /**
     * @brief Gets the complex gesture characteristic point, the point localization is dependent on eConditionType. The value is set by \ref compare function
     */
    QPoint getPoint() const;
    /**
     * @brief Gets the bounding box of whole complex gesture trace of the last compared trace
     */
    QRect getBoundingBox() const {return boundigBox_;}
    /**
     * @brief Gets the mouse button to used in QMouseEvent message
     */
    Qt::MouseButton getMouseButton() const;
    /**
     * @brief Sets the mouse button to be used in QMouseEvent message, if Qt::NoButton is set than no QMouseEvent is generated.
     */
    void setMouseButton(const Qt::MouseButton &mouseButton);
    /**
     * @brief Gets the key to be used in  QKeyEvent message.
     * @return The key value, can be ASCII or extended character, in predefined labels can be used ie.  Qt::Key_F1,  Qt::Key_Delete etc.
     */
    int getKeyboardShortcut() const;

    /**
     * @brief Gets the modifier key (such as shift, alt, ctrl) to be used in QKeyEvent message
     *
     */
    Qt::KeyboardModifier getKeyboardModifiers() const;
    /**
     * @brief Sets keyboard shortcut data for QKeyEvent message emitted when the condition is true
     * @param keyboardShortcut Can be ASCII symbol or Qt defines such as Qt::Key_F1,Qt::Key_Home etc.
     * @param keyboardModifiers Modifier keys (such as shift, alt, ctrl) to be used in QKeyEvent message
     */
    void setKeyboardShortcut(int keyboardShortcut,const Qt::KeyboardModifier &keyboardModifiers);


};


/**
 * @brief The cEyeTrackerFilter class
 */

class cEyeTrackerFilter : public cXmlIO
{
    Q_OBJECT

    QElapsedTimer timer_;
    QElapsedTimer timerMouseLeave_;
    QVector<sMousePos> mouseTrace_;
    QVector<sAtomGesture> atomGestures_;
    sAtomGesture previousGesture_;

    /**
     * @brief The maximum tangental deviation from horizontal or vertical line.
     * The value should be between 0 and 0.5 (0.5 means the 45deg angle)
    */
    ADD_PROPERTY(tangential_coefs, double)
    Q_CLASSINFO("tangential_coefs", "minimum=0;maximum=.5;singleStep=0.05;");
    /**
     * @brief If it is true watching at region of size \ref gaze_tolerance for time longer than \ref gaze_timeout will generate the left click event
    */
    ADD_PROPERTY(use_gaze2click, bool)
    /**
     * @brief The period in [ms]  required to generate click event when user watch at certain point (\ref use_gaze2click)
     * \see use_gaze2click
    */
    ADD_PROPERTY(gaze_timeout, int)
    /**
     * @brief The size of bounding box used to detect gaze_event (\ref use_gaze2click), the value is used also to detect horizontal and vertical line action. The line has to be longer than \ref minimum_gesture_segment_length_ratio * gaze_tolerance
     * \see use_gaze2click
    */
    ADD_PROPERTY(gaze_tolerance, int)
    /**
      * @brief The the minimum length of line segment detected as atomic gesture is calculated as \ref minimum_gesture_segment_length_ratio * \ref gaze_tolerance
      */
    ADD_PROPERTY(minimum_gesture_segment_length_ratio,double)
    Q_CLASSINFO("minimum_gesture_segment_length_ratio", "minimum=1;maximum=100;singleStep=0.1;");

    /**
     * @brief Turn on/off the complex gestures detection
    */
    ADD_PROPERTY(use_complex_gestures, bool)
    /**
     * @brief The maximum duration when mouse cursor leaves the window to be ignored. If false the atomic gestures queue is wiped out
    */
    ADD_PROPERTY(leave_window_timeout, int)
    /**
     * @brief The maximum tolerance (in px) used in complex gesture matching, passed to \ref cGestureDirectoryEntry::compare function
    */
    ADD_PROPERTY(gesture_tolerance,int)
    private:
        QObject* currentSenderObject_;
    QWidget* currentTopWidget_=0;
    QPoint currentLocalMousePos_;
    QWidget* parentWindow_;
    QVector<cGestureDirectoryEntry> gDirectory;//< list of detected atomic gestures, processed by detectComplexGesture routine
    int64_t max_time_=0;
    sAtomGesture::eGestureType processTrace();
    QVector<sMousePos> truncateMonotonicPeriod(QVector<sMousePos> trace, sAtomGesture::eGestureType previousGesture);
protected:
    /**
     * @brief Extracts atom and complex gestures
     * @param confirmType If set to sAtomGesture::GENERIC_CLICK, force to finish pending atomic gesture and to call detectComplexGesture routine, then wipes out buffer of detected gestures. Otherwise the argument should be sAtomGesture::IN_PROGRESS, then finishing atomic gesture is not forced, and the buffer is not cleared if the complex gesture was not detected.
     * @return If complex gesture was not detected it returns sAtomGesture::IN_PROGRESS, when gesture was detected it return sAtomGesture::COMPLEX_GESTURE_DETECTED
     *
     *  Firstly, it try to detect atomic gestures by calculating statistics for recorded mouse trace calling boundingBox function and than runs atomGesturesClassifier. Secondly, when at least two gestures were pushed into detected gestures buffer it runs find detectComplexGesture
     * \see detectComplexGesture()
     */
    sAtomGesture::eGestureType detectGesture(sAtomGesture::eGestureType confirmType);
    /**
     * @brief process the detected atomic gestures and compare them with gesture directory
     * @return sAtomGesture::IN_PROGRESS, or sAtomGesture::COMPLEX_GESTURE_DETECTED when complex gesture was detected
     *
     * The function use postEvent to send cComplexGestureEvent. It generates also QMouseEvent, QKeyEvent if gaze was detected or \ref cGestureDirecotry requires this
     * The function emits also \ref complexGesture signal
     */
    sAtomGesture::eGestureType detectComplexGesture();
    /**
     * @brief Clears the buffer of detected atomic gestures, and mouse trace
     */
    void clearBuffers();
    /**
     * @brief Adds new atomic gestures to buffer of detected gestures.
     * @param newGesture Gesture to be added to set of atom gestures
     *
     * If two following gestures are the same it will try to merge them, otherwise it adds the newElement as new gesture
     */
    void addAtomGesture(const sAtomGesture &newGesture);
    /**
     * @brief Calculates statistics for mouse cursor trace represented by set of sMouse elements, stored in buffer.
     * @param timeHorizont Determines the time interval in (ms) for calculating statistics. If it is -1 the all data are used
     * @return The structure of calculated statistics
     * \warning Should be run only when the mouse cursor trace buffer is not empty
     */
    boundingBoxInfo boundingBox(int64_t timeHorizont=-1);
    /**
     * @brief Tries to classify the mouse cursor trace into one of atomic gestures
     * @param info statistics of mouse trace calculated by boundingBox function
     * @return If gesture was unambiguously detected it might return (sAtomGesture::H_LINE_LEFT,sAtomGesture::H_LINE_RIGHT, sAtomGesture::V_LINE_UP, sAtomGesture::V_LINE_DOWN, sAtomGesture::GAZE), when the gesture can not be unambiguously determined sAtomGesture::IN_PROGRESS is returned, if no gesture match sAtomGesture::OTHER is returned
     */
    sAtomGesture atomGesturesClassifier(const boundingBoxInfo &info);

public:
    /**
     * @brief The constructor for event filter class
     * @param gDirectory - directory of complex gestures
     * @param mouseGrabberWidget - the main widget for which the mouse position is send by in QEvent::MouseMove to \ref eventFilter function
     * @param xmlID - id for xml serializer
     *
     * The filter can be added to any QObject or QApplication::instance()
     * \code
     * cEyeTrackerFilter* eventFilter= new cEyeTrackerFilter(v,this,0);
     * qApp->installEventFilter(eventFilter);
     * \endcode
     */
    explicit cEyeTrackerFilter(const QVector<cGestureDirectoryEntry>& gDirectory, QWidget *mouseGrabberWidget, int xmlID=0);
    /**
     * @brief the overridden eventFilter function used to process mouse events
     * @param watched The object watched events
     * @param event Pointer to event to be processed
     * @return true Discards event, false - accepts event
     */
    bool eventFilter(QObject *watched, QEvent *event);

    enum logSource{MOUSE_MOVE, MOUSE_CLICK, HOVER_LEAVE, HOVER_ENTER, ATOM_GESTURE_EVENT, COMPLEX_GESTURE_EVENT, CONFIRM_EVENT, USER=100};

signals:
    /**
     * @brief Signal is send when complex gesture was recognized
     * @param ID The gesture ID defined in \ref cGestureDirectoryEntry
     * @param bb The complex gesture resulted bounding box of mouse cursor trace
     * @param name The complex gesture name defined in \ref cGestureDirectoryEntry
     */
    void complexGesture(int ID, QRect bb, QPoint point, QString name);

    void logEvent(cEyeTrackerFilter::logSource ID, int x, int y,  int additional, QString info);
public slots:
};


/**
 * @brief Event class for passing complex gesture events recognized by cEyeTrackerFilter filter
 *
 * The packet can be processed by any eventFilter routine overridden from QObject.
 * \code
 * if(event->type()==cComplexGestureEvent::type())
 *   {
 *       cComplexGestureEvent* complexGestureEvent= static_cast<cComplexGestureEvent *>(event);
 *       qDebug()<<"cComplexGestureEvent"<<complexGestureEvent->gestureName();
 *   }
 *   \endcode
 */
class cComplexGestureEvent : public QEvent
{
public:
    /**
     * @brief The object is used to pass QEvent messages in QT framework
     * @param entry The directory entry of recognized complex gestures
     */
    cComplexGestureEvent(const cGestureDirectoryEntry& entry);

    virtual ~cComplexGestureEvent()
    {}

    /**
     * @brief overridden function from QEvent class
     * @return
     *
     * The unique event type identifier is generated in runtime
     */
    static QEvent::Type type()
    {
        if (customEventType == QEvent::None)
        {
            int generatedType = QEvent::registerEventType();
            customEventType = static_cast<QEvent::Type>(generatedType);
        }
        return customEventType;
    }

    /**
     * @brief Gets the characteristic point of detected complex gesture
     */
    QPoint pos() const;
    /**
     * @brief Sets the characteristic point in (px) in main widget coordinates to be send in QEvent message
     *
     */
    void setPos(const QPoint &pos);

    /**
     * @brief Gets bounding box of whole mouse cursor trace recognized as complex gesture
     *
     */
    QRect boundingBox() const;
    /**
     * @brief Sets bounding box of detected complex gesture
     */
    void setBoundingBox(const QRect &boundingBox);

    /**
     * @brief Gets complex gesture ID related to \ref cGestureDirectoryEntry::ID
     */
    int gestureID() const;
    /**
     * @brief Sets complex gesture ID, should be the same as in matched \ref cGestureDirectoryEntry object
     */
    void setGestureID(int gestureID);

    /**
     * @brief Gets complex gesture name, related to the \ref cGestureDirectoryEntry::getID()
     */
    QString gestureName() const;
    /**
     * @brief Sets complex gesture name to be send in QEvent message
     * @param gestureName Should be the same as in matched \ref cGestureDirectoryEntry::getName()
     */
    void setGestureName(const QString &gestureName);

private:
    static QEvent::Type customEventType;
    QPoint pos_;
    QRect boundingBox_;
    int gestureID_;
    QString gestureName_;

};



#endif // CEYETRACKERFILTER_H
/**@}*/
