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

#ifndef CC_METRICS_FRAME_SEQUENCE_TRACKER_H_
#define CC_METRICS_FRAME_SEQUENCE_TRACKER_H_

#include <memory>
#include <sstream>

#include "base/containers/circular_deque.h"
#include "base/containers/flat_set.h"
#include "base/time/time.h"
#include "cc/cc_export.h"
#include "cc/metrics/frame_sequence_metrics.h"

namespace gfx {
struct PresentationFeedback;
}

namespace viz {
struct BeginFrameArgs;
struct BeginFrameId;
}  // namespace viz

namespace cc {
// Tracks a sequence of frames to determine the throughput. It tracks this by
// tracking the vsync sequence-numbers (from |BeginFrameArgs::sequence_number|),
// and the presentation-timestamps (from |gfx::PresentationFeedback|).
// This object should be created through
// FrameSequenceTrackerCollection::CreateTracker() API.
class CC_EXPORT FrameSequenceTracker {
 public:
  enum class TerminationStatus {
    kActive,
    kScheduledForTermination,
    kReadyForTermination,
  };

  static const char* GetFrameSequenceTrackerTypeName(
      FrameSequenceTrackerType type);

  ~FrameSequenceTracker();

  FrameSequenceTracker(const FrameSequenceTracker&) = delete;
  FrameSequenceTracker& operator=(const FrameSequenceTracker&) = delete;

  // Notifies the tracker when the compositor thread starts to process a
  // BeginFrameArgs.
  void ReportBeginImplFrame(const viz::BeginFrameArgs& args);

  // Notifies the tracker when the compositor thread has finished processing a
  // BeginFrameArgs.
  void ReportFrameEnd(const viz::BeginFrameArgs& args,
                      const viz::BeginFrameArgs& main_args);

  // Notifies that frame production has currently paused. This is typically used
  // for interactive frame-sequences, e.g. during touch-scroll.
  void PauseFrameProduction();

  TerminationStatus termination_status() const { return termination_status_; }

  // Returns true if we should ask this tracker to report its throughput data.
  bool ShouldReportMetricsNow(const viz::BeginFrameArgs& args) const;

  FrameSequenceMetrics* metrics() { return metrics_.get(); }
  FrameSequenceTrackerType type() const { return metrics_->type(); }
  int custom_sequence_id() const { return custom_sequence_id_; }

  std::unique_ptr<FrameSequenceMetrics> TakeMetrics();

  // Called by the destructor of FrameSequenceTrackerCollection, asking its
  // |metrics_| to report.
  void CleanUp();

  // Called by FrameSorter, this delivers `frame_info` merging both Compositor
  // and Main thread updates for the given `args`. These are sorted by the
  // `viz::BeginFrameArgs::frame_id` so we do not have to perform our own
  // analysis of interleaving frames.
  void AddSortedFrame(const viz::BeginFrameArgs& args,
                      const FrameInfo& frame_info);

 private:
  friend class FrameSequenceTrackerCollection;
  friend class FrameSequenceTrackerTest;

  // Constructs a tracker for a typed sequence other than kCustom.
  explicit FrameSequenceTracker(FrameSequenceTrackerType type);
  // Constructs a tracker for a kCustom typed sequence.
  FrameSequenceTracker(int custom_sequence_id,
                       FrameSequenceMetrics::CustomReporter custom_reporter);

  void ScheduleTerminate();

  struct TrackedFrameData {
    // Represents the |BeginFrameArgs::source_id| and
    // |BeginFrameArgs::sequence_number| fields of the last processed
    // BeginFrameArgs.
    uint64_t previous_source = 0;
    uint64_t previous_sequence = 0;
  };

  bool ShouldIgnoreBeginFrameSource(uint64_t source_id) const;
  void ResetAllStateIfPaused();

  const int custom_sequence_id_;

  TerminationStatus termination_status_ = TerminationStatus::kActive;

  TrackedFrameData begin_impl_frame_data_;

  std::unique_ptr<FrameSequenceMetrics> metrics_;

  // Keeps track of whether the frame-states should be reset.
  bool reset_all_state_ = false;

  // Report the throughput metrics every 5 seconds.
  const base::TimeDelta time_delta_to_report_ = base::Seconds(5);

  // True when an impl-impl is not ended. A tracker is ready for termination
  // only when the last impl-frame is ended (ReportFrameEnd).
  bool is_inside_frame_ = false;

  // The args for the first frame that started after the tracker was created.
  viz::BeginFrameArgs first_begin_frame_args_;

  // Frame id of the last ended frame when the tracker is active.
  viz::BeginFrameId last_ended_frame_id_;
  // Frame id of the last sorted frame that the tracker was notified. If this is
  // at least equal to `last_ended_frame_id_` then we are no longer awaiting
  // frame feedback and can terminate immediately.
  viz::BeginFrameId last_sorted_frame_id_;
};

}  // namespace cc

#endif  // CC_METRICS_FRAME_SEQUENCE_TRACKER_H_
