/*
 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "kitemlistviewanimation.h"
#include "kitemviews/kitemlistview.h"

#include <QPropertyAnimation>

KItemListViewAnimation::KItemListViewAnimation(QObject *parent)
    : QObject(parent)
    , m_scrollOrientation(Qt::Vertical)
    , m_scrollOffset(0)
    , m_animation()
{
}

KItemListViewAnimation::~KItemListViewAnimation()
{
    for (const auto &type : m_animation) {
        qDeleteAll(type);
    }
}

void KItemListViewAnimation::setScrollOrientation(Qt::Orientation orientation)
{
    m_scrollOrientation = orientation;
}

Qt::Orientation KItemListViewAnimation::scrollOrientation() const
{
    return m_scrollOrientation;
}

void KItemListViewAnimation::setScrollOffset(qreal offset)
{
    const qreal diff = m_scrollOffset - offset;
    m_scrollOffset = offset;

    // The change of the offset requires that the position of all
    // animated QGraphicsWidgets get adjusted. An exception is made
    // for the delete animation that should just fade away on the
    // existing position.
    for (int type = 0; type < AnimationTypeCount; ++type) {
        if (type == DeleteAnimation) {
            continue;
        }

        QHashIterator<QGraphicsWidget *, QPropertyAnimation *> it(m_animation[type]);
        while (it.hasNext()) {
            it.next();

            QGraphicsWidget *widget = it.key();
            QPropertyAnimation *propertyAnim = it.value();

            QPointF currentPos = widget->pos();
            if (m_scrollOrientation == Qt::Vertical) {
                currentPos.ry() += diff;
            } else {
                currentPos.rx() += diff;
            }

            if (type == MovingAnimation) {
                // Stop the animation, calculate the moved start- and end-value
                // and restart the animation for the remaining duration.
                const int remainingDuration = propertyAnim->duration() - propertyAnim->currentTime();

                const bool block = propertyAnim->signalsBlocked();
                propertyAnim->blockSignals(true);
                propertyAnim->stop();

                QPointF endPos = propertyAnim->endValue().toPointF();
                if (m_scrollOrientation == Qt::Vertical) {
                    endPos.ry() += diff;
                } else {
                    endPos.rx() += diff;
                }

                propertyAnim->setDuration(remainingDuration);
                propertyAnim->setStartValue(currentPos);
                propertyAnim->setEndValue(endPos);
                propertyAnim->start();
                propertyAnim->blockSignals(block);
            } else {
                widget->setPos(currentPos);
            }
        }
    }
}

qreal KItemListViewAnimation::scrollOffset() const
{
    return m_scrollOffset;
}

void KItemListViewAnimation::start(QGraphicsWidget *widget, AnimationType type, const QVariant &endValue)
{
    stop(widget, type);

    QPropertyAnimation *propertyAnim = nullptr;
    const int animationDuration = widget->style()->styleHint(QStyle::SH_Widget_Animate) ? 200 : 1;

    switch (type) {
    case MovingAnimation: {
        const QPointF newPos = endValue.toPointF();
        if (newPos == widget->pos()) {
            return;
        }

        propertyAnim = new QPropertyAnimation(widget, "pos");
        propertyAnim->setDuration(animationDuration);
        propertyAnim->setEndValue(newPos);
        break;
    }

    case CreateAnimation: {
        propertyAnim = new QPropertyAnimation(widget, "opacity");
        propertyAnim->setEasingCurve(QEasingCurve::InQuart);
        propertyAnim->setDuration(animationDuration);
        propertyAnim->setStartValue(0.0);
        propertyAnim->setEndValue(1.0);
        break;
    }

    case DeleteAnimation: {
        propertyAnim = new QPropertyAnimation(widget, "opacity");
        propertyAnim->setEasingCurve(QEasingCurve::OutQuart);
        propertyAnim->setDuration(animationDuration);
        propertyAnim->setStartValue(1.0);
        propertyAnim->setEndValue(0.0);
        break;
    }

    default:
        Q_UNREACHABLE();
        break;
    }

    Q_ASSERT(propertyAnim);
    connect(propertyAnim, &QPropertyAnimation::finished, this, &KItemListViewAnimation::slotFinished);
    m_animation[type].insert(widget, propertyAnim);

    propertyAnim->start();

    Q_EMIT started(widget, type, endValue);
}

void KItemListViewAnimation::stop(QGraphicsWidget *widget, AnimationType type)
{
    QPropertyAnimation *propertyAnim = m_animation[type].value(widget);
    if (propertyAnim) {
        propertyAnim->stop();

        switch (type) {
        case MovingAnimation:
            break;
        case CreateAnimation:
            widget->setOpacity(1.0);
            break;
        case DeleteAnimation:
            widget->setOpacity(0.0);
            break;
        default:
            break;
        }

        m_animation[type].remove(widget);
        delete propertyAnim;

        Q_EMIT finished(widget, type);
    }
}

void KItemListViewAnimation::stop(QGraphicsWidget *widget)
{
    for (int type = 0; type < AnimationTypeCount; ++type) {
        stop(widget, static_cast<AnimationType>(type));
    }
}

bool KItemListViewAnimation::isStarted(QGraphicsWidget *widget, AnimationType type) const
{
    return m_animation[type].value(widget);
}

bool KItemListViewAnimation::isStarted(QGraphicsWidget *widget) const
{
    for (int type = 0; type < AnimationTypeCount; ++type) {
        if (isStarted(widget, static_cast<AnimationType>(type))) {
            return true;
        }
    }
    return false;
}

void KItemListViewAnimation::slotFinished()
{
    QPropertyAnimation *finishedAnim = qobject_cast<QPropertyAnimation *>(sender());
    for (int type = 0; type < AnimationTypeCount; ++type) {
        QMutableHashIterator<QGraphicsWidget *, QPropertyAnimation *> it(m_animation[type]);
        while (it.hasNext()) {
            it.next();
            QPropertyAnimation *propertyAnim = it.value();
            if (propertyAnim == finishedAnim) {
                QGraphicsWidget *widget = it.key();
                it.remove();
                finishedAnim->deleteLater();

                Q_EMIT finished(widget, static_cast<AnimationType>(type));
                return;
            }
        }
    }
    Q_ASSERT(false);
}

#include "moc_kitemlistviewanimation.cpp"
