// Copyright 2017 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 CC_TREES_IMAGE_ANIMATION_CONTROLLER_H_
#define CC_TREES_IMAGE_ANIMATION_CONTROLLER_H_

#include <string>
#include <vector>

#include "base/cancelable_callback.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "cc/cc_export.h"
#include "cc/paint/discardable_image_map.h"
#include "cc/paint/image_id.h"
#include "cc/paint/paint_image.h"
#include "cc/paint/paint_image_generator.h"
#include "cc/tiles/tile_priority.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"

namespace cc {
class PaintImage;

// ImageAnimationController is responsible for tracking state for ticking image
// animations in the compositor.
//
// 1) It receives the updated metadata for these images from new recordings
//    received from the client using UpdateAnimatedImage. The controller tracks
//    the frame index of an image used on a tree, and advances the animation to
//    the desired frame each time a new sync tree is created.
//
// 2) An AnimationDriver can register itself for deciding whether the
//    controller animates an image. The animation is paused if there are no
//    registered drivers interested in animating it.
//
//  3) An animation is only advanced on the sync tree, which is requested to be
//     created using the |client| callbacks. This effectively means that
//     the frame of the image used remains consistent throughout the lifetime of
//     a tree, guaranteeing that the image update is atomic.
class CC_EXPORT ImageAnimationController {
 public:
  // AnimationDrivers are clients interested in driving image animations. An
  // animation is ticked if there is at least one driver registered for which
  // ShouldAnimate returns true. Once
  // no drivers are registered for an image, or none of the registered drivers
  // want us to animate, the animation is no longer ticked.
  class CC_EXPORT AnimationDriver {
   public:
    virtual ~AnimationDriver() {}

    // Returns true if the image should be animated.
    virtual bool ShouldAnimate(PaintImage::Id paint_image_id) const = 0;
  };

  class CC_EXPORT Client {
   public:
    virtual ~Client() {}

    // Notifies the client that an impl frame is needed to animate an image.
    virtual void RequestBeginFrameForAnimatedImages() = 0;

    // Notifies the client that a sync tree is needed to invalidate the animated
    // images in this impl frame. This should only be called from within an impl
    // frame.
    virtual void RequestInvalidationForAnimatedImages() = 0;
  };

  // |task_runner| is the thread on which the controller is used. The |client|
  // can only be called on this thread.
  // |enable_image_animation_resync| specifies whether the animation can be
  // reset to the beginning to avoid skipping many frames.
  ImageAnimationController(base::SingleThreadTaskRunner* task_runner,
                           Client* client,
                           bool enable_image_animation_resync);
  ~ImageAnimationController();

  // Called to update the state for a PaintImage received in a new recording.
  void UpdateAnimatedImage(
      const DiscardableImageMap::AnimatedImageMetadata& data);

  // Registers/Unregisters an animation driver interested in animating this
  // image.
  // Note that the state for this image must have been populated to the
  // controller using UpdatePaintImage prior to registering any drivers.
  void RegisterAnimationDriver(PaintImage::Id paint_image_id,
                               AnimationDriver* driver);
  void UnregisterAnimationDriver(PaintImage::Id paint_image_id,
                                 AnimationDriver* driver);

  // Called to advance the animations to the frame to be used on the sync tree.
  // This should be called only once for a sync tree and must be followed with
  // a call to DidActivate when this tree is activated.
  // Returns the set of images that were animated and should be invalidated on
  // this sync tree.
  const PaintImageIdFlatSet& AnimateForSyncTree(
      const viz::BeginFrameArgs& args);

  // Called whenever the ShouldAnimate response for a driver could have changed.
  // For instance on a change in the visibility of the image, we would pause
  // off-screen animations.
  // This is called after every DrawProperties update and commit.
  void UpdateStateFromDrivers();

  // Called when the sync tree was activated and the animations' associated
  // state should be pushed to the active tree.
  void DidActivate();

  // Returns the frame index to use for the given PaintImage and tree.
  size_t GetFrameIndexForImage(PaintImage::Id paint_image_id,
                               WhichTree tree) const;

  // Notifies the beginning of an impl frame with the given |args|.
  void WillBeginImplFrame(const viz::BeginFrameArgs& args);

  void set_did_navigate() { did_navigate_ = true; }

  const base::flat_set<AnimationDriver*>& GetDriversForTesting(
      PaintImage::Id paint_image_id) const;
  size_t GetLastNumOfFramesSkippedForTesting(
      PaintImage::Id paint_image_id) const;

  size_t animation_state_map_size_for_testing() {
    return animation_state_map_.size();
  }

  using NowCallback = base::RepeatingCallback<base::TimeTicks()>;
  void set_now_callback_for_testing(NowCallback cb) {
    scheduler_.set_now_callback_for_testing(cb);
  }

 private:
  class AnimationState {
   public:
    AnimationState();
    AnimationState(const AnimationState&) = delete;
    AnimationState(AnimationState&& other);
    ~AnimationState();

    AnimationState& operator=(const AnimationState&) = delete;
    AnimationState& operator=(AnimationState&& other);

    bool ShouldAnimate() const;
    bool AdvanceFrame(const viz::BeginFrameArgs& args,
                      bool enable_image_animation_resync);
    void UpdateMetadata(const DiscardableImageMap::AnimatedImageMetadata& data);
    void PushPendingToActive();

    void AddDriver(AnimationDriver* driver);
    void RemoveDriver(AnimationDriver* driver);
    void UpdateStateFromDrivers();
    bool has_drivers() const { return !drivers_.empty(); }

    size_t pending_index() const { return pending_index_; }
    size_t active_index() const { return active_index_; }
    base::TimeTicks next_desired_tick_time() const {
      return next_desired_tick_time_;
    }
    const base::flat_set<AnimationDriver*>& drivers_for_testing() const {
      return drivers_;
    }
    size_t last_num_frames_skipped_for_testing() const {
      return last_num_frames_skipped_;
    }
    std::string ToString() const;

   private:
    void ResetAnimation();
    size_t NextFrameIndex() const;
    bool is_complete() const {
      return completion_state_ == PaintImage::CompletionState::DONE;
    }
    bool needs_invalidation() const { return pending_index_ != active_index_; }

    PaintImage::Id paint_image_id_ = PaintImage::kInvalidId;

    // The frame metadata received from the most updated recording with this
    // PaintImage.
    std::vector<FrameMetadata> frames_;

    // The number of animation loops requested for this image. For a value > 0,
    // this number represents the exact number of iterations requested. A few
    // special cases are represented using constants defined in
    // cc/paint/image_animation_count.h
    int requested_repetitions_ = kAnimationNone;

    // The number of loops the animation has finished so far.
    int repetitions_completed_ = 0;

    // A set of drivers interested in animating this image.
    base::flat_set<AnimationDriver*> drivers_;

    // The index being used on the active tree, if a recording with this image
    // is still present.
    size_t active_index_ = PaintImage::kDefaultFrameIndex;

    // The index being displayed on the pending tree.
    size_t pending_index_ = PaintImage::kDefaultFrameIndex;

    // The time at which we would like to display the next frame. This can be in
    // the past, for instance, if we pause the animation from the image becoming
    // invisible. This time is updated strictly based on the animation timeline
    // provided by the image.
    base::TimeTicks next_desired_frame_time_;

    // The time of the next tick at which we want to invalidate and update the
    // current frame.
    base::TimeTicks next_desired_tick_time_;

    // Set if there is at least one driver interested in animating this image,
    // cached from the last update.
    bool should_animate_from_drivers_ = false;

    // Set if the animation has been started.
    bool animation_started_ = false;
    // Used for tracing, the time at which the animation was started.
    base::TimeTicks animation_started_time_;

    // The last synchronized sequence id for resetting this animation.
    PaintImage::AnimationSequenceId reset_animation_sequence_id_ = 0;

    // Whether the image is known to be completely loaded in the most recent
    // recording received.
    PaintImage::CompletionState completion_state_ =
        PaintImage::CompletionState::PARTIALLY_DONE;

    // The number of frames skipped during catch-up the last time this animation
    // was advanced.
    size_t last_num_frames_skipped_ = 0u;
  };

  class InvalidationScheduler {
   public:
    InvalidationScheduler(base::SingleThreadTaskRunner* task_runner,
                          Client* client);
    ~InvalidationScheduler();

    void Schedule(base::TimeTicks animation_time);
    void Cancel();
    void WillAnimate();
    void WillBeginImplFrame(const viz::BeginFrameArgs& args);
    void set_now_callback_for_testing(NowCallback cb) {
      now_callback_for_testing_ = cb;
    }

   private:
    enum InvalidationState {
      // No notification pending.
      kIdle,
      // Task pending to request impl frame.
      kPendingRequestBeginFrame,
      // Impl frame request pending after request dispatched to client.
      kPendingImplFrame,
      // Sync tree for animation pending after request dispatched to client.
      kPendingInvalidation,
    };

    void RequestBeginFrame();
    void RequestInvalidation();

    base::SingleThreadTaskRunner* task_runner_;
    Client* const client_;
    NowCallback now_callback_for_testing_;

    InvalidationState state_ = InvalidationState::kIdle;

    // The time at which the next animation is expected to run.
    base::TimeTicks next_animation_time_;

    base::WeakPtrFactory<InvalidationScheduler> weak_factory_{this};
  };

  // The AnimationState for images is persisted until they are cleared on
  // navigation. This is because while an image might not be painted anymore, if
  // it moves out of the interest rect for instance, the state retained is
  // necessary to resume the animation.
  // TODO(khushalsagar): Implement clearing of state on navigations.
  using AnimationStateMap = base::flat_map<PaintImage::Id, AnimationState>;
  AnimationStateMap animation_state_map_;

  // The set of animations with registered drivers.
  PaintImageIdFlatSet registered_animations_;

  // The set of images that were animated and invalidated on the last sync tree.
  PaintImageIdFlatSet images_animated_on_sync_tree_;

  InvalidationScheduler scheduler_;

  const bool enable_image_animation_resync_;

  bool did_navigate_ = false;
};

}  // namespace cc

#endif  // CC_TREES_IMAGE_ANIMATION_CONTROLLER_H_
