#include "osthumb.h"

#include <QTimer>
#include <QPainter>
#include <QApplication>
#include <QFontMetrics>
#include <QMouseEvent>

static int thumbWidth=20;
static int thumbHeight=72;
//#define THUMB_WIDTH          20
//#define THUMB_HEIGHT         72
#define THUMB_WIDTH thumbWidth
#define THUMB_HEIGHT thumbHeight
#define OUTLINE_THICKNESS     2
#define ANIMATION_TIMEOUT    10
#define HIDE_TIMEOUT        100
#define ANIMATION_ALPHA_INC  15

///*
// *  Variables used only for implementing methods.
// */
//QTimer *animationTimer; // The timer used to set the timeout for updating the alpha of the thumb
//                        // (used for fade in/out animation).
//QTimer *hideTimer;
//int alpha; // Current alpha of the thumb.
//int yPos;  // The position the thumb is to be moved (when aligned vertically).
//int xPos;  // Same as above (when aligned horizontally).
//bool mouseButtonPressed; // Holds the state of the left mouse button.
//bool wasDragged; // The widget has been dragged (neded to separate click on page up/down and drag action).

class OsThumbPrivate
{
    Q_DECLARE_PUBLIC(OsThumb)

public:
    OsThumbPrivate(OsThumb *q);
    bool hidden;
    Qt::Orientation orientation;
    int minimum;
    int maximum;
    OsThumb *q_ptr;
    void checkForLeave(QMouseEvent *event);

    /*
     *  Variables used only for implementing methods.
     */
    QTimer *animationTimer; // The timer used to set the timeout for updating the alpha of the thumb
                            // (used for fade in/out animation).
    QTimer *hideTimer;
    int alpha; // Current alpha of the thumb.
    int yPos;  // The position the thumb is to be moved (when aligned vertically).
    int xPos;  // Same as above (when aligned horizontally).
    bool mouseButtonPressed; // Holds the state of the left mouse button.
    bool wasDragged; // The widget has been dragged (neded to separate click on page up/down and drag action).
};

OsThumbPrivate::OsThumbPrivate(OsThumb *q) : q_ptr(q)
{
    /*
     *  Initialize variables used only in implementation.
     */
    alpha = 0;
    animationTimer = new QTimer(q);
    hideTimer = new QTimer(q);
    hideTimer->setSingleShot(true);
    wasDragged = false;
}

void OsThumbPrivate::checkForLeave(QMouseEvent *event)
{
    Q_Q(OsThumb);

    if (mouseButtonPressed) {
        return;
    }
    if (orientation == Qt::Vertical) {
        if ((event->globalX() >= q->geometry().x() + q->width()) ||
            (event->globalY() < q->geometry().y()) ||
            (event->globalY() >= q->geometry().y() + q->height())
           )
            hideTimer->start(HIDE_TIMEOUT);
    } else {
        if ((event->globalY() >= q->geometry().y() + q->height()) ||
            (event->globalX() < q->geometry().x()) ||
            (event->globalX() >= q->geometry().x() + q->width())
           )
            hideTimer->start(HIDE_TIMEOUT);
    }
}

OsThumb::OsThumb(Qt::Orientation o, QWidget *parent) :
    QWidget(parent), d_ptr(new OsThumbPrivate(this))
{
    int fh=QApplication::fontMetrics().height();
    thumbWidth=qMax(20, fh);
    thumbWidth=(((int)(thumbWidth/2))*2)+(thumbWidth%2 ? 2 : 0);
    thumbHeight=thumbWidth*3.5;

    /*
     *  Set up orientation.
     */
    setOrientation(o);
    if (o == Qt::Vertical)
        setFixedSize(THUMB_WIDTH, THUMB_HEIGHT);
    else
        setFixedSize(THUMB_HEIGHT, THUMB_WIDTH);

    /*
     *  Set some window properties. The thumb needs to be a separate window with no border, no background
     *  and it needs to bypass the window manager in order to have a custom behaviour.
     *  (Thank you Andrea for the idea)
     */
    setWindowFlags(Qt::X11BypassWindowManagerHint);
    setAttribute(Qt::WA_TranslucentBackground);

    /*
     *  Initialize class properties.
     */
    setHidden(false);
    setMouseTracking(true);

//    /*
//     *  Initialize variables used only in implementation.
//     */
//    alpha = 0;
//    animationTimer = new QTimer(this);
//    hideTimer = new QTimer(this);
//    hideTimer->setSingleShot(true);
//    wasDragged = false;

    /*
     *  Connect the animationTimer to the update() method so that the paintEvent() is triggered and the thumb is
     *  redrawn with a new alpha. The timer stops when alpha reaches 255 (fade in) or 0 (fade out). Also connect
     *  the hideTimer so that when the mouse pointer leaves the thumb it hides after 200 ms if there is no other
     *  action performed on the thumb.
     */
    QObject::connect(d_ptr->animationTimer, SIGNAL(timeout()), this, SLOT(update()));
    QObject::connect(d_ptr->hideTimer, SIGNAL(timeout()), this, SLOT(hide()));
}

void OsThumb::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)
    Q_D(OsThumb);
    /*
         *  Initialize the painter and set up the colors that are going to be used for drawing the thumb's
         *  elements.
         */
        QPainter painter(this);
        QPalette palette;
        QColor thumbStartColor = QColor::fromRgb(0xf2f1f0);
        QColor thumbStopColor = QColor::fromRgb(0xd2dedc);
        QColor outlineColor = palette.color(QPalette::Highlight);
        QColor arrowColor(Qt::black);
        QColor etchColor(Qt::white);
        QLinearGradient fill(1, 1, THUMB_WIDTH - 2, THUMB_HEIGHT - 2);

        int start=(THUMB_WIDTH/4)*1.5;
        int size=THUMB_WIDTH/5;
        QPoint pgUpPoints[] = {
            QPoint(THUMB_WIDTH/2, start),
            QPoint(THUMB_WIDTH/2 + size, start+size+1),
            QPoint(THUMB_WIDTH/2 - size, start+size+1)
        };
        QPoint pgDownPoints[] = {
            QPoint(THUMB_WIDTH/2, THUMB_HEIGHT - start/*75*/),
            QPoint(THUMB_WIDTH/2 + size, THUMB_HEIGHT - (start+size+1)/*69*/),
            QPoint(THUMB_WIDTH/2 - size, THUMB_HEIGHT - (start+size+1)/*69*/)
        };

        /*
         *  The folowing condition is responsible for the alpha setting during the animation. If the thumb
         *  is hidden that means that is about to be drawn therefor we increment the alpha value by
         *  ANIMATION_ALPHA_INC, once the alpha is at it's maximum value (255) we stop the timer. If the
         *  thumb is shown (!hidden) we decrement the alpha value by ANIMATION_ALPHA_INC until it reaches
         *  it's minimum (0), at which time we stop the timer and hide the widget.
         */
        if (hidden())
        {
            if (d->alpha >= 255)
            {
                d->animationTimer->stop();
            }
            else
                d->alpha += ANIMATION_ALPHA_INC;
            if (d->alpha>100) {
                emit showing();
            }
        }
        else
        {
            if (d->alpha <= 0)
            {
                d->animationTimer->stop();
                setVisible(false);
            }
            else
                d->alpha -= ANIMATION_ALPHA_INC;
            if (d->alpha<100) {
                emit hiding();
            }
        }

        /*
         *  Set the current alpha for each individual color of the widget.
         */
        thumbStartColor.setAlpha(d->alpha);
        thumbStopColor.setAlpha(d->alpha);
        outlineColor.setAlpha(d->alpha);
        arrowColor.setAlpha(d->alpha*0.5);
        etchColor.setAlpha(d->alpha*0.5);

        /*
         *  Set up the gradient used for filling the widget
         */
        fill.setColorAt(0, thumbStartColor);
        fill.setColorAt(0.5, thumbStopColor);
        fill.setColorAt(1, thumbStartColor);

        /*
         *  Save the current painter state onto the stack
         */
        painter.save();

        /*
         *  Set up antialiasing
         */
        painter.setRenderHint(QPainter::Antialiasing);

        /*
         *  Check if the widget is horizontal, and if it is apply required transformations to the painter and thumb
         */
        if (orientation() == Qt::Horizontal)
        {
            painter.translate(THUMB_HEIGHT, 0);
            painter.rotate(90);
        }

        /*
         *  Draw the widget's contents
         */
        painter.setPen(Qt::NoPen);
        painter.setBrush(QBrush(fill));
        painter.drawRect(1, 1, THUMB_WIDTH - 2, THUMB_HEIGHT - 2);

        painter.setPen(QPen(outlineColor, OUTLINE_THICKNESS));
        painter.setBrush(Qt::NoBrush);
        painter.drawRect(1, 1, THUMB_WIDTH - 2, THUMB_HEIGHT - 2);

        painter.setPen(Qt::NoPen);
        painter.setBrush(QBrush(arrowColor, Qt::SolidPattern));
        painter.drawConvexPolygon(pgUpPoints, 3);
        painter.drawConvexPolygon(pgDownPoints, 3);

        arrowColor.setAlphaF(arrowColor.alphaF()*0.4);
        painter.setPen(arrowColor);
        painter.drawLine(QPointF(3.5, (THUMB_HEIGHT/2)+0.5), QPointF(THUMB_WIDTH-3.5, (THUMB_HEIGHT/2)+0.5));
        painter.setPen(etchColor);
        painter.drawLine(QPointF(3.5, (THUMB_HEIGHT/2)+1.5), QPointF(THUMB_WIDTH-3.5, (THUMB_HEIGHT/2)+1.5));

        // TODO: Draw dots...

        /*
         *  Pop the new painter state from the stack
         */
        painter.restore();
}

void OsThumb::show()
{
    Q_D(OsThumb);
    /*
     *  Show the thumb (in case it was not already shown), in case it was fading out stop the animation,
     *  set the thumb's current state (visibility wise) (hidden property) to true in order for
     *  the paintEvent to start incrementing the alpha value of the thumb, and start the animation again.
     */
    setVisible(true);
    d->animationTimer->stop();
    setHidden(true);
    d->animationTimer->start(ANIMATION_TIMEOUT);
}

void OsThumb::hide()
{
    Q_D(OsThumb);
    /*
     *  In case it was fading out stop the animation, set the thumb's current state (visibility wise)
     *  (hidden property) to false in order for the paintEvent to start decrementing the alpha value
     *  of the thumb, and start the animation again.
     */
    d->animationTimer->stop();
    setHidden(false);
    d->animationTimer->start(ANIMATION_TIMEOUT);
}

void OsThumb::mousePressEvent(QMouseEvent *event)
{
    Q_D(OsThumb);
    d->hideTimer->stop();
    /*
     *  Verify that the left mouse button was pressed and save the position at which the event
     *  occurred. Set the mouseButtonPressed property to true in order to initiate dragging in case
     *  the user moves the mouse (mouseMoveEvent occurres) while the left mouse button is pressed.
     */
    if (event->button() == Qt::LeftButton)
    {
        d->yPos = event->pos().y();
        d->xPos = event->pos().x();
        d->mouseButtonPressed = true;
    }
    //else QApplication::quit();
}

void OsThumb::mouseMoveEvent(QMouseEvent *event)
{
    Q_D(OsThumb);

    d->hideTimer->stop();
    d->checkForLeave(event);
    /*
     *  If the mouse button is pressed and the user starts moving the mouse; move the thumb
     *  to the mouse's Y position (vertical) or X position (horizontal). Also don't move the thumb
     *  outside the proximity region's boundries. Set wasDragged property to true so that when
     *  the mouseReleaseEvent occurs don't initiate a pageUp or pageDown movement within the
     *  scrollable content.
     */
    if (d->mouseButtonPressed) {
        int toXPos = x();
        int toYPos = y();

        if (orientation() == Qt::Vertical) {
            // vertical movement
            toYPos = event->globalY() - d->yPos;
            int minYPos = minimum();
            int maxYPos = maximum();

            if (toYPos < minYPos)
                toYPos = minYPos;
            if (toYPos > maxYPos)
                toYPos = maxYPos;
            // end vertical movement
        } else {
            // horizontal movement
            toXPos = event->globalX() - d->xPos;
            int minXPos = minimum();
            int maxXPos = maximum();

            if (toXPos < minXPos)
                toXPos = minXPos;
            if (toXPos > maxXPos)
                toXPos = maxXPos;
            // end horizontal movement
        }

        move(toXPos, toYPos);

        d->wasDragged = true;

        emit thumbDragged(QPoint(toXPos,toYPos));
    }
}

void OsThumb::mouseReleaseEvent(QMouseEvent *event)
{
    Q_D(OsThumb);

    d->hideTimer->stop();

    /*
     *  When the mouse button is released set mouseButton pressed to false, verify if the thumb has beed dragged
     *  if it has then check if the mouse pointer has left the thumb's surface and hide it or not accordingly.
     */
    d->mouseButtonPressed = false;

    if (d->wasDragged) {
        d->wasDragged = false;

        d->checkForLeave(event);

    /*
     *  If the thumb wasn't dragged, based on orientation, the user clicked the top/bottom, lef/right arrow and
     *  the content should scroll one page in the respective direction.
     */
    } else {
        if (orientation() == Qt::Vertical)
        {
            if (event->y() < THUMB_HEIGHT / 2)
                emit pageUp();
            if (event->y() > THUMB_HEIGHT / 2)
                emit pageDown();
        }
        else
        {
            if (event->x() < THUMB_HEIGHT / 2)
                emit pageUp();
            if (event->x() > THUMB_HEIGHT / 2)
                emit pageDown();
        }
    }
}

bool OsThumb::isVisible() const
{
    return hidden();
}

bool OsThumb::mouseButtonPressed() const
{
    Q_D(const OsThumb);
    return d->mouseButtonPressed;
}

Qt::Orientation OsThumb::orientation() const
{
    Q_D(const OsThumb);
    return d->orientation;
}

void OsThumb::setOrientation(Qt::Orientation o)
{
    Q_D(OsThumb);
    d->orientation = o;
    if (o == Qt::Vertical)
        setFixedSize(THUMB_WIDTH, THUMB_HEIGHT);
    else
        setFixedSize(THUMB_HEIGHT, THUMB_WIDTH);
}

int OsThumb::minimum() const
{
    Q_D(const OsThumb);
    return d->minimum;
}

int OsThumb::maximum() const
{
    Q_D(const OsThumb);
    return d->maximum;
}

void OsThumb::setMinimum(int minimum)
{
    Q_D(OsThumb);
    d->minimum = minimum;
}

void OsThumb::setMaximum(int maximum)
{
    Q_D(OsThumb);
    d->maximum = maximum;
}

bool OsThumb::hidden() const
{
    Q_D(const OsThumb);
    return d->hidden;
}

void OsThumb::setHidden(bool hidden)
{
    Q_D(OsThumb);
    d->hidden = hidden;
}
