#include "ceyetrackerfilter.h"
#include <QMouseEvent>
#include <QMetaEnum>
#include <QApplication>
#include <QDesktopWidget>
#include <QPushButton>
#include <QPainter>

#include <QDebug>

#include <cmath>

QEvent::Type cComplexGestureEvent::customEventType = QEvent::None;
void cEyeTrackerFilter::clearBuffers()
{
    mouseTrace_ = mouseTrace_.mid( mouseTrace_.size()-1);
    atomGestures_.clear();
    previousGesture_.gestureType = sAtomGesture::OTHER;
}

cEyeTrackerFilter::cEyeTrackerFilter(const QVector<cGestureDirectoryEntry>& _gDirectory, QWidget *mouseGrabberWidget, int xmlID) : gDirectory(_gDirectory),m_gaze_timeout(1000), m_gaze_tolerance(25),m_use_gaze2click(false), m_tangential_coefs(0.4), m_use_complex_gestures(true), m_leave_window_timeout(500), cXmlIO(xmlID,mouseGrabberWidget), m_gesture_tolerance(30), m_minimum_gesture_segment_length_ratio(1.3)
{
    Q_ASSERT(mouseGrabberWidget!=nullptr);
    if(mouseGrabberWidget!=nullptr)
        parentWindow_ = dynamic_cast<QWidget*>(mouseGrabberWidget);
    previousGesture_.gestureType = sAtomGesture::IN_PROGRESS;
    timerMouseLeave_.start();
    clearBuffers();
}
bool cEyeTrackerFilter::eventFilter(QObject *watched, QEvent *event)
{


    if(event->type()==cComplexGestureEvent::type())
    {
        cComplexGestureEvent* cev= static_cast<cComplexGestureEvent *>(event);
        //qDebug()<<"cComplexGestureEvent"<<cev->gestureName();
        return false;
    }

    if (event->type() == QEvent::MouseMove)
    {
        QAbstractButton* b = dynamic_cast<QAbstractButton*>(watched);
        if(b!=NULL) //discards move events for buttons, exception is only when the button is the parent window
        {
            if(b != parentWindow_)
                return false;
        }

        currentSenderObject_ = watched;

        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);

        currentLocalMousePos_ = mouseEvent->pos();

        sMousePos mouseData;
        mouseData.point = QCursor::pos();//mouseEvent->pos();
        emit logEvent(MOUSE_MOVE,mouseData.point.x(),mouseData.point.y(),0,"mouse move");
        //qDebug()<<mouseData.point;
        mouseData.time = timer_.elapsed();
        mouseData.modifiers = mouseEvent->modifiers();
        currentTopWidget_ = QApplication::widgetAt(QCursor::pos());
        QAbstractButton* pAbstactButton = dynamic_cast<QAbstractButton*>(currentTopWidget_);

        mouseData.button = pAbstactButton;
        mouseTrace_.push_back(mouseData);
        if(!m_use_complex_gestures) return false;
        sAtomGesture::eGestureType g=processTrace();

        return false; //accept all events
    }

    if (event->type() == QEvent::MouseButtonPress||event->type() == QEvent::MouseButtonRelease)
    {
        //qDebug()<<"mouse pressed/released";
        currentSenderObject_ = watched;

        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
        //emit logEvent(event->type(),mouseEvent->pos().x(),mouseEvent->pos().y(),"mouse pressed/released");
        QPoint point = QCursor::pos();
        emit logEvent(MOUSE_CLICK,point.x(),point.y(),mouseEvent->buttons(),"mouse pressed/released");
        currentLocalMousePos_ = mouseEvent->pos();
        clearBuffers(); //click clears the atomic gesture queue
        return false;

    }
    if(event->type() == QEvent::HoverLeave)
    {
        //qDebug()<<"Hover leave";
        QAbstractButton* b = dynamic_cast<QAbstractButton*>(watched);
        if(b!=NULL) //discards move events for buttons, exception is only when the button is the parent window
        {
            if(b != parentWindow_)
                return false;
        }
        QPoint point = QCursor::pos();
        emit logEvent(HOVER_LEAVE,point.x(),point.y(),0,"hover leave");
        //qDebug()<<"QEvent::HoverLeave"<<watched->metaObject()->className();
        //emit logEvent(event->type(),-1,-1,"Hover Leave");
        timerMouseLeave_.restart();
        return false;
    }

    if (event->type() == QEvent::HoverEnter)
    {
        //qDebug()<<"Hover enter";
        int el = timerMouseLeave_.elapsed();
        QAbstractButton* b = dynamic_cast<QAbstractButton*>(watched);
        if(b!=NULL) //discards move events for buttons, exception is only when the button is the parent window
        {
            if(b != parentWindow_)
                return false;
        }
        QPoint point = QCursor::pos();
        emit logEvent(HOVER_LEAVE,point.x(),point.y(),0,"hover enter");
        //qDebug()<<"QEvent::HoverEnter"<<watched->metaObject()->className();

        //emit logEvent(event->type(),el,m_leave_window_timeout,"Hover enter");
        if(el>m_leave_window_timeout)
        {
            mouseTrace_.clear();
            clearBuffers();
        }
        return false;
    }
    return false;
}
sAtomGesture::eGestureType cEyeTrackerFilter::detectComplexGesture()
{
    //TODO:
    if(!m_use_complex_gestures)
    {
        clearBuffers();
        return sAtomGesture::IN_PROGRESS;
    }
    int N=atomGestures_.size();
    Qt::MouseButton mouseButton = Qt::NoButton;
    int detectedComplexGesture = -1;
    sAtomGesture::eGestureType ret = sAtomGesture::IN_PROGRESS;
    QPoint clickPos= QCursor::pos();
    QPoint pos = QCursor::pos();
    QWidget* top=currentTopWidget_;
    QWidget *pw = parentWindow_;
    QVector<sAtomGesture> atomGestures_tmp = atomGestures_;

    if(atomGestures_[N-1].gestureType==sAtomGesture::EYE_BLINK||atomGestures_[N-1].gestureType==sAtomGesture::GAZE)
    {
        atomGestures_tmp.pop_back();
    }
    for(int i = 0; i<gDirectory.size(); i++)
    {
        if(gDirectory[i].compare(atomGestures_tmp,m_gesture_tolerance)) //do not use confirm gesture
        {

            //qDebug()<<"gesture "<<gDirectory[i].getName()<<" found";
            mouseButton = gDirectory[i].getMouseButton();
            detectedComplexGesture = gDirectory[i].getID();
            ret = sAtomGesture::COMPLEX_GESTURE_DETECTED;

            clickPos = gDirectory[i].getPoint();//parentWindow_->mapToGlobal(gDirectory[i].getPoint());//-currentLocalMousePos_ +clickPos;
            //emit logEvent(100+gDirectory[i].getID(),clickPos.x(),clickPos.y(),gDirectory[i].getName()+QString(" at (%1,%2)").arg(gDirectory[i].getPoint().x()).arg(gDirectory[i].getPoint().y()));

            //GENERATE CUSTOM EVENT
            cComplexGestureEvent* ev = new cComplexGestureEvent(gDirectory[i]);
            //ev->setTopWidget(top);

            if(top!=nullptr)
            {
                QApplication::postEvent(parentWindow_, ev);
            }

            //emit complexGesture(detectedComplexGesture,gDirectory[i].getBoundingBox(),gDirectory[i].getName() );
            //generate key shortcut
            if(gDirectory[i].getKeyboardShortcut()!=0)
            {
                QKeyEvent *kev = new QKeyEvent(QEvent::ShortcutOverride,gDirectory[i].getKeyboardShortcut(),gDirectory[i].getKeyboardModifiers());
                if(top!=nullptr)
                {
                    QApplication::postEvent(parentWindow_, kev);
                }

            }
            emit complexGesture(gDirectory[i].getID(), gDirectory[i].getBoundingBox(), gDirectory[i].getPoint(),gDirectory[i].getName());
            emit logEvent(COMPLEX_GESTURE_EVENT,gDirectory[i].getPoint().x(),gDirectory[i].getPoint().y(),gDirectory[i].getID(),"complex gesture:"+gDirectory[i].getName());
            clearBuffers();
            break;
        }
    }
    if(detectedComplexGesture<0)
    {
        if(atomGestures_[N-1].gestureType==sAtomGesture::EYE_BLINK||(atomGestures_[N-1].gestureType==sAtomGesture::GAZE&&m_use_gaze2click))
        {
            mouseButton = Qt::LeftButton;
            clearBuffers();
            ret = sAtomGesture::GENERIC_CLICK;
        }
        else
        {
            return ret;
        }
    }
    else
    {
        clearBuffers();
    }


    if(mouseButton!=Qt::NoButton)
    {
        QWidget* top=QApplication::widgetAt(clickPos);
        if(top!=nullptr)
        {
            //sends click to top window
            QPoint localPos = top->mapFromGlobal(clickPos);

            QMouseEvent *pressEvent= new QMouseEvent(QEvent::MouseButtonPress, localPos, mouseButton, mouseButton, Qt::NoModifier);
            QApplication::postEvent(top, pressEvent);
            QMouseEvent *releaseEvent= new QMouseEvent(QEvent::MouseButtonRelease, localPos, mouseButton, mouseButton, Qt::NoModifier);
            QApplication::postEvent(top, releaseEvent);
            return ret;
        }
    }

    return ret;

}

boundingBoxInfo cEyeTrackerFilter::boundingBox(int64_t timeHorizont)
{
    boundingBoxInfo bb;
    int N = mouseTrace_.size();
    if(N==0)
    {
        bb.gazeDetection = 0;
        bb.delta_t = 0;
        bb.isMonotonicX = 1;
        bb.isMonotonicY = 1;
        bb.region = QRect(0,0,0,0);
        return bb;
    }
    Q_ASSERT(mouseTrace_.size() > 0);
    int xMin = std::numeric_limits<int>::max(); // everything is <= this
    int xMax = std::numeric_limits<int>::min(); // everything is >= this
    int yMin = std::numeric_limits<int>::max();
    int yMax = std::numeric_limits<int>::min();
    int deltaX_min=std::numeric_limits<int>::max(), deltaX_max=std::numeric_limits<int>::min(), deltaY_min=std::numeric_limits<int>::max(), deltaY_max=std::numeric_limits<int>::min();
    sMousePos prev = mouseTrace_[0];
    int64_t endTime = mouseTrace_[N-1].time;

    foreach (sMousePos p, mouseTrace_) {
        if(timeHorizont>0)
            if(endTime-timeHorizont>p.time)
                continue;
        QPoint pt = p.point-prev.point;
        prev.point = p.point;
        deltaX_min = qMin(deltaX_min,pt.x());
        deltaX_max = qMax(deltaX_max,pt.x());
        deltaY_min = qMin(deltaY_min,pt.y());
        deltaY_max = qMax(deltaY_max,pt.y());
        xMin = qMin(xMin, p.point.x());
        xMax = qMax(xMax, p.point.x());
        yMin = qMin(yMin, p.point.y());
        yMax = qMax(yMax, p.point.y());
    }

    bb.isMonotonicX = 0;
    if(deltaX_max<=0&&deltaX_min < 0)
        bb.isMonotonicX = -1;
    if(deltaX_max>0&&deltaX_min >= 0)
        bb.isMonotonicX = 1;

    bb.isMonotonicY = 0;
    if(deltaY_max<=0&&deltaY_min < 0)
        bb.isMonotonicY = -1;

    if(deltaY_max>0&&deltaY_min >= 0)
        bb.isMonotonicY = 1;

    bb.region = QRect(QPoint(xMin,yMin),QPoint(xMax,yMax));
    bb.delta_t = mouseTrace_[N-1].time-mouseTrace_[0].time;
    bb.gazeDetection = false;

    if(bb.delta_t>m_gaze_timeout&&timeHorizont<0)
    {
        boundingBoxInfo bg = boundingBox(m_gaze_timeout);
        if(bg.region.width()<m_gaze_tolerance&&bg.region.height()<m_gaze_tolerance)
        {
            bb.gazeDetection = true;
        }
    }

    bb.trace = mouseTrace_;
    return bb;
}

sAtomGesture cEyeTrackerFilter::atomGesturesClassifier(const boundingBoxInfo& info)
{
    sAtomGesture expectedGesture;
    expectedGesture.gestureType = sAtomGesture::OTHER;
    expectedGesture.boundingBox = info.region;
    expectedGesture.start = info.trace.first();
    expectedGesture.end = info.trace.last();
    //expectedGesture.point = info.region.center();
    if(info.isMonotonicY)
    {
        if(info.region.width()<m_gaze_tolerance||(double)info.region.width()/info.region.height()<tangential_coefs())
        {
            if(info.region.height()>m_minimum_gesture_segment_length_ratio*m_gaze_tolerance)
            {
                if(info.isMonotonicY>0)
                {
                    expectedGesture.gestureType = sAtomGesture::V_LINE_DOWN;
                    //qDebug()<<"sAtomGesture::V_LINE_DOWN started at"<<info.trace.first().point<<" number of gestures stord:"<<atomGestures_.size();
                }
                else
                {
                    expectedGesture.gestureType = sAtomGesture::V_LINE_UP;
                    //qDebug()<<"sAtomGesture::V_LINE_UP started at"<<info.trace.first().point<<" number of gestures stord:"<<atomGestures_.size();
                }
            }
            else
            {
                //line is too short
                //qDebug()<<"maybe sAtomicGesture::V_LINE in progres started at"<<info.trace.first().point;
                expectedGesture.gestureType = sAtomGesture::IN_PROGRESS;
            }
        }
    }
    //Vertical Line
    if(info.isMonotonicX)
    {
        if(info.region.height()<m_gaze_tolerance||(double)info.region.height()/info.region.height()<tangential_coefs())
        {
            if(info.region.width()>2*m_gaze_tolerance)
            {
                if(info.isMonotonicX>0)
                {
                    expectedGesture.gestureType = sAtomGesture::H_LINE_RIGHT;
                    //qDebug()<<"sAtomGesture::H_LINE_RIGHT started at"<<info.trace.first().point;;;
                }
                else
                {
                    expectedGesture.gestureType = sAtomGesture::H_LINE_LEFT;
                    //qDebug()<<"sAtomGesture::H_LINE_LEFT started at"<<info.trace.first().point;;;
                }
            }
            else
            {
                //qDebug()<<"maybe sAtomicGesture::H_LINE in progres started at"<<info.trace.first().point;;;
                expectedGesture.gestureType = sAtomGesture::IN_PROGRESS;
            }
        }
    }

    if(expectedGesture.gestureType == sAtomGesture::OTHER)
    {
        if(info.gazeDetection||(info.region.width()<m_gaze_tolerance&&info.region.height()<m_gaze_tolerance))
        {
            expectedGesture.gestureType = sAtomGesture::IN_PROGRESS;
        }
    }
    return expectedGesture;
}

sAtomGesture::eGestureType cEyeTrackerFilter::detectGesture(sAtomGesture::eGestureType confirmType)
{
    int N=mouseTrace_.size();
    boundingBoxInfo info = boundingBox();
    sAtomGesture lineGesture = atomGesturesClassifier(info);
    //qDebug()<<"detected gesture"<<lineGesture.gestureType;
    if(lineGesture.gestureType == sAtomGesture::OTHER)
    {
        if(previousGesture_.gestureType!=sAtomGesture::OTHER&&previousGesture_.gestureType!=sAtomGesture::IN_PROGRESS)
        {
            addAtomGesture(previousGesture_);
            emit logEvent(ATOM_GESTURE_EVENT,previousGesture_.start.point.x(),previousGesture_.start.point.y(),previousGesture_.gestureType,QString("atom gesture:%1").arg(previousGesture_.gestureType));
            mouseTrace_ = truncateMonotonicPeriod(mouseTrace_,previousGesture_.gestureType);
            boundingBoxInfo info_tmp = boundingBox();
            sAtomGesture lineGesture_tmp = atomGesturesClassifier(info_tmp);
            if(lineGesture_tmp.gestureType==sAtomGesture::IN_PROGRESS||lineGesture_tmp.gestureType==sAtomGesture::OTHER)
            {
                //no gesture found
                previousGesture_.gestureType = sAtomGesture::IN_PROGRESS;
                lineGesture.gestureType = sAtomGesture::IN_PROGRESS;
                //mouseTrace_ = mouseTrace_.mid(mouseTrace_.size()-1);
                //mouseTrace_ = truncateMonotonicPeriod(mouseTrace_);
            }
            else
            {
                //2 points gesture found
                previousGesture_ = lineGesture_tmp;
                lineGesture = lineGesture_tmp;
            }

            if(info.gazeDetection==false && confirmType==sAtomGesture::IN_PROGRESS) //click will not be generated
            {
                //
                return detectComplexGesture();
            }

        }
        else
        {
            //qDebug()<<"trace rejected not clasified"<<mouseTrace_.size();
            //trace rejected, the point set does not match to any atomic gesture
            //leave monotonic points chain

            mouseTrace_ = truncateMonotonicPeriod(mouseTrace_,sAtomGesture::OTHER);//.mid(mouseTrace_.size()-2); //leaves only 2 points
            //mouseTrace_.mid(mouseTrace_.size()-2); //leaves only 2 points

            boundingBoxInfo info_tmp = boundingBox();

            sAtomGesture lineGesture_tmp = atomGesturesClassifier(info_tmp); //try to find atomic gesture
            if(lineGesture_tmp.gestureType==sAtomGesture::IN_PROGRESS||lineGesture_tmp.gestureType==sAtomGesture::OTHER)
            {
                previousGesture_.gestureType = sAtomGesture::IN_PROGRESS;
                lineGesture.gestureType = sAtomGesture::IN_PROGRESS;
                //mouseTrace_ = mouseTrace_.mid(mouseTrace_.size()-1);
            }
            else
            {
                //gesture found
                previousGesture_ = lineGesture_tmp;
                lineGesture = lineGesture_tmp;
            }

        }

    }
    else //detected gesture is not OTHER
    {
        //known gesture not finished yet when info.gazeDetection==0
        if(info.gazeDetection==false&&(confirmType==sAtomGesture::IN_PROGRESS))
        {
            //try to detect complex gesture when at least two atomic gestures were detected
            if(lineGesture.gestureType!=sAtomGesture::OTHER&&lineGesture.gestureType!=sAtomGesture::IN_PROGRESS&&atomGestures_.size()>0)
            {
                QVector<sAtomGesture> atomGestures_tmp = atomGestures_;
                addAtomGesture(lineGesture);
                sAtomGesture::eGestureType res_tmp = detectComplexGesture();
                if(res_tmp == sAtomGesture::IN_PROGRESS) //no complex gesture detected
                {
                    atomGestures_ = atomGestures_tmp;
                }
                else //complex gesture found
                {
                    emit logEvent(ATOM_GESTURE_EVENT,lineGesture.start.point.x(),lineGesture.start.point.y(),lineGesture.gestureType,QString("atom gesture:%1").arg(lineGesture.gestureType));
                    mouseTrace_ = mouseTrace_.mid(mouseTrace_.size()-1); //truncateMonotonicPeriod(mouseTrace_, lineGesture.gestureType);//mouseTrace_.mid(mouseTrace_.size()-2);
                    previousGesture_.gestureType = sAtomGesture::IN_PROGRESS;
                    lineGesture.gestureType = sAtomGesture::IN_PROGRESS;
                }
                previousGesture_ = lineGesture;
                return res_tmp;
            }

        }
    }
    if(info.gazeDetection) //gaze was detected
    {
        // emit logEvent(-200,info.region.center().x(),info.region.center().y(),"gaze detected");
        //qDebug()<<"gaze detected";
        if(confirmType  == sAtomGesture::IN_PROGRESS)
            confirmType = sAtomGesture::GAZE;
    }

    if(confirmType!=sAtomGesture::IN_PROGRESS) //the external or gaze-clikc was generated
    {
        if(lineGesture.gestureType!=sAtomGesture::OTHER&&lineGesture.gestureType!=sAtomGesture::IN_PROGRESS)
        {
            //if new gesture is pending add to queue
            addAtomGesture(lineGesture);
            emit logEvent(ATOM_GESTURE_EVENT,lineGesture.start.point.x(),lineGesture.start.point.y(),lineGesture.gestureType,QString("atom gesture:%1").arg(lineGesture.gestureType));
            mouseTrace_.mid(mouseTrace_.size()-1);
        }
        else
        {
            //unkonow gesture
        }
        //insert comfirm event
        previousGesture_.gestureType=confirmType;
        //previousGesture_.point = (*(info.trace.end()-2)).point;//point followed eye close
        previousGesture_.boundingBox=QRect((*(info.trace.end()-2)).point,(*(info.trace.end()-2)).point);
        previousGesture_.start = (*(info.trace.end()-2));//previousGesture_.point;
        previousGesture_.end = (*(info.trace.end()-2));//previousGesture_.point;
        addAtomGesture(previousGesture_);
        emit logEvent(CONFIRM_EVENT,previousGesture_.start.point.x(),previousGesture_.start.point.y(),previousGesture_.gestureType,QString("atom gesture:%1").arg(previousGesture_.gestureType));
        previousGesture_.gestureType = sAtomGesture::IN_PROGRESS;
        return detectComplexGesture();
    }
    previousGesture_ = lineGesture;
    return sAtomGesture::IN_PROGRESS;
}

void cEyeTrackerFilter::addAtomGesture(const sAtomGesture& newGesture)
{
    bool mergable = false;
    if(atomGestures_.size()>0)
    {
        //try to merge
        sAtomGesture previous = atomGestures_.last();
        if(newGesture.gestureType == previous.gestureType)
        {

            QRect bb = newGesture.boundingBox.united(previous.boundingBox);
            switch(newGesture.gestureType)
            {
            case sAtomGesture::V_LINE_DOWN: case sAtomGesture::V_LINE_UP:
            {
                //merging condition for vertical line
                if(bb.width()<m_gaze_tolerance||(double)bb.width()/bb.height()<m_tangential_coefs)
                {
                    if(newGesture.end.time - previous.start.time < m_gaze_timeout)
                        mergable = true;

                }
            }
                break;
            case sAtomGesture::H_LINE_RIGHT: case sAtomGesture::H_LINE_LEFT:
            {
                //merge condition for horizontal line
                if(bb.height()<m_gaze_tolerance||(double)bb.height()/bb.width()<m_tangential_coefs)
                {
                    if(newGesture.end.time - previous.start.time < m_gaze_timeout)
                        mergable =  true;
                }
            }
                break;
            }
        }
    }
    if(mergable)
    {
        //qDebug()<<"two sAtomGestures merged together";
        sAtomGesture& v = atomGestures_.last();
        v.boundingBox = v.boundingBox.united(newGesture.boundingBox);
        v.end = newGesture.end;

        //v.point = v.boundingBox.center();
    }
    else
    {
        atomGestures_.push_back(newGesture);
    }



}

sAtomGesture::eGestureType cEyeTrackerFilter::processTrace()
{
    int N=mouseTrace_.size();
    if(N>1)
    {
        //        int64_t delta = mouseTrace_[N-1].time-mouseTrace_[N-2].time;
        //        if(delta>m_gaze_timeout)
        //        {

        //            return detectGesture(sAtomGesture::EYE_BLINK);
        //        }
        //        else
        return detectGesture(sAtomGesture::IN_PROGRESS);
    }
    return sAtomGesture::IN_PROGRESS;
}
int signum(float val)
{
    return val>=0?1:-1;
}

QVector<sMousePos> cEyeTrackerFilter::truncateMonotonicPeriod(QVector<sMousePos> trace, sAtomGesture::eGestureType previousGesture)
{
    QVector<sMousePos> trace_res;
    if(trace.size()>1)
    {
        QPoint delta = trace.last().point - (*(trace.end()-2)).point;
        int monotonicX = signum(delta.x());

        int monotonicY = signum(delta.y());
        //if(previousGesture == sAtomGesture::H_LINE_LEFT||previousGesture == sAtomGesture::H_LINE_RIGHT)
        //    monotonicY = 0;
        //if(previousGesture == sAtomGesture::V_LINE_DOWN||previousGesture == sAtomGesture::V_LINE_UP)
        //    monotonicX = 0;
        //qDebug()<<"truncate according monotonic: X,Y"<<monotonicX<<","<<monotonicY<<trace.last().point;
        for(int i=trace.size()-3; i>=0; i--)
        {
            //qDebug()<<trace[i+1].point<<" - "<<trace[i].point;
            delta = trace[i+1].point - trace[i].point;
            if(monotonicX != signum(delta.x()) && delta.x() != 0) monotonicX = 0;
            if(monotonicY != signum(delta.y()) && delta.y() != 0) monotonicY = 0;
            if(monotonicX==0&&monotonicY==0)
            {
                //qDebug()<<"truncated at due to monotony condition"<<trace[i].point<<delta;
                return trace.mid(i+1);
            }
            QPoint tDist = trace.last().point - trace[i].point;
            if(fabs(tDist.x())>0.15*m_gaze_tolerance&&monotonicX==0)
            {
                return trace.mid(i+1);
            }
            if(fabs(tDist.y())>0.15*m_gaze_tolerance&&monotonicY==0)
            {
                return trace.mid(i+1);
            }
            if(qMin(fabs(tDist.y()),fabs(tDist.x()))>0.15*m_gaze_tolerance)
            {
                return trace.mid(i+1);
            }

        }
    }
    return trace.mid(trace.size()-2);
}

QString cGestureDirectoryEntry::getName() const
{
    return name_;
}

int cGestureDirectoryEntry::getID() const
{
    return id_;
}

QPoint cGestureDirectoryEntry::getPoint() const {
    return clickPoint_;}



Qt::MouseButton cGestureDirectoryEntry::getMouseButton() const
{
    return mouseButton_;
}

void cGestureDirectoryEntry::setMouseButton(const Qt::MouseButton &mouseButton)
{
    mouseButton_ = mouseButton;
}

int cGestureDirectoryEntry::getKeyboardShortcut() const
{
    return keyboardShortcut_;
}

void cGestureDirectoryEntry::setKeyboardShortcut(int keyboardShortcut, const Qt::KeyboardModifier& modifiers)
{
    keyboardShortcut_ = keyboardShortcut;
    keyboardModifiers_ = modifiers;
}

Qt::KeyboardModifier cGestureDirectoryEntry::getKeyboardModifiers() const
{
    return keyboardModifiers_;
}


cGestureDirectoryEntry::cGestureDirectoryEntry(int _ID, const QString& _name, const QVector<sAtomGesture::eGestureType>& _chain, cGestureDirectoryEntry::eConditionType _conditopnType, Qt::MouseButton clickEvent, int keyboardShortcut, Qt::KeyboardModifier keyboardModifiers, int max_duration):id_(_ID),name_(_name),chain(_chain), conditionType(_conditopnType), mouseButton_(clickEvent), keyboardShortcut_(keyboardShortcut),keyboardModifiers_(keyboardModifiers),max_duration_(max_duration)
{

}

bool cGestureDirectoryEntry::compare(QVector<sAtomGesture> atomGestures, double tolerance)
{
    if(id_<0) return false;
    if(atomGestures.size()>=chain.size())
    {

        int size = chain.size();
        int AGsize = atomGestures.size();
        bool isEqual = true;
        QVector<sAtomGesture> selectedEl;
        double meanLength = 0;
        boundigBox_ = atomGestures[AGsize - 1].boundingBox;
        clickPoint_ = boundigBox_.center();
        for(int j = 0; j<size; j++)
        {
            auto gt = atomGestures[AGsize - 1 - j].gestureType;
            auto chs = chain[size-1-j];
            if(gt!=chs)
            {
                isEqual = false;
                break;
            }
            selectedEl.push_back(atomGestures[AGsize - 1 - j]);
            meanLength += (double)qMax(atomGestures[AGsize - 1 - j].boundingBox.height(), atomGestures[AGsize - 1 - j].boundingBox.width())/size;
            boundigBox_ = boundigBox_.united(atomGestures[AGsize - 1 - j].boundingBox);
        }
        if(isEqual)
        {
            //qDebug()<<"complex gesture was found" <<selectedEl.size()<<conditionType<<name_;

            int delta_t = selectedEl.first().end.time-selectedEl.last().start.time;
            bool passed = true;
            if(max_duration_ < delta_t && max_duration_>=0)
            {
                passed = false;
            }
            else
            {
                switch(conditionType)
                {
                case NONE: passed = true;
                    break;
                case EQUAL_SIZE:
                {
                    for(int j = 0; j<size; j++)
                    {
                        if(fabs(qMax(atomGestures[AGsize - 1 - j].boundingBox.height(), atomGestures[AGsize - 1 - j].boundingBox.width())- meanLength)>tolerance)
                        {
                            clickPoint_ = boundigBox_.center();
                            passed = false;
                            break;
                        }

                    }

                }
                    break;

                case EQUAL_START_END:
                {
                    clickPoint_ = selectedEl.last().start.point;

                    QPoint p = selectedEl.first().end.point-selectedEl.last().start.point;
                    if(fabs(p.x())>tolerance || fabs(p.y())>tolerance)
                        passed = false;
                }
                    break;
                case SAME_BUTTON_END:
                {
                    clickPoint_ = (selectedEl.first().end.point);
                    if(!((selectedEl.first().end.button==selectedEl.last().start.button)&&(selectedEl.last().start.button!=nullptr)))
                    {
                        passed = false;
                    }
                }
                    break;
                    //default:

                }
            }
            return passed;
        }
    }
    return false;
}

cComplexGestureEvent::cComplexGestureEvent(const cGestureDirectoryEntry &entry) : QEvent(cComplexGestureEvent::type())
{
    pos_ = entry.getPoint();
    boundingBox_ = entry.getBoundingBox();
    gestureID_ = entry.getID();
    gestureName_ = entry.getName();
}

QPoint cComplexGestureEvent::pos() const
{
    return pos_;
}

void cComplexGestureEvent::setPos(const QPoint &pos)
{
    pos_ = pos;
}

QRect cComplexGestureEvent::boundingBox() const
{
    return boundingBox_;
}

void cComplexGestureEvent::setBoundingBox(const QRect &boundingBox)
{
    boundingBox_ = boundingBox;
}

int cComplexGestureEvent::gestureID() const
{
    return gestureID_;
}

void cComplexGestureEvent::setGestureID(int gestureID)
{
    gestureID_ = gestureID;
}

QString cComplexGestureEvent::gestureName() const
{
    return gestureName_;
}

void cComplexGestureEvent::setGestureName(const QString &gestureName)
{
    gestureName_ = gestureName;
}

