/*
 * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "AnimationBase.h"

#include "CSSAnimationControllerPrivate.h"
#include "CSSPrimitiveValue.h"
#include "CSSPropertyAnimation.h"
#include "CompositeAnimation.h"
#include "Document.h"
#include "FloatConversion.h"
#include "GeometryUtilities.h"
#include "Logging.h"
#include "RenderBox.h"
#include "RenderStyle.h"
#include "RenderView.h"
#include <algorithm>
#include <wtf/CurrentTime.h>
#include <wtf/Ref.h>

namespace WebCore {

AnimationBase::AnimationBase(const Animation& animation, Element& element, CompositeAnimation& compositeAnimation)
    : m_element(&element)
    , m_compositeAnimation(&compositeAnimation)
    , m_animation(const_cast<Animation&>(animation))
{
    // Compute the total duration
    if (m_animation->iterationCount() > 0)
        m_totalDuration = m_animation->duration() * m_animation->iterationCount();
}

AnimationBase::~AnimationBase() = default;

const RenderStyle& AnimationBase::currentStyle() const
{
    if (auto* renderer = this->renderer())
        return renderer->style();
    return unanimatedStyle();
}

RenderElement* AnimationBase::renderer() const
{
    return m_element ? m_element->renderer() : nullptr;
}

RenderBoxModelObject* AnimationBase::compositedRenderer() const
{
    auto* renderer = this->renderer();
    if (!renderer || !renderer->isComposited())
        return nullptr;
    return downcast<RenderBoxModelObject>(renderer);
}

void AnimationBase::clear()
{
    endAnimation();
    m_element = nullptr;
    m_compositeAnimation = nullptr;
}

void AnimationBase::setNeedsStyleRecalc(Element* element)
{
    if (!element || element->document().renderTreeBeingDestroyed())
        return;

    ASSERT(element->document().pageCacheState() == Document::NotInPageCache);
    element->invalidateStyleAndLayerComposition();
}

double AnimationBase::duration() const
{
    return m_animation->duration();
}

bool AnimationBase::playStatePlaying() const
{
    return m_animation->playState() == AnimPlayStatePlaying;
}

bool AnimationBase::animationsMatch(const Animation& animation) const
{
    return m_animation->animationsMatch(animation);
}

#if !LOG_DISABLED
static const char* nameForState(AnimationBase::AnimationState state)
{
    switch (state) {
    case AnimationBase::AnimationState::New: return "New";
    case AnimationBase::AnimationState::StartWaitTimer: return "StartWaitTimer";
    case AnimationBase::AnimationState::StartWaitStyleAvailable: return "StartWaitStyleAvailable";
    case AnimationBase::AnimationState::StartWaitResponse: return "StartWaitResponse";
    case AnimationBase::AnimationState::Looping: return "Looping";
    case AnimationBase::AnimationState::Ending: return "Ending";
    case AnimationBase::AnimationState::PausedNew: return "PausedNew";
    case AnimationBase::AnimationState::PausedWaitTimer: return "PausedWaitTimer";
    case AnimationBase::AnimationState::PausedWaitStyleAvailable: return "PausedWaitStyleAvailable";
    case AnimationBase::AnimationState::PausedWaitResponse: return "PausedWaitResponse";
    case AnimationBase::AnimationState::PausedRun: return "PausedRun";
    case AnimationBase::AnimationState::Done: return "Done";
    case AnimationBase::AnimationState::FillingForwards: return "FillingForwards";
    }
    return "";
}

static const char* nameForStateInput(AnimationBase::AnimationStateInput input)
{
    switch (input) {
    case AnimationBase::AnimationStateInput::MakeNew: return "MakeNew";
    case AnimationBase::AnimationStateInput::StartAnimation: return "StartAnimation";
    case AnimationBase::AnimationStateInput::RestartAnimation: return "RestartAnimation";
    case AnimationBase::AnimationStateInput::StartTimerFired: return "StartTimerFired";
    case AnimationBase::AnimationStateInput::StyleAvailable: return "StyleAvailable";
    case AnimationBase::AnimationStateInput::StartTimeSet: return "StartTimeSet";
    case AnimationBase::AnimationStateInput::LoopTimerFired: return "LoopTimerFired";
    case AnimationBase::AnimationStateInput::EndTimerFired: return "EndTimerFired";
    case AnimationBase::AnimationStateInput::PauseOverride: return "PauseOverride";
    case AnimationBase::AnimationStateInput::ResumeOverride: return "ResumeOverride";
    case AnimationBase::AnimationStateInput::PlayStateRunning: return "PlayStateRunning";
    case AnimationBase::AnimationStateInput::PlayStatePaused: return "PlayStatePaused";
    case AnimationBase::AnimationStateInput::EndAnimation: return "EndAnimation";
    }
    return "";
}
#endif

void AnimationBase::updateStateMachine(AnimationStateInput input, double param)
{
    if (!m_compositeAnimation)
        return;

    // If we get AnimationStateInput::RestartAnimation then we force a new animation, regardless of state.
    if (input == AnimationStateInput::MakeNew) {
        if (m_animationState == AnimationState::StartWaitStyleAvailable)
            m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(this);
        LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState));
        m_animationState = AnimationState::New;
        m_startTime = std::nullopt;
        m_pauseTime = std::nullopt;
        m_requestedStartTime = 0;
        m_nextIterationDuration = std::nullopt;
        endAnimation();
        return;
    }

    if (input == AnimationStateInput::RestartAnimation) {
        if (m_animationState == AnimationState::StartWaitStyleAvailable)
            m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(this);
        LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState));
        m_animationState = AnimationState::New;
        m_startTime = std::nullopt;
        m_pauseTime = std::nullopt;
        m_requestedStartTime = 0;
        m_nextIterationDuration = std::nullopt;
        endAnimation();

        if (!paused())
            updateStateMachine(AnimationStateInput::StartAnimation, -1);
        return;
    }

    if (input == AnimationStateInput::EndAnimation) {
        if (m_animationState == AnimationState::StartWaitStyleAvailable)
            m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(this);
        LOG(Animations, "%p AnimationState %s -> Done", this, nameForState(m_animationState));
        m_animationState = AnimationState::Done;
        endAnimation();
        return;
    }

    if (input == AnimationStateInput::PauseOverride) {
        if (m_animationState == AnimationState::StartWaitResponse) {
            // If we are in AnimationState::StartWaitResponse, the animation will get canceled before
            // we get a response, so move to the next state.
            endAnimation();
            updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime());
        }
        return;
    }

    if (input == AnimationStateInput::ResumeOverride) {
        if (m_animationState == AnimationState::Looping || m_animationState == AnimationState::Ending) {
            // Start the animation
            startAnimation(beginAnimationUpdateTime() - m_startTime.value_or(0));
        }
        return;
    }

    // Execute state machine
    switch (m_animationState) {
        case AnimationState::New:
            ASSERT(input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::PlayStatePaused);

            if (input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning) {
                m_requestedStartTime = beginAnimationUpdateTime();
                LOG(Animations, "%p AnimationState %s -> StartWaitTimer", this, nameForState(m_animationState));
                m_animationState = AnimationState::StartWaitTimer;
            } else {
                // We are pausing before we even started.
                LOG(Animations, "%p AnimationState %s -> AnimationState::PausedNew", this, nameForState(m_animationState));
                m_animationState = AnimationState::PausedNew;
                m_pauseTime = std::nullopt;
            }

#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
            if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger())
                m_compositeAnimation->animationController().addToAnimationsDependentOnScroll(this);
#endif
            break;
        case AnimationState::StartWaitTimer:
            ASSERT(input == AnimationStateInput::StartTimerFired || input == AnimationStateInput::PlayStatePaused);

            if (input == AnimationStateInput::StartTimerFired) {
                ASSERT(param >= 0);
                // Start timer has fired, tell the animation to start and wait for it to respond with start time
                LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable (time is %f)", this, nameForState(m_animationState), param);
                m_animationState = AnimationState::StartWaitStyleAvailable;
                m_compositeAnimation->animationController().addToAnimationsWaitingForStyle(this);

                // Trigger a render so we can start the animation
                if (m_element)
                    m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element);
            } else {
                ASSERT(!paused());
                // We're waiting for the start timer to fire and we got a pause. Cancel the timer, pause and wait
                m_pauseTime = beginAnimationUpdateTime();
                LOG(Animations, "%p AnimationState %s -> PausedWaitTimer", this, nameForState(m_animationState));
                m_animationState = AnimationState::PausedWaitTimer;
            }
            break;
        case AnimationState::StartWaitStyleAvailable:
            ASSERT(input == AnimationStateInput::StyleAvailable || input == AnimationStateInput::PlayStatePaused);

            if (input == AnimationStateInput::StyleAvailable) {
                // Start timer has fired, tell the animation to start and wait for it to respond with start time
                LOG(Animations, "%p AnimationState %s -> StartWaitResponse (time is %f)", this, nameForState(m_animationState), param);
                m_animationState = AnimationState::StartWaitResponse;

                overrideAnimations();

                // Start the animation
                if (overridden()) {
                    // We won't try to start accelerated animations if we are overridden and
                    // just move on to the next state.
                    LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState));
                    m_animationState = AnimationState::StartWaitResponse;
                    m_isAccelerated = false;
                    updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime());
                } else {
                    double timeOffset = 0;
                    // If the value for 'animation-delay' is negative then the animation appears to have started in the past.
                    if (m_animation->delay() < 0)
                        timeOffset = -m_animation->delay();
                    bool started = startAnimation(timeOffset);

                    m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(this, started);
                    m_isAccelerated = started;
                }
            } else {
                // We're waiting for the style to be available and we got a pause. Pause and wait
                m_pauseTime = beginAnimationUpdateTime();
                LOG(Animations, "%p AnimationState %s -> PausedWaitStyleAvailable", this, nameForState(m_animationState));
                m_animationState = AnimationState::PausedWaitStyleAvailable;
            }
            break;
        case AnimationState::StartWaitResponse:
            ASSERT(input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::PlayStatePaused);

            if (input == AnimationStateInput::StartTimeSet) {
                ASSERT(param > -0.001); // Sometimes Core Animation gives us a beginTime slightly into the future.
                LOG(Animations, "%p AnimationState %s -> StartTimeSet (time is %f)", this, nameForState(m_animationState), param);

                // We have a start time, set it, unless the startTime is already set
                if (!m_startTime) {
                    m_startTime = param;
                    // If the value for 'animation-delay' is negative then the animation appears to have started in the past.
                    if (m_animation->delay() < 0)
                        m_startTime = m_startTime.value() + m_animation->delay();
                }

                // Now that we know the start time, fire the start event.
                onAnimationStart(0); // The elapsedTime is 0.

                // Decide whether to go into looping or ending state
                goIntoEndingOrLoopingState();

                // Dispatch updateStyleIfNeeded so we can start the animation
                if (m_element)
                    m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element);
            } else {
                // We are pausing while waiting for a start response. Cancel the animation and wait. When
                // we unpause, we will act as though the start timer just fired
                m_pauseTime = beginAnimationUpdateTime();
                pauseAnimation(beginAnimationUpdateTime() - m_startTime.value_or(0));
                LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animationState));
                m_animationState = AnimationState::PausedWaitResponse;
            }
            break;
        case AnimationState::Looping:
            ASSERT(input == AnimationStateInput::LoopTimerFired || input == AnimationStateInput::PlayStatePaused);

            if (input == AnimationStateInput::LoopTimerFired) {
                ASSERT(param >= 0);
                LOG(Animations, "%p AnimationState %s -> LoopTimerFired (time is %f)", this, nameForState(m_animationState), param);

                // Loop timer fired, loop again or end.
                onAnimationIteration(param);

                // Decide whether to go into looping or ending state
                goIntoEndingOrLoopingState();
            } else {
                // We are pausing while running. Cancel the animation and wait
                m_pauseTime = beginAnimationUpdateTime();
                pauseAnimation(beginAnimationUpdateTime() - m_startTime.value_or(0));
                LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animationState));
                m_animationState = AnimationState::PausedRun;
            }
            break;
        case AnimationState::Ending:
#if !LOG_DISABLED
            if (input != AnimationStateInput::EndTimerFired && input != AnimationStateInput::PlayStatePaused)
                LOG_ERROR("State is AnimationState::Ending, but input is not AnimationStateInput::EndTimerFired or AnimationStateInput::PlayStatePaused. It is %s.", nameForStateInput(input));
#endif
            if (input == AnimationStateInput::EndTimerFired) {
                ASSERT(param >= 0);
                // End timer fired, finish up
                onAnimationEnd(param);

                LOG(Animations, "%p AnimationState %s -> Done (time is %f)", this, nameForState(m_animationState), param);
                m_animationState = AnimationState::Done;

                if (m_element) {
                    if (m_animation->fillsForwards()) {
                        LOG(Animations, "%p AnimationState %s -> FillingForwards", this, nameForState(m_animationState));
                        m_animationState = AnimationState::FillingForwards;
                    } else
                        resumeOverriddenAnimations();

                    // Fire off another style change so we can set the final value
                    if (m_element)
                        m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element);
                }
            } else {
                // We are pausing while running. Cancel the animation and wait
                m_pauseTime = beginAnimationUpdateTime();
                pauseAnimation(beginAnimationUpdateTime() - m_startTime.value_or(0));
                LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animationState));
                m_animationState = AnimationState::PausedRun;
            }
            // |this| may be deleted here
            break;
        case AnimationState::PausedWaitTimer:
            ASSERT(input == AnimationStateInput::PlayStateRunning);
            ASSERT(paused());
            // Update the times
            m_startTime = m_startTime.value() + beginAnimationUpdateTime() - m_pauseTime.value_or(0);
            m_pauseTime = std::nullopt;

            // we were waiting for the start timer to fire, go back and wait again
            LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState));
            m_animationState = AnimationState::New;
            updateStateMachine(AnimationStateInput::StartAnimation, 0);
            break;
        case AnimationState::PausedNew:
        case AnimationState::PausedWaitResponse:
        case AnimationState::PausedWaitStyleAvailable:
        case AnimationState::PausedRun:
            // We treat these two cases the same. The only difference is that, when we are in
            // AnimationState::PausedWaitResponse, we don't yet have a valid startTime, so we send 0 to startAnimation.
            // When the AnimationStateInput::StartTimeSet comes in and we were in AnimationState::PausedRun, we will notice
            // that we have already set the startTime and will ignore it.
            ASSERT(input == AnimationStateInput::PlayStatePaused || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::StyleAvailable || input == AnimationStateInput::StartAnimation);
            ASSERT(paused());

            if (input == AnimationStateInput::PlayStateRunning) {
                if (m_animationState == AnimationState::PausedNew) {
                    // We were paused before we even started, and now we're supposed
                    // to start, so jump back to the New state and reset.
                    LOG(Animations, "%p AnimationState %s -> AnimationState::New", this, nameForState(m_animationState));
                    m_animationState = AnimationState::New;
                    m_pauseTime = std::nullopt;
                    updateStateMachine(input, param);
                    break;
                }

                // Update the times
                if (m_animationState == AnimationState::PausedRun)
                    m_startTime = m_startTime.value() + beginAnimationUpdateTime() - m_pauseTime.value_or(0);
                else
                    m_startTime = 0;

                m_pauseTime = std::nullopt;

                if (m_animationState == AnimationState::PausedWaitStyleAvailable) {
                    LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable", this, nameForState(m_animationState));
                    m_animationState = AnimationState::StartWaitStyleAvailable;
                } else {
                    // We were either running or waiting for a begin time response from the animation.
                    // Either way we need to restart the animation (possibly with an offset if we
                    // had already been running) and wait for it to start.
                    LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState));
                    m_animationState = AnimationState::StartWaitResponse;

                    // Start the animation
                    if (overridden()) {
                        // We won't try to start accelerated animations if we are overridden and
                        // just move on to the next state.
                        updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime());
                        m_isAccelerated = true;
                    } else {
                        bool started = startAnimation(beginAnimationUpdateTime() - m_startTime.value_or(0));
                        m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(this, started);
                        m_isAccelerated = started;
                    }
                }
                break;
            }

            if (input == AnimationStateInput::StartTimeSet) {
                ASSERT(m_animationState == AnimationState::PausedWaitResponse);

                // We are paused but we got the callback that notifies us that an accelerated animation started.
                // We ignore the start time and just move into the paused-run state.
                LOG(Animations, "%p AnimationState %s -> PausedRun (time is %f)", this, nameForState(m_animationState), param);
                m_animationState = AnimationState::PausedRun;
                ASSERT(!m_startTime);
                m_startTime = param;
                m_pauseTime = m_pauseTime.value_or(0) + param;
                break;
            }

            ASSERT(m_animationState == AnimationState::PausedNew || m_animationState == AnimationState::PausedWaitStyleAvailable);
            // We are paused but we got the callback that notifies us that style has been updated.
            // We move to the AnimationState::PausedWaitResponse state
            LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animationState));
            m_animationState = AnimationState::PausedWaitResponse;
            overrideAnimations();
            break;
        case AnimationState::FillingForwards:
        case AnimationState::Done:
            // We're done. Stay in this state until we are deleted
            break;
    }
}

void AnimationBase::fireAnimationEventsIfNeeded()
{
    if (!m_compositeAnimation)
        return;

    // If we are waiting for the delay time to expire and it has, go to the next state
    if (m_animationState != AnimationState::StartWaitTimer && m_animationState != AnimationState::Looping && m_animationState != AnimationState::Ending)
        return;

    // We have to make sure to keep a ref to the this pointer, because it could get destroyed
    // during an animation callback that might get called. Since the owner is a CompositeAnimation
    // and it ref counts this object, we will keep a ref to that instead. That way the AnimationBase
    // can still access the resources of its CompositeAnimation as needed.
    Ref<AnimationBase> protectedThis(*this);
    Ref<CompositeAnimation> protectCompositeAnimation(*m_compositeAnimation);

    // Check for start timeout
    if (m_animationState == AnimationState::StartWaitTimer) {
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
        if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger()) {
            if (m_element) {
                float offset = m_compositeAnimation->animationController().scrollPosition();
                auto& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger());
                if (offset > scrollTrigger.startValue().value())
                    updateStateMachine(AnimationStateInput::StartTimerFired, 0);
            }

            return;
        }
#endif
        if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay())
            updateStateMachine(AnimationStateInput::StartTimerFired, 0);
        return;
    }

    double elapsedDuration = beginAnimationUpdateTime() - m_startTime.value_or(0);
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
    // If we are a triggered animation that depends on scroll, our elapsed
    // time is determined by the scroll position.
    if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger())
        elapsedDuration = getElapsedTime();
#endif

    // FIXME: we need to ensure that elapsedDuration is never < 0. If it is, this suggests that
    // we had a recalcStyle() outside of beginAnimationUpdate()/endAnimationUpdate().
    // Also check in getTimeToNextEvent().
    elapsedDuration = std::max(elapsedDuration, 0.0);

    // Check for end timeout
    if (m_totalDuration && elapsedDuration >= m_totalDuration.value()) {
        // We may still be in AnimationState::Looping if we've managed to skip a
        // whole iteration, in which case we should jump to the end state.
        LOG(Animations, "%p AnimationState %s -> Ending", this, nameForState(m_animationState));
        m_animationState = AnimationState::Ending;

        // Fire an end event
        updateStateMachine(AnimationStateInput::EndTimerFired, m_totalDuration.value());
    } else {
        // Check for iteration timeout
        if (!m_nextIterationDuration) {
            // Hasn't been set yet, set it
            double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
            m_nextIterationDuration = elapsedDuration + durationLeft;
        }

        if (elapsedDuration >= m_nextIterationDuration) {
            // Set to the next iteration
            double previous = m_nextIterationDuration.value();
            double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
            m_nextIterationDuration = elapsedDuration + durationLeft;

            // Send the event
            updateStateMachine(AnimationStateInput::LoopTimerFired, previous);
        }
    }
}

void AnimationBase::updatePlayState(EAnimPlayState playState)
{
    if (!m_compositeAnimation)
        return;

    // When we get here, we can have one of 4 desired states: running, paused, suspended, paused & suspended.
    // The state machine can be in one of two states: running, paused.
    // Set the state machine to the desired state.
    bool pause = playState == AnimPlayStatePaused || m_compositeAnimation->isSuspended();

    if (pause == paused() && !isNew())
        return;

    updateStateMachine(pause ?  AnimationStateInput::PlayStatePaused : AnimationStateInput::PlayStateRunning, -1);
}

std::optional<Seconds> AnimationBase::timeToNextService()
{
    // Returns the time at which next service is required. std::nullopt means no service is required. 0 means
    // service is required now, and > 0 means service is required that many seconds in the future.
    if (paused() || isNew() || postActive() || fillingForwards())
        return std::nullopt;

    if (m_animationState == AnimationState::StartWaitTimer) {
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
        if (m_animation->trigger()->isScrollAnimationTrigger()) {
            if (m_element) {
                float currentScrollPosition = m_element->document().view()->scrollPositionForFixedPosition().y().toFloat();
                auto& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger());
                if (currentScrollPosition >= scrollTrigger.startValue().value() && (!scrollTrigger.hasEndValue() || currentScrollPosition <= scrollTrigger.endValue().value()))
                    return 0_s;
            }
            return std::nullopt;
        }
#endif
        double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime);
        return std::max(Seconds { timeFromNow }, 0_s);
    }

    fireAnimationEventsIfNeeded();

    // In all other cases, we need service right away.
    return 0_s;
}

// Compute the fractional time, taking into account direction.
// There is no need to worry about iterations, we assume that we would have
// short circuited above if we were done.

double AnimationBase::fractionalTime(double scale, double elapsedTime, double offset) const
{
    double fractionalTime = m_animation->duration() ? (elapsedTime / m_animation->duration()) : 1;
    // FIXME: startTime can be before the current animation "frame" time. This is to sync with the frame time
    // concept in AnimationTimeController. So we need to somehow sync the two. Until then, the possible
    // error is small and will probably not be noticeable. Until we fix this, remove the assert.
    // https://bugs.webkit.org/show_bug.cgi?id=52037
    // ASSERT(fractionalTime >= 0);
    if (fractionalTime < 0)
        fractionalTime = 0;

    int integralTime = static_cast<int>(fractionalTime);
    const int integralIterationCount = static_cast<int>(m_animation->iterationCount());
    const bool iterationCountHasFractional = m_animation->iterationCount() - integralIterationCount;
    if (m_animation->iterationCount() != Animation::IterationCountInfinite && !iterationCountHasFractional)
        integralTime = std::min(integralTime, integralIterationCount - 1);

    fractionalTime -= integralTime;

    if (((m_animation->direction() == Animation::AnimationDirectionAlternate) && (integralTime & 1))
        || ((m_animation->direction() == Animation::AnimationDirectionAlternateReverse) && !(integralTime & 1))
        || m_animation->direction() == Animation::AnimationDirectionReverse)
        fractionalTime = 1 - fractionalTime;

    if (scale != 1 || offset)
        fractionalTime = (fractionalTime - offset) * scale;

    return fractionalTime;
}

double AnimationBase::progress(double scale, double offset, const TimingFunction* timingFunction) const
{
    if (preActive())
        return 0;

    if (postActive())
        return 1;

    double elapsedTime = getElapsedTime();

    double duration = m_animation->duration();
    if (m_animation->iterationCount() > 0)
        duration *= m_animation->iterationCount();

    if (fillingForwards())
        elapsedTime = duration;

    double fractionalTime = this->fractionalTime(scale, elapsedTime, offset);

    if (m_animation->iterationCount() > 0 && elapsedTime >= duration) {
        if (WTF::isIntegral(fractionalTime))
            return fractionalTime;
    }

    if (!timingFunction)
        timingFunction = m_animation->timingFunction();

    return timingFunction->transformTime(fractionalTime, m_animation->duration());
}

void AnimationBase::getTimeToNextEvent(Seconds& time, bool& isLooping) const
{
    // Decide when the end or loop event needs to fire
    const double elapsedDuration = std::max(beginAnimationUpdateTime() - m_startTime.value_or(0), 0.0);
    double durationLeft = 0;
    double nextIterationTime = m_totalDuration.value_or(0);

    if (!m_totalDuration || elapsedDuration < m_totalDuration.value()) {
        durationLeft = m_animation->duration() > 0 ? (m_animation->duration() - fmod(elapsedDuration, m_animation->duration())) : 0;
        nextIterationTime = elapsedDuration + durationLeft;
    }

    if (!m_totalDuration || nextIterationTime < m_totalDuration.value()) {
        // We are not at the end yet
        ASSERT(nextIterationTime > 0);
        isLooping = true;
    } else {
        // We are at the end
        isLooping = false;
    }

    time = Seconds { durationLeft };
}

void AnimationBase::goIntoEndingOrLoopingState()
{
    Seconds t;
    bool isLooping;
    getTimeToNextEvent(t, isLooping);
    LOG(Animations, "%p AnimationState %s -> %s", this, nameForState(m_animationState), isLooping ? "Looping" : "Ending");
    m_animationState = isLooping ? AnimationState::Looping : AnimationState::Ending;
}

void AnimationBase::freezeAtTime(double t)
{
    if (!m_compositeAnimation)
        return;

    if (!m_startTime) {
        // If we haven't started yet, make it as if we started.
        LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState));
        m_animationState = AnimationState::StartWaitResponse;
        onAnimationStartResponse(monotonicallyIncreasingTime());
    }

    ASSERT(m_startTime); // If m_startTime is zero, we haven't started yet, so we'll get a bad pause time.
    if (t <= m_animation->delay())
        m_pauseTime = m_startTime.value_or(0);
    else
        m_pauseTime = m_startTime.value_or(0) + t - m_animation->delay();

    if (auto* renderer = compositedRenderer())
        renderer->suspendAnimations(m_pauseTime.value());
}

double AnimationBase::beginAnimationUpdateTime() const
{
    if (!m_compositeAnimation)
        return 0;

    return m_compositeAnimation->animationController().beginAnimationUpdateTime();
}

double AnimationBase::getElapsedTime() const
{
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
    if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger()) {
        auto& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger());
        if (scrollTrigger.hasEndValue() && m_element) {
            float offset = m_compositeAnimation->animationController().scrollPosition();
            float startValue = scrollTrigger.startValue().value();
            if (offset < startValue)
                return 0;
            float endValue = scrollTrigger.endValue().value();
            if (offset > endValue)
                return m_animation->duration();
            return m_animation->duration() * (offset - startValue) / (endValue - startValue);
        }
    }
#endif

    if (paused()) {
        double delayOffset = (!m_startTime && m_animation->delay() < 0) ? m_animation->delay() : 0;
        return m_pauseTime.value_or(0) - m_startTime.value_or(0) - delayOffset;
    }

    if (!m_startTime)
        return 0;

    if (postActive() || fillingForwards())
        return m_totalDuration.value_or(0);

    return beginAnimationUpdateTime() - m_startTime.value_or(0);
}

void AnimationBase::setElapsedTime(double time)
{
    // FIXME: implement this method
    UNUSED_PARAM(time);
}

void AnimationBase::play()
{
    // FIXME: implement this method
}

void AnimationBase::pause()
{
    // FIXME: implement this method
}

static bool containsRotation(const Vector<RefPtr<TransformOperation>>& operations)
{
    for (const auto& operation : operations) {
        if (operation->type() == TransformOperation::ROTATE)
            return true;
    }
    return false;
}

bool AnimationBase::computeTransformedExtentViaTransformList(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const
{
    FloatRect floatBounds = bounds;
    FloatPoint transformOrigin;

    bool applyTransformOrigin = containsRotation(style.transform().operations()) || style.transform().affectedByTransformOrigin();
    if (applyTransformOrigin) {
        transformOrigin.setX(rendererBox.x() + floatValueForLength(style.transformOriginX(), rendererBox.width()));
        transformOrigin.setY(rendererBox.y() + floatValueForLength(style.transformOriginY(), rendererBox.height()));
        // Ignore transformOriginZ because we'll bail if we encounter any 3D transforms.

        floatBounds.moveBy(-transformOrigin);
    }

    for (const auto& operation : style.transform().operations()) {
        if (operation->type() == TransformOperation::ROTATE) {
            // For now, just treat this as a full rotation. This could take angle into account to reduce inflation.
            floatBounds = boundsOfRotatingRect(floatBounds);
        } else {
            TransformationMatrix transform;
            operation->apply(transform, rendererBox.size());
            if (!transform.isAffine())
                return false;

            if (operation->type() == TransformOperation::MATRIX || operation->type() == TransformOperation::MATRIX_3D) {
                TransformationMatrix::Decomposed2Type toDecomp;
                transform.decompose2(toDecomp);
                // Any rotation prevents us from using a simple start/end rect union.
                if (toDecomp.angle)
                    return false;
            }

            floatBounds = transform.mapRect(floatBounds);
        }
    }

    if (applyTransformOrigin)
        floatBounds.moveBy(transformOrigin);

    bounds = LayoutRect(floatBounds);
    return true;
}

bool AnimationBase::computeTransformedExtentViaMatrix(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const
{
    TransformationMatrix transform;
    style.applyTransform(transform, rendererBox, RenderStyle::IncludeTransformOrigin);
    if (!transform.isAffine())
        return false;

    TransformationMatrix::Decomposed2Type fromDecomp;
    transform.decompose2(fromDecomp);
    // Any rotation prevents us from using a simple start/end rect union.
    if (fromDecomp.angle)
        return false;

    bounds = LayoutRect(transform.mapRect(bounds));
    return true;

}

} // namespace WebCore
