//////////////////////////////////////////////////////////////////////////////
// oxygentoolbardata.cpp
// stores event filters and maps widgets to timelines for animations
// -------------------
//
// SPDX-FileCopyrightText: 2009 Hugo Pereira Da Costa <hugo.pereira@free.fr>
//
// SPDX-License-Identifier: MIT
//////////////////////////////////////////////////////////////////////////////

#include "oxygentoolbardata.h"

#include <QToolButton>

namespace Oxygen
{
//________________________________________________________________________
ToolBarData::ToolBarData(QObject *parent, QWidget *target, int duration)
    : AnimationData(parent, target)
    , _opacity(0)
    , _progress(0)
    , _currentObject(nullptr)
    , _entered(false)
{
    target->installEventFilter(this);

    _animation = new Animation(duration, this);
    animation().data()->setDirection(Animation::Forward);
    animation().data()->setStartValue(0.0);
    animation().data()->setEndValue(1.0);
    animation().data()->setTargetObject(this);
    animation().data()->setPropertyName("opacity");

    // progress animation
    _progressAnimation = new Animation(duration, this);
    progressAnimation().data()->setDirection(Animation::Forward);
    progressAnimation().data()->setStartValue(0);
    progressAnimation().data()->setEndValue(1);
    progressAnimation().data()->setTargetObject(this);
    progressAnimation().data()->setPropertyName("progress");
    progressAnimation().data()->setEasingCurve(QEasingCurve::Linear);

    // add all children widgets to event handler
    const auto children = target->children();
    for (QObject *child : children) {
        if (qobject_cast<QToolButton *>(child))
            childAddedEvent(child);
    }
}

//______________________________________________
bool ToolBarData::eventFilter(QObject *object, QEvent *event)
{
    // check object
    const QObject *targetData = target().data();
    if (object == targetData) {
        switch (event->type()) {
        case QEvent::Enter: {
            if (enabled()) {
                object->event(event);
                enterEvent(object);
                return true;
            } else
                return false;
        }

        case QEvent::ChildAdded: {
            // add children even in disabled case, to make sure they
            // are properly registered when engine is enabled
            QChildEvent *childEvent(static_cast<QChildEvent *>(event));
            childAddedEvent(childEvent->child());
            break;
        }

        default:
            break;
        }

    } else if (object->parent() == targetData) {
        if (!enabled())
            return false;

        switch (event->type()) {
        case QEvent::HoverEnter:
            childEnterEvent(object);
            break;

        case QEvent::HoverLeave:
            if (currentObject() && !_timer.isActive())
                _timer.start(100, this);
            break;

        default:
            break;
        }
    }

    return false;
}

//____________________________________________________________
void ToolBarData::updateAnimatedRect(void)
{
    // check rect validity
    if (currentRect().isNull() || previousRect().isNull()) {
        _animatedRect = QRect();
        return;
    }

    // compute rect located 'between' previous and current
    _animatedRect.setLeft(previousRect().left() + progress() * (currentRect().left() - previousRect().left()));
    _animatedRect.setRight(previousRect().right() + progress() * (currentRect().right() - previousRect().right()));
    _animatedRect.setTop(previousRect().top() + progress() * (currentRect().top() - previousRect().top()));
    _animatedRect.setBottom(previousRect().bottom() + progress() * (currentRect().bottom() - previousRect().bottom()));

    // trigger update
    setDirty();
    return;
}

//________________________________________________________________________
void ToolBarData::enterEvent(const QObject *)
{
    if (_timer.isActive())
        _timer.stop();
    if (animation().data()->isRunning())
        animation().data()->stop();
    if (progressAnimation().data()->isRunning())
        progressAnimation().data()->stop();
    clearPreviousRect();
    clearAnimatedRect();

    return;
}

//________________________________________________________________________
void ToolBarData::leaveEvent(const QObject *)
{
    if (progressAnimation().data()->isRunning())
        progressAnimation().data()->stop();
    if (animation().data()->isRunning())
        animation().data()->stop();
    clearAnimatedRect();
    clearPreviousRect();

    if (currentObject()) {
        clearCurrentObject();
        animation().data()->setDirection(Animation::Backward);
        animation().data()->start();
    }

    return;
}

//________________________________________________________________________
void ToolBarData::childEnterEvent(const QObject *object)
{
    if (object == currentObject())
        return;

    const QToolButton *local = qobject_cast<const QToolButton *>(object);

    // check if current position match another action
    if (local && local->isEnabled()) {
        if (_timer.isActive())
            _timer.stop();

        // get rect
        QRect activeRect(local->rect().translated(local->mapToParent(QPoint(0, 0))));

        // update previous rect if the current action is valid
        if (currentObject()) {
            if (!progressAnimation().data()->isRunning()) {
                setPreviousRect(currentRect());

            } else if (progress() < 1 && currentRect().isValid() && previousRect().isValid()) {
                // re-calculate previous rect so that animatedRect
                // is unchanged after currentRect is updated
                // this prevents from having jumps in the animation
                qreal ratio = progress() / (1.0 - progress());
                _previousRect.adjust(ratio * (currentRect().left() - activeRect.left()),
                                     ratio * (currentRect().top() - activeRect.top()),
                                     ratio * (currentRect().right() - activeRect.right()),
                                     ratio * (currentRect().bottom() - activeRect.bottom()));
            }

            // update current action
            setCurrentObject(local);
            setCurrentRect(activeRect);
            if (animation().data()->isRunning())
                animation().data()->stop();
            if (!progressAnimation().data()->isRunning())
                progressAnimation().data()->start();

        } else {
            setCurrentObject(local);
            setCurrentRect(activeRect);
            if (!_entered) {
                _entered = true;
                if (animation().data()->isRunning())
                    animation().data()->stop();
                if (!progressAnimation().data()->isRunning())
                    progressAnimation().data()->start();

            } else {
                setPreviousRect(activeRect);
                clearAnimatedRect();
                if (progressAnimation().data()->isRunning())
                    progressAnimation().data()->stop();
                animation().data()->setDirection(Animation::Forward);
                if (!animation().data()->isRunning())
                    animation().data()->start();
            }
        }

    } else if (currentObject()) {
        if (!_timer.isActive())
            _timer.start(100, this);
    }

    return;
}

//___________________________________________________________________
void ToolBarData::childAddedEvent(QObject *object)
{
    QWidget *widget(qobject_cast<QWidget *>(object));
    if (!widget)
        return;

    // add connections
    connect(animation().data(), SIGNAL(valueChanged(QVariant)), widget, SLOT(update()), Qt::UniqueConnection);
    connect(progressAnimation().data(), SIGNAL(valueChanged(QVariant)), widget, SLOT(update()), Qt::UniqueConnection);

    // add event filter
    widget->removeEventFilter(this);
    widget->installEventFilter(this);
}

//___________________________________________________________
void ToolBarData::timerEvent(QTimerEvent *event)
{
    if (event->timerId() != _timer.timerId())
        return AnimationData::timerEvent(event);
    _timer.stop();
    leaveEvent(target().data());
}
}
