// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant

#include "private/qparallelanimationgroupjob_p.h"
#include "private/qanimationjobutil_p.h"

QT_BEGIN_NAMESPACE

QParallelAnimationGroupJob::QParallelAnimationGroupJob()
    : QAnimationGroupJob()
    , m_previousLoop(0)
    , m_previousCurrentTime(0)
{
}

QParallelAnimationGroupJob::~QParallelAnimationGroupJob()
{
}

int QParallelAnimationGroupJob::duration() const
{
    int ret = 0;

    for (const QAbstractAnimationJob *animation : m_children) {
        int currentDuration = animation->totalDuration();
        if (currentDuration == -1)
            return -1; // Undetermined length
        ret = qMax(ret, currentDuration);
    }

    return ret;
}

void QParallelAnimationGroupJob::updateCurrentTime(int /*currentTime*/)
{
    if (m_children.isEmpty())
        return;

    if (m_currentLoop > m_previousLoop) {
        // simulate completion of the loop
        int dura = duration();
        if (dura < 0) {
            // For an uncontrolled parallel group, we need to simulate the end of running animations.
            // As uncontrolled animation finish time is already reset for this next loop, we pick the
            // longest of the known stop times.
            for (QAbstractAnimationJob *animation : m_children) {
                int currentDuration = animation->totalDuration();
                if (currentDuration >= 0)
                    dura = qMax(dura, currentDuration);
            }
        }
        if (dura > 0) {
            for (QAbstractAnimationJob *animation : m_children) {
                if (!animation->isStopped())
                    RETURN_IF_DELETED(animation->setCurrentTime(dura));   // will stop
            }
        }
    } else if (m_currentLoop < m_previousLoop) {
        // simulate completion of the loop seeking backwards
        for (QAbstractAnimationJob *animation : m_children) {
            //we need to make sure the animation is in the right state
            //and then rewind it
            applyGroupState(animation);
            RETURN_IF_DELETED(animation->setCurrentTime(0));
            animation->stop();
        }
    }

    // finally move into the actual time of the current loop
    for (QAbstractAnimationJob *animation : m_children) {
        const int dura = animation->totalDuration();
        //if the loopcount is bigger we should always start all animations
        if (m_currentLoop > m_previousLoop
            //if we're at the end of the animation, we need to start it if it wasn't already started in this loop
            //this happens in Backward direction where not all animations are started at the same time
            || shouldAnimationStart(animation, m_previousCurrentTime > dura /*startIfAtEnd*/)) {
            applyGroupState(animation);
        }

        if (animation->state() == state()) {
            RETURN_IF_DELETED(animation->setCurrentTime(m_currentTime));
            if (dura > 0 && m_currentTime > dura)
                animation->stop();
        }
    }
    m_previousLoop = m_currentLoop;
    m_previousCurrentTime = m_currentTime;
}

void QParallelAnimationGroupJob::updateState(QAbstractAnimationJob::State newState,
                                          QAbstractAnimationJob::State oldState)
{
    QAnimationGroupJob::updateState(newState, oldState);

    switch (newState) {
    case Stopped:
        for (QAbstractAnimationJob *animation : m_children)
            animation->stop();
        break;
    case Paused:
        for (QAbstractAnimationJob *animation : m_children)
            if (animation->isRunning())
                animation->pause();
        break;
    case Running:
        for (QAbstractAnimationJob *animation : m_children) {
            if (oldState == Stopped) {
                animation->stop();
                m_previousLoop = m_direction == Forward ? 0 : m_loopCount - 1;
            }
            RETURN_IF_DELETED(resetUncontrolledAnimationFinishTime(animation));
            animation->setDirection(m_direction);
            if (shouldAnimationStart(animation, oldState == Stopped))
                RETURN_IF_DELETED(animation->start());
        }
        break;
    }
}

bool QParallelAnimationGroupJob::shouldAnimationStart(QAbstractAnimationJob *animation, bool startIfAtEnd) const
{
    const int dura = animation->totalDuration();

    if (dura == -1)
        return uncontrolledAnimationFinishTime(animation) == -1;

    if (startIfAtEnd)
        return m_currentTime <= dura;
    if (m_direction == Forward)
        return m_currentTime < dura;
    else //direction == Backward
        return m_currentTime && m_currentTime <= dura;
}

void QParallelAnimationGroupJob::applyGroupState(QAbstractAnimationJob *animation)
{
    switch (m_state)
    {
    case Running:
        animation->start();
        break;
    case Paused:
        animation->pause();
        break;
    case Stopped:
    default:
        break;
    }
}

void QParallelAnimationGroupJob::updateDirection(QAbstractAnimationJob::Direction direction)
{
    //we need to update the direction of the current animation
    if (!isStopped()) {
        for (QAbstractAnimationJob *animation : m_children) {
            animation->setDirection(direction);
        }
    } else {
        if (direction == Forward) {
            m_previousLoop = 0;
            m_previousCurrentTime = 0;
        } else {
            // Looping backwards with loopCount == -1 does not really work well...
            m_previousLoop = (m_loopCount == -1 ? 0 : m_loopCount - 1);
            m_previousCurrentTime = duration();
        }
    }
}

void QParallelAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation)
{
    Q_ASSERT(animation && (animation->duration() == -1 || animation->loopCount() < 0));
    int uncontrolledRunningCount = 0;

    for (QAbstractAnimationJob *child : m_children) {
        if (child == animation) {
            setUncontrolledAnimationFinishTime(animation, animation->currentTime());
        } else if (child->duration() == -1 || child->loopCount() < 0) {
            if (uncontrolledAnimationFinishTime(child) == -1)
                ++uncontrolledRunningCount;
        }
    }

    if (uncontrolledRunningCount > 0)
        return;

    int maxDuration = 0;
    bool running = false;
    for (QAbstractAnimationJob *job : m_children) {
        if (job->state() == Running)
            running = true;
        maxDuration = qMax(maxDuration, job->totalDuration());
    }

    setUncontrolledAnimationFinishTime(this, qMax(maxDuration + m_currentLoopStartTime, currentTime()));

    if (!running
            && ((m_direction == Forward && m_currentLoop == m_loopCount -1)
                || (m_direction == Backward && m_currentLoop == 0))) {
        stop();
    }
}

void QParallelAnimationGroupJob::debugAnimation(QDebug d) const
{
    d << "ParallelAnimationGroupJob(" << Qt::hex << (const void *) this << Qt::dec << ")";

    debugChildren(d);
}

QT_END_NAMESPACE

