// Target bitrate for the video lane's x264 encoder, driven by the viewer's
// per-second inbound-rtp deltas (packets lost / received) and the ICE round
// trip. The GStreamer GCC estimator (rtpgccbwe, gst-plugins-rs) is not
// installed on the CPU workers, and NACK/RTX already repairs isolated loss,
// so this only has to keep the encoder inside the path: heavy loss cuts the
// target hard, light loss holds it, and a clean run climbs back slowly.
#pragma once

#include <algorithm>

namespace w3cs {

struct VideoRateController {
  static constexpr double kHeavyLoss = 0.03;
  static constexpr double kLightLoss = 0.01;
  // Bufferbloat reads as RTT, not loss: a path whose queue keeps growing
  // shows the same symptom as a lossy one, later.
  static constexpr double kHeavyRttSeconds = 0.80;
  static constexpr double kLightRttSeconds = 0.40;
  static constexpr unsigned kCleanBeforeClimb = 6;
  static constexpr unsigned kMinimumPackets = 20;

  unsigned min_kbps;
  unsigned max_kbps;
  unsigned kbps;
  unsigned clean_samples = 0;

  VideoRateController(unsigned start, unsigned minimum, unsigned maximum)
      : min_kbps(std::min(minimum, maximum)),
        max_kbps(std::max(minimum, maximum)),
        kbps(std::clamp(start, min_kbps, max_kbps)) {}

  // One viewer report. Returns true when the target changed.
  bool observe(unsigned long long lost, unsigned long long received,
               double rtt_seconds) {
    const unsigned long long packets = lost + received;
    // A parked or idle interval carries no information about the path.
    if (packets < kMinimumPackets) return false;
    const double loss = static_cast<double>(lost) / packets;
    const bool heavy = loss >= kHeavyLoss || rtt_seconds >= kHeavyRttSeconds;
    const bool light = !heavy &&
        (loss >= kLightLoss || rtt_seconds >= kLightRttSeconds);
    const unsigned before = kbps;
    if (heavy) {
      clean_samples = 0;
      kbps = std::max(min_kbps, kbps - kbps * 30 / 100);
    } else if (light) {
      clean_samples = 0;
    } else if (++clean_samples >= kCleanBeforeClimb) {
      clean_samples = 0;
      kbps = std::min(max_kbps, kbps + kbps * 15 / 100);
    }
    return kbps != before;
  }
};

}  // namespace w3cs
