/*************************************************************************
 * Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr>    *
 *                                                                       *
 * 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 .        *
 *************************************************************************/

#include "darklyscrollbardata.h"

#include <QHoverEvent>
#include <QScrollBar>
#include <QStyleOptionSlider>
#include <QTimer>

Q_GUI_EXPORT QStyleOptionSlider qt_qscrollbarStyleOption(QScrollBar *);

namespace Darkly
{

static const int s_transientTimerInterval = 2000;

inline static QList<QScrollBar *> scrollAreaScrollBars(QObject *target)
{
    QList<QScrollBar *> scrollBars;
    auto scrollBarContainer = target->parent();
    QAbstractScrollArea *scrollArea = nullptr;
    if (scrollBarContainer) {
        scrollArea = qobject_cast<QAbstractScrollArea *>(scrollBarContainer->parent());
    }
    if (scrollArea) {
        if (scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff && scrollArea->verticalScrollBar()) {
            scrollBars << scrollArea->verticalScrollBar();
        }
        if (scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff && scrollArea->horizontalScrollBar()) {
            scrollBars << scrollArea->horizontalScrollBar();
        }
    }
    return scrollBars;
}


//______________________________________________
ScrollBarData::ScrollBarData(QObject *parent, QWidget *target, int duration)
    : WidgetStateData(parent, target, duration)
    , _position(-1, -1)
{
    target->installEventFilter(this);

    _addLineData._animation = new Animation(duration, this);
    _subLineData._animation = new Animation(duration, this);
    _grooveData._animation = new Animation(duration, this);
    _transientAnimationShowDuration = duration;
    _transientAnimationHideDuration = 1000;
    _transientAnimation = new Animation(duration, this);


    connect(addLineAnimation().data(), &QAbstractAnimation::finished, this, &ScrollBarData::clearAddLineRect);
    connect(subLineAnimation().data(), &QAbstractAnimation::finished, this, &ScrollBarData::clearSubLineRect);

    // setup animation
    setupAnimation(addLineAnimation(), "addLineOpacity");
    setupAnimation(subLineAnimation(), "subLineOpacity");
    setupAnimation(grooveAnimation(), "grooveOpacity");
    setupAnimation(_transientAnimation, "transientOpacity");

    _transientTimer = new QTimer(this);
    _transientTimer->setSingleShot(true);
    _transientTimer->setInterval(duration + s_transientTimerInterval);
    connect(_transientTimer, &QTimer::timeout, this, [this]() {
        const auto scrollBars = scrollAreaScrollBars(this->target());

        // Don't hide scrollbar, if at least of one is active
        for (auto &scrollBar : scrollBars) {
            QStyleOptionSlider opt(qt_qscrollbarStyleOption(scrollBar));
            if (opt.state & QStyle::State_MouseOver) {
                _transientTimer->start();
                return;
            }
        }

        _transientAnimation->setDirection(Animation::Backward);
        if (!_transientAnimation->isRunning()) {
            _transientAnimation->setDuration(_transientAnimationHideDuration);
            _transientAnimation->start();
        }
    });

    const auto scrollBars = scrollAreaScrollBars(target);
    for (auto &scrollBar : scrollBars) {
        connect(scrollBar, &QScrollBar::actionTriggered, this, [this]() {
            transientUpdate();
        });
    }
}

//______________________________________________
inline static void updateScrollBarGeometry(QObject *object)
{
    auto scrollBar = qobject_cast<QScrollBar *>(object);
    if (!scrollBar) {
        return;
    }

    QStyleOptionSlider opt(qt_qscrollbarStyleOption(scrollBar));
    if (!scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, &opt, scrollBar)) {
        return;
    }

    scrollBar->updateGeometry();

}

//______________________________________________
bool ScrollBarData::eventFilter(QObject *object, QEvent *event)
{
    if (object != target().data()) {
        return WidgetStateData::eventFilter(object, event);
    }

    // check event type
    switch (event->type()) {
    case QEvent::HoverEnter:
        updateScrollBarGeometry(object);
        setGrooveHovered(true);
        grooveAnimation().data()->setDirection(Animation::Forward);
        if (!grooveAnimation().data()->isRunning())
            grooveAnimation().data()->start();
        break;

    case QEvent::HoverMove:
        hoverMoveEvent(object, event);
        break;

    case QEvent::HoverLeave:
        updateScrollBarGeometry(object);
        setGrooveHovered(false);
        grooveAnimation().data()->setDirection(Animation::Backward);
        if (!grooveAnimation().data()->isRunning())
            grooveAnimation().data()->start();
        hoverLeaveEvent(object, event);
        break;

    default:
        break;
    }

    return WidgetStateData::eventFilter(object, event);
}

//______________________________________________
const Animation::Pointer &ScrollBarData::animation(QStyle::SubControl subcontrol) const
{
    switch (subcontrol) {
    default:
    case QStyle::SC_ScrollBarSlider:
        return animation();

    case QStyle::SC_ScrollBarAddLine:
        return addLineAnimation();

    case QStyle::SC_ScrollBarSubLine:
        return subLineAnimation();

    case QStyle::SC_ScrollBarGroove:
        return grooveAnimation();
    }
}

//______________________________________________
qreal ScrollBarData::opacity(QStyle::SubControl subcontrol) const
{
    switch (subcontrol) {
    default:
    case QStyle::SC_ScrollBarSlider:
        return opacity();

    case QStyle::SC_ScrollBarAddLine:
        return addLineOpacity();

    case QStyle::SC_ScrollBarSubLine:
        return subLineOpacity();

    case QStyle::SC_ScrollBarGroove:
        return grooveOpacity();
    }
}
//______________________________________________
void ScrollBarData::setDuration(int duration)
{
    WidgetStateData::setDuration(duration);
    addLineAnimation().data()->setDuration(duration);
    subLineAnimation().data()->setDuration(duration);
    grooveAnimation().data()->setDuration(duration);
    _transientAnimation->setDuration(duration);
    _transientTimer->setInterval(duration + s_transientTimerInterval);
}

//______________________________________________
void ScrollBarData::transientUpdate()
{
    auto scrollBar = static_cast<QScrollBar *>(target().data());
    if (!scrollBar) {
        return;
    }

    QStyleOptionSlider opt(qt_qscrollbarStyleOption(scrollBar));
    if (!scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, &opt, scrollBar)) {
        return;
    }

    _transientAnimation->setDirection(Animation::Forward);
    if (!_transientAnimation->isRunning() && !_transientTimer->isActive()) {
        _transientAnimation->setDuration(_transientAnimationShowDuration);
        _transientAnimation->start();
    }
    _transientTimer->start();
}

//______________________________________________
void ScrollBarData::hoverMoveEvent(QObject *object, QEvent *event)
{
    // try cast object to scrollbar
    QScrollBar *scrollBar(qobject_cast<QScrollBar *>(object));
    if (!scrollBar || scrollBar->isSliderDown())
        return;

    // retrieve scrollbar option
    QStyleOptionSlider opt(qt_qscrollbarStyleOption(scrollBar));

    // cast event
    QHoverEvent *hoverEvent = static_cast<QHoverEvent *>(event);

    QStyle::SubControl hoverControl =
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        scrollBar->style()->hitTestComplexControl(QStyle::CC_ScrollBar, &opt, hoverEvent->position().toPoint(), scrollBar);
#else
        scrollBar->style()->hitTestComplexControl(QStyle::CC_ScrollBar, &opt, hoverEvent->pos(), scrollBar);
#endif
    // update hover state
    updateAddLineArrow(hoverControl);
    updateSubLineArrow(hoverControl);

    // store position
    _position =
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        hoverEvent->position().toPoint();
#else
        hoverEvent->pos();
#endif
}

//______________________________________________
void ScrollBarData::hoverLeaveEvent(QObject *, QEvent *)
{
    // reset hover state
    updateSubLineArrow(QStyle::SC_None);
    updateAddLineArrow(QStyle::SC_None);

    // reset mouse position
    _position = QPoint(-1, -1);
}

//_____________________________________________________________________
void ScrollBarData::updateSubLineArrow(QStyle::SubControl hoverControl)
{
    if (hoverControl == QStyle::SC_ScrollBarSubLine) {
        if (!subLineArrowHovered()) {
            setSubLineArrowHovered(true);
            if (enabled()) {
                subLineAnimation().data()->setDirection(Animation::Forward);
                if (!subLineAnimation().data()->isRunning())
                    subLineAnimation().data()->start();
            } else
                setDirty();
        }

    } else {
        if (subLineArrowHovered()) {
            setSubLineArrowHovered(false);
            if (enabled()) {
                subLineAnimation().data()->setDirection(Animation::Backward);
                if (!subLineAnimation().data()->isRunning())
                    subLineAnimation().data()->start();
            } else
                setDirty();
        }
    }
}

//_____________________________________________________________________
void ScrollBarData::updateAddLineArrow(QStyle::SubControl hoverControl)
{
    if (hoverControl == QStyle::SC_ScrollBarAddLine) {
        if (!addLineArrowHovered()) {
            setAddLineArrowHovered(true);
            if (enabled()) {
                addLineAnimation().data()->setDirection(Animation::Forward);
                if (!addLineAnimation().data()->isRunning())
                    addLineAnimation().data()->start();
            } else
                setDirty();
        }

    } else {
        if (addLineArrowHovered()) {
            setAddLineArrowHovered(false);
            if (enabled()) {
                addLineAnimation().data()->setDirection(Animation::Backward);
                if (!addLineAnimation().data()->isRunning())
                    addLineAnimation().data()->start();
            } else
                setDirty();
        }
    }
}

}
