// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CSSAnimationUpdate_h
#define CSSAnimationUpdate_h

#include "core/animation/AnimationStack.h"
#include "core/animation/InertEffect.h"
#include "core/animation/Interpolation.h"
#include "core/animation/KeyframeEffectModel.h"
#include "core/animation/css/CSSAnimatableValueFactory.h"
#include "core/animation/css/CSSPropertyEquality.h"
#include "core/css/CSSKeyframesRule.h"
#include "core/layout/LayoutObject.h"
#include "wtf/Allocator.h"
#include "wtf/HashMap.h"
#include "wtf/Vector.h"
#include "wtf/text/AtomicString.h"

namespace blink {

class Animation;

// This class stores the CSS Animations/Transitions information we use during a style recalc.
// This includes updates to animations/transitions as well as the Interpolations to be applied.
class CSSAnimationUpdate final {
    DISALLOW_NEW();
    WTF_MAKE_NONCOPYABLE(CSSAnimationUpdate);
public:
    class NewAnimation {
        DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
    public:
        NewAnimation(AtomicString name, size_t nameIndex, const InertEffect& effect, Timing timing, PassRefPtrWillBeRawPtr<StyleRuleKeyframes> styleRule)
            : name(name)
            , nameIndex(nameIndex)
            , effect(effect)
            , timing(timing)
            , styleRule(styleRule)
            , styleRuleVersion(this->styleRule->version())
        {
        }

        DEFINE_INLINE_TRACE()
        {
            visitor->trace(effect);
            visitor->trace(styleRule);
        }

        AtomicString name;
        size_t nameIndex;
        Member<const InertEffect> effect;
        Timing timing;
        RefPtrWillBeMember<StyleRuleKeyframes> styleRule;
        unsigned styleRuleVersion;
    };

    class UpdatedAnimation {
        DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
    public:
        UpdatedAnimation(size_t index, Animation* animation, const InertEffect& effect, Timing specifiedTiming, PassRefPtrWillBeRawPtr<StyleRuleKeyframes> styleRule)
            : index(index)
            , animation(animation)
            , effect(&effect)
            , specifiedTiming(specifiedTiming)
            , styleRule(styleRule)
            , styleRuleVersion(this->styleRule->version())
        {
        }

        DEFINE_INLINE_TRACE()
        {
            visitor->trace(animation);
            visitor->trace(effect);
            visitor->trace(styleRule);
        }

        size_t index;
        Member<Animation> animation;
        Member<const InertEffect> effect;
        Timing specifiedTiming;
        RefPtrWillBeMember<StyleRuleKeyframes> styleRule;
        unsigned styleRuleVersion;
    };

    CSSAnimationUpdate()
    {
    }

    ~CSSAnimationUpdate()
    {
        // For performance reasons, explicitly clear HeapVectors and
        // HeapHashMaps to avoid giving a pressure on Oilpan's GC.
        clear();
    }

    void copy(const CSSAnimationUpdate& update)
    {
        ASSERT(isEmpty());
        m_newAnimations = update.newAnimations();
        m_animationsWithUpdates = update.animationsWithUpdates();
        m_newTransitions = update.newTransitions();
        m_activeInterpolationsForAnimations = update.activeInterpolationsForAnimations();
        m_activeInterpolationsForTransitions = update.activeInterpolationsForTransitions();
        m_cancelledAnimationIndices = update.cancelledAnimationIndices();
        m_animationIndicesWithPauseToggled = update.animationIndicesWithPauseToggled();
        m_cancelledTransitions = update.cancelledTransitions();
        m_finishedTransitions = update.finishedTransitions();
        m_updatedCompositorKeyframes = update.updatedCompositorKeyframes();
    }

    void clear()
    {
        m_newAnimations.clear();
        m_animationsWithUpdates.clear();
        m_newTransitions.clear();
        m_activeInterpolationsForAnimations.clear();
        m_activeInterpolationsForTransitions.clear();
        m_cancelledAnimationIndices.clear();
        m_animationIndicesWithPauseToggled.clear();
        m_cancelledTransitions.clear();
        m_finishedTransitions.clear();
        m_updatedCompositorKeyframes.clear();
    }

    void startAnimation(const AtomicString& animationName, size_t nameIndex, const InertEffect& effect, const Timing& timing, PassRefPtrWillBeRawPtr<StyleRuleKeyframes> styleRule)
    {
        m_newAnimations.append(NewAnimation(animationName, nameIndex, effect, timing, styleRule));
    }
    // Returns whether animation has been suppressed and should be filtered during style application.
    bool isSuppressedAnimation(const Animation* animation) const { return m_suppressedAnimations.contains(animation); }
    void cancelAnimation(size_t index, const Animation& animation)
    {
        m_cancelledAnimationIndices.append(index);
        m_suppressedAnimations.add(&animation);
    }
    void toggleAnimationIndexPaused(size_t index)
    {
        m_animationIndicesWithPauseToggled.append(index);
    }
    void updateAnimation(size_t index, Animation* animation, const InertEffect& effect, const Timing& specifiedTiming,
        PassRefPtrWillBeRawPtr<StyleRuleKeyframes> styleRule)
    {
        m_animationsWithUpdates.append(UpdatedAnimation(index, animation, effect, specifiedTiming, styleRule));
        m_suppressedAnimations.add(animation);
    }
    void updateCompositorKeyframes(Animation* animation)
    {
        m_updatedCompositorKeyframes.append(animation);
    }

    void startTransition(CSSPropertyID id, const AnimatableValue* from, const AnimatableValue* to, const InertEffect& effect)
    {
        NewTransition newTransition;
        newTransition.id = id;
        newTransition.from = from;
        newTransition.to = to;
        newTransition.effect = &effect;
        m_newTransitions.set(id, newTransition);
    }
    bool isCancelledTransition(CSSPropertyID id) const { return m_cancelledTransitions.contains(id); }
    void cancelTransition(CSSPropertyID id) { m_cancelledTransitions.add(id); }
    void finishTransition(CSSPropertyID id) { m_finishedTransitions.add(id); }

    const HeapVector<NewAnimation>& newAnimations() const { return m_newAnimations; }
    const Vector<size_t>& cancelledAnimationIndices() const { return m_cancelledAnimationIndices; }
    const HeapHashSet<Member<const Animation>>& suppressedAnimations() const { return m_suppressedAnimations; }
    const Vector<size_t>& animationIndicesWithPauseToggled() const { return m_animationIndicesWithPauseToggled; }
    const HeapVector<UpdatedAnimation>& animationsWithUpdates() const { return m_animationsWithUpdates; }
    const HeapVector<Member<Animation>>& updatedCompositorKeyframes() const { return m_updatedCompositorKeyframes; }

    struct NewTransition {
        DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
    public:
        DEFINE_INLINE_TRACE()
        {
            visitor->trace(effect);
        }

        CSSPropertyID id;
        const AnimatableValue* from;
        const AnimatableValue* to;
        Member<const InertEffect> effect;
    };
    using NewTransitionMap = HeapHashMap<CSSPropertyID, NewTransition>;
    const NewTransitionMap& newTransitions() const { return m_newTransitions; }
    const HashSet<CSSPropertyID>& cancelledTransitions() const { return m_cancelledTransitions; }
    const HashSet<CSSPropertyID>& finishedTransitions() const { return m_finishedTransitions; }

    void adoptActiveInterpolationsForAnimations(ActiveInterpolationsMap& newMap) { newMap.swap(m_activeInterpolationsForAnimations); }
    void adoptActiveInterpolationsForTransitions(ActiveInterpolationsMap& newMap) { newMap.swap(m_activeInterpolationsForTransitions); }
    const ActiveInterpolationsMap& activeInterpolationsForAnimations() const { return m_activeInterpolationsForAnimations; }
    const ActiveInterpolationsMap& activeInterpolationsForTransitions() const { return m_activeInterpolationsForTransitions; }
    ActiveInterpolationsMap& activeInterpolationsForAnimations() { return m_activeInterpolationsForAnimations; }

    bool isEmpty() const
    {
        return m_newAnimations.isEmpty()
            && m_cancelledAnimationIndices.isEmpty()
            && m_suppressedAnimations.isEmpty()
            && m_animationIndicesWithPauseToggled.isEmpty()
            && m_animationsWithUpdates.isEmpty()
            && m_newTransitions.isEmpty()
            && m_cancelledTransitions.isEmpty()
            && m_finishedTransitions.isEmpty()
            && m_activeInterpolationsForAnimations.isEmpty()
            && m_activeInterpolationsForTransitions.isEmpty()
            && m_updatedCompositorKeyframes.isEmpty();
    }

    DEFINE_INLINE_TRACE()
    {
        visitor->trace(m_newTransitions);
        visitor->trace(m_newAnimations);
        visitor->trace(m_suppressedAnimations);
        visitor->trace(m_animationsWithUpdates);
        visitor->trace(m_updatedCompositorKeyframes);
    }

private:
    // Order is significant since it defines the order in which new animations
    // will be started. Note that there may be multiple animations present
    // with the same name, due to the way in which we split up animations with
    // incomplete keyframes.
    HeapVector<NewAnimation> m_newAnimations;
    Vector<size_t> m_cancelledAnimationIndices;
    HeapHashSet<Member<const Animation>> m_suppressedAnimations;
    Vector<size_t> m_animationIndicesWithPauseToggled;
    HeapVector<UpdatedAnimation> m_animationsWithUpdates;
    HeapVector<Member<Animation>> m_updatedCompositorKeyframes;

    NewTransitionMap m_newTransitions;
    HashSet<CSSPropertyID> m_cancelledTransitions;
    HashSet<CSSPropertyID> m_finishedTransitions;

    ActiveInterpolationsMap m_activeInterpolationsForAnimations;
    ActiveInterpolationsMap m_activeInterpolationsForTransitions;

    friend class PendingAnimationUpdate;
};

} // namespace blink

WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::CSSAnimationUpdate::NewAnimation);
WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::CSSAnimationUpdate::UpdatedAnimation);

#endif
