// Copyright 2018 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 UI_LATENCY_WINDOWED_ANALYZER_H_
#define UI_LATENCY_WINDOWED_ANALYZER_H_

#include <cstdint>

#include "base/containers/circular_deque.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event_argument.h"
#include "ui/latency/fixed_point.h"

namespace ui {

// FrameRegionResult encodes window of time where a metric was worst.
// The |sample_count| is the number of samples/frames within the time window
// used to calculate the result. It is reported in case the client wants to
// assess the confidence of the result.
struct FrameRegionResult {
  double value = 0;
  size_t sample_count = 0;
  base::TimeTicks window_begin;
  base::TimeTicks window_end;

  void AsValueInto(base::trace_event::TracedValue* state) const;
};

namespace frame_metrics {

// Client delegates that are specific to each WindowedAnalyzer.
class WindowedAnalyzerClient {
 public:
  // The WorstMean,RMS,SMR methods will give TransformResult() a chance to
  // modify the results via this delegate.
  // This can be used to undo any tranformations applied to values added
  // to AddSample, such as conversions to fixed point.
  virtual double TransformResult(double result) const = 0;

  // TODO(brianderson): Replace WindowedAnalyzer::window_queue_ with a client
  // interface here. All latency derived metrics should be able to share a
  // common history of values. http://crbug.com/822054
};

// Client delegates that can be shared by multiple WindowedAnalyzers.
// Tracks the current window of time that can be stored as the worst
// window of time if a metric detects it as such.
struct SharedWindowedAnalyzerClient {
  SharedWindowedAnalyzerClient() : max_window_size(0) {}

  explicit SharedWindowedAnalyzerClient(size_t max_window_size)
      : max_window_size(max_window_size) {}

  SharedWindowedAnalyzerClient(size_t max_window_size,
                               base::TimeTicks window_begin,
                               base::TimeTicks window_end)
      : max_window_size(max_window_size),
        window_begin(window_begin),
        window_end(window_end) {}

  // Maximum window size in number of samples.
  size_t max_window_size;

  // Current window of time for the samples being added.
  base::TimeTicks window_begin;
  base::TimeTicks window_end;
};

// Detects the worst windows of time for a metric.
// Tracks the current values of the current window of time for the
// mean, RMS, and SMR of a single metric. It maintains a history
// of the recent samples and, for each new sample, updates it's accumulators
// using the oldest and newest samples, without looking at any of the other
// samples in between.
class WindowedAnalyzer {
 public:
  WindowedAnalyzer(const WindowedAnalyzerClient* client,
                   const SharedWindowedAnalyzerClient* shared_client);
  virtual ~WindowedAnalyzer();

  // ResetWosrtValues only resets the memory of worst values encountered,
  // without resetting recent sample history.
  void ResetWorstValues();

  // ResetHistory only resets recent sample history without resetting memory
  // of the worst values ecnountered.
  void ResetHistory();

  // Callers of AddSample will already have calculated weighted values to
  // track cumulative results, so just let them pass in the values here
  // rather than re-calculating them.
  void AddSample(uint32_t value,
                 uint32_t weight,
                 uint64_t weighted_value,
                 uint64_t weighted_root,
                 const Accumulator96b& weighted_square);

  // Returns the worst regions encountered so far.
  FrameRegionResult ComputeWorstMean() const;
  FrameRegionResult ComputeWorstRMS() const;
  FrameRegionResult ComputeWorstSMR() const;

 protected:
  struct QueueEntry {
    uint32_t value = 0;
    uint32_t weight = 0;
  };

  // Updates the result with the current value, if it is worse than the
  // value in |result| or if |initialize| is true.
  template <typename AccumulatorT>
  void UpdateWorst(const AccumulatorT& accumulator,
                   FrameRegionResult* result,
                   bool initialize) const {
    double current_mean = AsDouble(accumulator) / total_weight_;
    if (initialize || current_mean > result->value) {
      result->value = current_mean;
      result->sample_count = window_queue_.size();
      result->window_begin = shared_client_->window_begin;
      result->window_end = shared_client_->window_end;
    }
  }

  const WindowedAnalyzerClient* const client_;
  const SharedWindowedAnalyzerClient* const shared_client_;

  // We need to maintain a history of values so we can
  // remove old samples from the accumulators.
  base::circular_deque<QueueEntry> window_queue_;

  uint64_t total_weight_ = 0;
  uint64_t accumulator_ = 0;
  uint64_t root_accumulator_ = 0;
  Accumulator96b square_accumulator_;

  // Internal results that track the worst region so far.
  // The time region is stored correctly, however the results are intermediate
  // and must be adjusted by result_transform_ and fixed_point_multipler before
  // exposure to the client. Furthermore, RMS needs to square root the result
  // and SMR needs to square the result.
  struct InternalResults {
    FrameRegionResult mean;
    FrameRegionResult root;
    FrameRegionResult square;
  };
  // Optional since they aren't valid until we've seen enough samples.
  // This delay prevents the first couple samples from dominating the result.
  base::Optional<InternalResults> results_;

  DISALLOW_COPY_AND_ASSIGN(WindowedAnalyzer);
};

}  // namespace frame_metrics
}  // namespace ui

#endif  // UI_LATENCY_WINDOWED_ANALYZER_H_
