/*
 *  Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "video/rate_utilization_tracker.h"

#include <algorithm>
#include <cstddef>
#include <optional>

#include "api/units/data_rate.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "rtc_base/checks.h"

namespace webrtc {

RateUtilizationTracker::RateUtilizationTracker(
    size_t max_num_encoded_data_points,
    TimeDelta max_duration)
    : max_data_points_(max_num_encoded_data_points),
      max_duration_(max_duration),
      current_rate_(DataRate::Zero()) {
  RTC_CHECK_GE(max_num_encoded_data_points, 0);
  RTC_CHECK_GT(max_duration, TimeDelta::Zero());
}

void RateUtilizationTracker::OnDataRateChanged(DataRate rate, Timestamp time) {
  current_rate_ = rate;
  if (data_points_.empty()) {
    // First entry should be contain first produced data, so just return after
    // setting `current_rate_`.
    return;
  } else {
    RateUsageUpdate& last_data_point = data_points_.back();
    RTC_CHECK_GE(time, last_data_point.time);
    if (last_data_point.time == time) {
      last_data_point.target_rate = rate;
    } else {
      data_points_.push_back({.time = time,
                              .target_rate = rate,
                              .produced_data = DataSize::Zero()});
    }
  }

  CullOldData(time);
}

void RateUtilizationTracker::OnDataProduced(DataSize size, Timestamp time) {
  if (data_points_.empty()) {
    data_points_.push_back(
        {.time = time, .target_rate = current_rate_, .produced_data = size});
  } else {
    RateUsageUpdate& last_data_point = data_points_.back();
    RTC_CHECK_GE(time, last_data_point.time);
    if (last_data_point.time == time) {
      last_data_point.produced_data += size;
    } else {
      data_points_.push_back(
          {.time = time, .target_rate = current_rate_, .produced_data = size});
    }
  }

  CullOldData(time);
}

std::optional<double> RateUtilizationTracker::GetRateUtilizationFactor(
    Timestamp time) const {
  if (data_points_.empty()) {
    return std::nullopt;
  }

  RTC_CHECK_GE(time, data_points_.back().time);
  DataSize allocated_send_data_size = DataSize::Zero();
  DataSize total_produced_data = DataSize::Zero();

  // Keep track of the last time data was produced - how much it was and how
  // much rate budget has been allocated since then.
  DataSize data_allocated_for_last_data = DataSize::Zero();
  DataSize size_of_last_data = DataSize::Zero();

  RTC_DCHECK(!data_points_.front().produced_data.IsZero());
  for (size_t i = 0; i < data_points_.size(); ++i) {
    const RateUsageUpdate& update = data_points_[i];
    total_produced_data += update.produced_data;

    DataSize allocated_since_previous_data_point =
        i == 0 ? DataSize::Zero()
               : (update.time - data_points_[i - 1].time) *
                     data_points_[i - 1].target_rate;
    allocated_send_data_size += allocated_since_previous_data_point;

    if (update.produced_data.IsZero()) {
      // Just a rate update past the last seen produced data.
      data_allocated_for_last_data =
          std::min(size_of_last_data, data_allocated_for_last_data +
                                          allocated_since_previous_data_point);
    } else {
      // A newer data point with produced data, reset accumulator for rate
      // allocated past the last data point.
      size_of_last_data = update.produced_data;
      data_allocated_for_last_data = DataSize::Zero();
    }
  }

  if (allocated_send_data_size.IsZero() && current_rate_.IsZero()) {
    // No allocated rate across all of the data points, ignore.
    return std::nullopt;
  }

  // Calculate the rate past the very last data point until the polling time.
  const RateUsageUpdate& last_update = data_points_.back();
  DataSize allocated_since_last_data_point =
      (time - last_update.time) * last_update.target_rate;

  // If the last produced data packet is larger than the accumulated rate
  // allocation window since then, use that data point size instead (minus any
  // data rate accumulated in rate updates after that data point was produced).
  allocated_send_data_size +=
      std::max(allocated_since_last_data_point,
               size_of_last_data - data_allocated_for_last_data);

  return total_produced_data.bytes<double>() / allocated_send_data_size.bytes();
}

void RateUtilizationTracker::CullOldData(Timestamp time) {
  // Remove data points that are either too old, exceed the limit of number of
  // data points - and make sure the first entry in the list contains actual
  // data produced since we calculate send usage since that time.

  // We don't allow negative times so always start window at absolute time >= 0.
  const Timestamp oldest_included_time =
      time.ms() > max_duration_.ms() ? time - max_duration_ : Timestamp::Zero();

  while (!data_points_.empty() &&
         (data_points_.front().time < oldest_included_time ||
          data_points_.size() > max_data_points_ ||
          data_points_.front().produced_data.IsZero())) {
    data_points_.pop_front();
  }
}

}  // namespace webrtc
