// 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.

#include "ui/latency/windowed_analyzer.h"

namespace ui {

void FrameRegionResult::AsValueInto(
    base::trace_event::TracedValue* state) const {
  // We don't report sample_count, since that is reported at a higher level.
  state->SetDouble("value", value);
  state->SetDouble("start", window_begin.since_origin().InMillisecondsF());
  state->SetDouble("duration", (window_end - window_begin).InMillisecondsF());
}

namespace frame_metrics {

WindowedAnalyzer::WindowedAnalyzer(
    const WindowedAnalyzerClient* client,
    const SharedWindowedAnalyzerClient* shared_client)
    : client_(client), shared_client_(shared_client) {
  window_queue_.reserve(shared_client->max_window_size);
}

WindowedAnalyzer::~WindowedAnalyzer() = default;

void WindowedAnalyzer::ResetWorstValues() {
  results_.reset();
}

void WindowedAnalyzer::ResetHistory() {
  total_weight_ = 0;
  accumulator_ = 0;
  root_accumulator_ = 0;
  square_accumulator_ = Accumulator96b();
  window_queue_.resize(0);
}

void WindowedAnalyzer::AddSample(uint32_t value,
                                 uint32_t weight,
                                 uint64_t weighted_value,
                                 uint64_t weighted_root,
                                 const Accumulator96b& weighted_square) {
  DCHECK_GT(weight, 0u);
  DCHECK_EQ(weighted_value, static_cast<uint64_t>(weight) * value);

  // Remove old values from the accumulators.
  if (window_queue_.size() >= shared_client_->max_window_size) {
    const uint32_t old_value = window_queue_.front().value;
    const uint32_t old_weight = window_queue_.front().weight;
    window_queue_.pop_front();

    // Re-calculate some of the old values here. Although squared and root are
    // passed in, we've only stored the original value to reduce memory usage.
    total_weight_ -= old_weight;
    accumulator_ -= static_cast<uint64_t>(old_weight) * old_value;
    // Casting the whole rhs is important to ensure rounding happens at a place
    // equivalent to when it was added.
    root_accumulator_ -= static_cast<uint64_t>(
        old_weight *
        std::sqrt(static_cast<uint64_t>(old_value) << kFixedPointRootShift));
    square_accumulator_.Subtract(Accumulator96b(old_value, old_weight));
  }

  // Verify overflow isn't an issue.
  // square_accumulator_ has DCHECKs internally, so we don't worry about
  // checking that here.
  DCHECK_LT(weighted_value,
            std::numeric_limits<decltype(accumulator_)>::max() - accumulator_);
  DCHECK_LT(weighted_root,
            std::numeric_limits<decltype(root_accumulator_)>::max() -
                root_accumulator_);
  DCHECK_LT(weight, std::numeric_limits<decltype(total_weight_)>::max() -
                        total_weight_);

  window_queue_.push_back({value, weight});
  total_weight_ += weight;
  accumulator_ += weighted_value;
  root_accumulator_ += weighted_root;
  square_accumulator_.Add(weighted_square);
  if (window_queue_.size() >= shared_client_->max_window_size) {
    bool initialize_results = !results_;
    if (initialize_results)
      results_.emplace();
    UpdateWorst(accumulator_, &results_->mean, initialize_results);
    UpdateWorst(root_accumulator_, &results_->root, initialize_results);
    UpdateWorst(square_accumulator_, &results_->square, initialize_results);
  }
}

FrameRegionResult WindowedAnalyzer::ComputeWorstMean() const {
  FrameRegionResult result;
  if (results_) {
    result = results_->mean;
  } else {
    UpdateWorst(accumulator_, &result, true);
  }
  result.value = client_->TransformResult(result.value);
  return result;
}

FrameRegionResult WindowedAnalyzer::ComputeWorstRMS() const {
  FrameRegionResult result;
  if (results_) {
    result = results_->square;
  } else {
    UpdateWorst(square_accumulator_, &result, true);
  }
  result.value = client_->TransformResult(std::sqrt(result.value));
  return result;
}

FrameRegionResult WindowedAnalyzer::ComputeWorstSMR() const {
  FrameRegionResult result;
  if (results_) {
    result = results_->root;
  } else {
    UpdateWorst(root_accumulator_, &result, true);
  }
  result.value = client_->TransformResult((result.value * result.value) /
                                          kFixedPointRootMultiplier);
  return result;
}

}  // namespace frame_metrics
}  // namespace ui
