/*
 *  Copyright (c) 2011 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 "modules/video_coding/timing/timing.h"

#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "system_wrappers/include/clock.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"

namespace webrtc {
namespace {

constexpr Frequency k25Fps = Frequency::Hertz(25);
constexpr Frequency k90kHz = Frequency::KiloHertz(90);

MATCHER(HasConsistentVideoDelayTimings, "") {
  // Delays should be non-negative.
  bool p1 = arg.minimum_delay >= TimeDelta::Zero();
  bool p2 = arg.estimated_max_decode_time >= TimeDelta::Zero();
  bool p3 = arg.render_delay >= TimeDelta::Zero();
  bool p4 = arg.min_playout_delay >= TimeDelta::Zero();
  bool p5 = arg.max_playout_delay >= TimeDelta::Zero();
  bool p6 = arg.target_delay >= TimeDelta::Zero();
  bool p7 = arg.current_delay >= TimeDelta::Zero();
  *result_listener << "\np: " << p1 << p2 << p3 << p4 << p5 << p6 << p7;
  bool p = p1 && p2 && p3 && p4 && p5 && p6 && p7;

  // Delays should be internally consistent.
  bool m1 = arg.minimum_delay <= arg.target_delay;
  if (!m1) {
    *result_listener << "\nminimum_delay: " << arg.minimum_delay << ", "
                     << "target_delay: " << arg.target_delay << "\n";
  }
  bool m2 = arg.minimum_delay <= arg.current_delay;
  if (!m2) {
    *result_listener << "\nminimum_delay: " << arg.minimum_delay << ", "
                     << "current_delay: " << arg.current_delay;
  }
  bool m3 = arg.target_delay >= arg.min_playout_delay;
  if (!m3) {
    *result_listener << "\ntarget_delay: " << arg.target_delay << ", "
                     << "min_playout_delay: " << arg.min_playout_delay << "\n";
  }
  // TODO(crbug.com/webrtc/15197): Uncomment when this is guaranteed.
  // bool m4 = arg.target_delay <= arg.max_playout_delay;
  bool m5 = arg.current_delay >= arg.min_playout_delay;
  if (!m5) {
    *result_listener << "\ncurrent_delay: " << arg.current_delay << ", "
                     << "min_playout_delay: " << arg.min_playout_delay << "\n";
  }
  bool m6 = arg.current_delay <= arg.max_playout_delay;
  if (!m6) {
    *result_listener << "\ncurrent_delay: " << arg.current_delay << ", "
                     << "max_playout_delay: " << arg.max_playout_delay << "\n";
  }
  bool m = m1 && m2 && m3 && m5 && m6;

  return p && m;
}

}  // namespace

TEST(VCMTimingTest, JitterDelay) {
  test::ScopedKeyValueConfig field_trials;
  SimulatedClock clock(0);
  VCMTiming timing(&clock, field_trials);
  timing.Reset();

  uint32_t timestamp = 0;
  timing.UpdateCurrentDelay(timestamp);

  timing.Reset();

  timing.IncomingTimestamp(timestamp, clock.CurrentTime());
  TimeDelta jitter_delay = TimeDelta::Millis(20);
  timing.SetJitterDelay(jitter_delay);
  timing.UpdateCurrentDelay(timestamp);
  timing.set_render_delay(TimeDelta::Zero());
  auto wait_time = timing.MaxWaitingTime(
      timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(),
      /*too_many_frames_queued=*/false);
  // First update initializes the render time. Since we have no decode delay
  // we get wait_time = renderTime - now - renderDelay = jitter.
  EXPECT_EQ(jitter_delay, wait_time);

  jitter_delay += TimeDelta::Millis(VCMTiming::kDelayMaxChangeMsPerS + 10);
  timestamp += 90000;
  clock.AdvanceTimeMilliseconds(1000);
  timing.SetJitterDelay(jitter_delay);
  timing.UpdateCurrentDelay(timestamp);
  wait_time = timing.MaxWaitingTime(
      timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(),
      /*too_many_frames_queued=*/false);
  // Since we gradually increase the delay we only get 100 ms every second.
  EXPECT_EQ(jitter_delay - TimeDelta::Millis(10), wait_time);

  timestamp += 90000;
  clock.AdvanceTimeMilliseconds(1000);
  timing.UpdateCurrentDelay(timestamp);
  wait_time = timing.MaxWaitingTime(
      timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(),
      /*too_many_frames_queued=*/false);
  EXPECT_EQ(jitter_delay, wait_time);

  // Insert frames without jitter, verify that this gives the exact wait time.
  const int kNumFrames = 300;
  for (int i = 0; i < kNumFrames; i++) {
    clock.AdvanceTime(1 / k25Fps);
    timestamp += k90kHz / k25Fps;
    timing.IncomingTimestamp(timestamp, clock.CurrentTime());
  }
  timing.UpdateCurrentDelay(timestamp);
  wait_time = timing.MaxWaitingTime(
      timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(),
      /*too_many_frames_queued=*/false);
  EXPECT_EQ(jitter_delay, wait_time);

  // Add decode time estimates for 1 second.
  const TimeDelta kDecodeTime = TimeDelta::Millis(10);
  for (int i = 0; i < k25Fps.hertz(); i++) {
    clock.AdvanceTime(kDecodeTime);
    timing.StopDecodeTimer(kDecodeTime, clock.CurrentTime());
    timestamp += k90kHz / k25Fps;
    clock.AdvanceTime(1 / k25Fps - kDecodeTime);
    timing.IncomingTimestamp(timestamp, clock.CurrentTime());
  }
  timing.UpdateCurrentDelay(timestamp);
  wait_time = timing.MaxWaitingTime(
      timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(),
      /*too_many_frames_queued=*/false);
  EXPECT_EQ(jitter_delay, wait_time);

  const TimeDelta kMinTotalDelay = TimeDelta::Millis(200);
  timing.set_min_playout_delay(kMinTotalDelay);
  clock.AdvanceTimeMilliseconds(5000);
  timestamp += 5 * 90000;
  timing.UpdateCurrentDelay(timestamp);
  const TimeDelta kRenderDelay = TimeDelta::Millis(10);
  timing.set_render_delay(kRenderDelay);
  wait_time = timing.MaxWaitingTime(
      timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(),
      /*too_many_frames_queued=*/false);
  // We should at least have kMinTotalDelayMs - decodeTime (10) - renderTime
  // (10) to wait.
  EXPECT_EQ(kMinTotalDelay - kDecodeTime - kRenderDelay, wait_time);
  // The total video delay should be equal to the min total delay.
  EXPECT_EQ(kMinTotalDelay, timing.TargetVideoDelay());

  // Reset playout delay.
  timing.set_min_playout_delay(TimeDelta::Zero());
  clock.AdvanceTimeMilliseconds(5000);
  timestamp += 5 * 90000;
  timing.UpdateCurrentDelay(timestamp);

  EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}

TEST(VCMTimingTest, TimestampWrapAround) {
  constexpr auto kStartTime = Timestamp::Millis(1337);
  test::ScopedKeyValueConfig field_trials;
  SimulatedClock clock(kStartTime);
  VCMTiming timing(&clock, field_trials);

  // Provoke a wrap-around. The fifth frame will have wrapped at 25 fps.
  constexpr uint32_t kRtpTicksPerFrame = k90kHz / k25Fps;
  uint32_t timestamp = 0xFFFFFFFFu - 3 * kRtpTicksPerFrame;
  for (int i = 0; i < 5; ++i) {
    timing.IncomingTimestamp(timestamp, clock.CurrentTime());
    clock.AdvanceTime(1 / k25Fps);
    timestamp += kRtpTicksPerFrame;
    EXPECT_EQ(kStartTime + 3 / k25Fps,
              timing.RenderTime(0xFFFFFFFFu, clock.CurrentTime()));
    // One ms later in 90 kHz.
    EXPECT_EQ(kStartTime + 3 / k25Fps + TimeDelta::Millis(1),
              timing.RenderTime(89u, clock.CurrentTime()));
  }

  EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}

TEST(VCMTimingTest, UseLowLatencyRenderer) {
  test::ScopedKeyValueConfig field_trials;
  SimulatedClock clock(0);
  VCMTiming timing(&clock, field_trials);
  timing.Reset();
  // Default is false.
  EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering);
  // False if min playout delay > 0.
  timing.set_min_playout_delay(TimeDelta::Millis(10));
  timing.set_max_playout_delay(TimeDelta::Millis(20));
  EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering);
  // True if min==0, max > 0.
  timing.set_min_playout_delay(TimeDelta::Zero());
  EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering);
  // True if min==max==0.
  timing.set_max_playout_delay(TimeDelta::Zero());
  EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering);
  // True also for max playout delay==500 ms.
  timing.set_max_playout_delay(TimeDelta::Millis(500));
  EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering);
  // False if max playout delay > 500 ms.
  timing.set_max_playout_delay(TimeDelta::Millis(501));
  EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering);

  EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}

TEST(VCMTimingTest, MaxWaitingTimeIsZeroForZeroRenderTime) {
  // This is the default path when the RTP playout delay header extension is set
  // to min==0 and max==0.
  constexpr int64_t kStartTimeUs = 3.15e13;  // About one year in us.
  constexpr TimeDelta kTimeDelta = 1 / Frequency::Hertz(60);
  constexpr Timestamp kZeroRenderTime = Timestamp::Zero();
  SimulatedClock clock(kStartTimeUs);
  test::ScopedKeyValueConfig field_trials;
  VCMTiming timing(&clock, field_trials);
  timing.Reset();
  timing.set_max_playout_delay(TimeDelta::Zero());
  for (int i = 0; i < 10; ++i) {
    clock.AdvanceTime(kTimeDelta);
    Timestamp now = clock.CurrentTime();
    EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now,
                                    /*too_many_frames_queued=*/false),
              TimeDelta::Zero());
  }
  // Another frame submitted at the same time also returns a negative max
  // waiting time.
  Timestamp now = clock.CurrentTime();
  EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            TimeDelta::Zero());
  // MaxWaitingTime should be less than zero even if there's a burst of frames.
  EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            TimeDelta::Zero());
  EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            TimeDelta::Zero());
  EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            TimeDelta::Zero());

  EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}

TEST(VCMTimingTest, MaxWaitingTimeZeroDelayPacingExperiment) {
  // The minimum pacing is enabled by a field trial and active if the RTP
  // playout delay header extension is set to min==0.
  constexpr TimeDelta kMinPacing = TimeDelta::Millis(3);
  test::ScopedKeyValueConfig field_trials(
      "WebRTC-ZeroPlayoutDelay/min_pacing:3ms/");
  constexpr int64_t kStartTimeUs = 3.15e13;  // About one year in us.
  constexpr TimeDelta kTimeDelta = 1 / Frequency::Hertz(60);
  constexpr auto kZeroRenderTime = Timestamp::Zero();
  SimulatedClock clock(kStartTimeUs);
  VCMTiming timing(&clock, field_trials);
  timing.Reset();
  // MaxWaitingTime() returns zero for evenly spaced video frames.
  for (int i = 0; i < 10; ++i) {
    clock.AdvanceTime(kTimeDelta);
    Timestamp now = clock.CurrentTime();
    EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now,
                                    /*too_many_frames_queued=*/false),
              TimeDelta::Zero());
    timing.SetLastDecodeScheduledTimestamp(now);
  }
  // Another frame submitted at the same time is paced according to the field
  // trial setting.
  auto now = clock.CurrentTime();
  EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            kMinPacing);
  // If there's a burst of frames, the wait time is calculated based on next
  // decode time.
  EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            kMinPacing);
  EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            kMinPacing);
  // Allow a few ms to pass, this should be subtracted from the MaxWaitingTime.
  constexpr TimeDelta kTwoMs = TimeDelta::Millis(2);
  clock.AdvanceTime(kTwoMs);
  now = clock.CurrentTime();
  EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            kMinPacing - kTwoMs);
  // A frame is decoded at the current time, the wait time should be restored to
  // pacing delay.
  timing.SetLastDecodeScheduledTimestamp(now);
  EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now,
                                  /*too_many_frames_queued=*/false),
            kMinPacing);

  EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}

TEST(VCMTimingTest, DefaultMaxWaitingTimeUnaffectedByPacingExperiment) {
  // The minimum pacing is enabled by a field trial but should not have any
  // effect if render_time_ms is greater than 0;
  test::ScopedKeyValueConfig field_trials(
      "WebRTC-ZeroPlayoutDelay/min_pacing:3ms/");
  constexpr int64_t kStartTimeUs = 3.15e13;  // About one year in us.
  const TimeDelta kTimeDelta = TimeDelta::Millis(1000.0 / 60.0);
  SimulatedClock clock(kStartTimeUs);
  VCMTiming timing(&clock, field_trials);
  timing.Reset();
  clock.AdvanceTime(kTimeDelta);
  auto now = clock.CurrentTime();
  Timestamp render_time = now + TimeDelta::Millis(30);
  // Estimate the internal processing delay from the first frame.
  TimeDelta estimated_processing_delay =
      (render_time - now) -
      timing.MaxWaitingTime(render_time, now,
                            /*too_many_frames_queued=*/false);
  EXPECT_GT(estimated_processing_delay, TimeDelta::Zero());

  // Any other frame submitted at the same time should be scheduled according to
  // its render time.
  for (int i = 0; i < 5; ++i) {
    render_time += kTimeDelta;
    EXPECT_EQ(timing.MaxWaitingTime(render_time, now,
                                    /*too_many_frames_queued=*/false),
              render_time - now - estimated_processing_delay);
  }

  EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}

TEST(VCMTimingTest, MaxWaitingTimeReturnsZeroIfTooManyFramesQueuedIsTrue) {
  // The minimum pacing is enabled by a field trial and active if the RTP
  // playout delay header extension is set to min==0.
  constexpr TimeDelta kMinPacing = TimeDelta::Millis(3);
  test::ScopedKeyValueConfig field_trials(
      "WebRTC-ZeroPlayoutDelay/min_pacing:3ms/");
  constexpr int64_t kStartTimeUs = 3.15e13;  // About one year in us.
  const TimeDelta kTimeDelta = TimeDelta::Millis(1000.0 / 60.0);
  constexpr auto kZeroRenderTime = Timestamp::Zero();
  SimulatedClock clock(kStartTimeUs);
  VCMTiming timing(&clock, field_trials);
  timing.Reset();
  // MaxWaitingTime() returns zero for evenly spaced video frames.
  for (int i = 0; i < 10; ++i) {
    clock.AdvanceTime(kTimeDelta);
    auto now = clock.CurrentTime();
    EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now,
                                    /*too_many_frames_queued=*/false),
              TimeDelta::Zero());
    timing.SetLastDecodeScheduledTimestamp(now);
  }
  // Another frame submitted at the same time is paced according to the field
  // trial setting.
  auto now_ms = clock.CurrentTime();
  EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now_ms,
                                  /*too_many_frames_queued=*/false),
            kMinPacing);
  // MaxWaitingTime returns 0 even if there's a burst of frames if
  // too_many_frames_queued is set to true.
  EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now_ms,
                                  /*too_many_frames_queued=*/true),
            TimeDelta::Zero());
  EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now_ms,
                                  /*too_many_frames_queued=*/true),
            TimeDelta::Zero());

  EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}

TEST(VCMTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) {
  test::ScopedKeyValueConfig field_trials;
  SimulatedClock clock(0);
  VCMTiming timing(&clock, field_trials);
  timing.Reset();

  // Set larger initial current delay.
  timing.set_min_playout_delay(TimeDelta::Millis(200));
  timing.UpdateCurrentDelay(Timestamp::Millis(900), Timestamp::Millis(1000));

  // Add a few microseconds to ensure that the delta of decode time is 0 after
  // rounding, and should reset to the target delay.
  timing.set_min_playout_delay(TimeDelta::Millis(50));
  Timestamp decode_time = Timestamp::Millis(1337);
  Timestamp render_time =
      decode_time + TimeDelta::Millis(10) + TimeDelta::Micros(37);
  timing.UpdateCurrentDelay(render_time, decode_time);
  EXPECT_EQ(timing.GetTimings().current_delay, timing.TargetVideoDelay());

  // TODO(crbug.com/webrtc/15197): Fix this.
  // EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}

TEST(VCMTimingTest, GetTimings) {
  test::ScopedKeyValueConfig field_trials;
  SimulatedClock clock(33);
  VCMTiming timing(&clock, field_trials);
  timing.Reset();

  // Setup.
  TimeDelta render_delay = TimeDelta::Millis(11);
  timing.set_render_delay(render_delay);
  TimeDelta min_playout_delay = TimeDelta::Millis(50);
  timing.set_min_playout_delay(min_playout_delay);
  TimeDelta max_playout_delay = TimeDelta::Millis(500);
  timing.set_max_playout_delay(max_playout_delay);

  // On complete.
  timing.IncomingTimestamp(3000, clock.CurrentTime());
  clock.AdvanceTimeMilliseconds(1);

  // On decodable.
  Timestamp render_time =
      timing.RenderTime(/*next_temporal_unit_rtp=*/3000, clock.CurrentTime());
  TimeDelta minimum_delay = TimeDelta::Millis(123);
  timing.SetJitterDelay(minimum_delay);
  timing.UpdateCurrentDelay(render_time, clock.CurrentTime());
  clock.AdvanceTimeMilliseconds(100);

  // On decoded.
  TimeDelta decode_time = TimeDelta::Millis(4);
  timing.StopDecodeTimer(decode_time, clock.CurrentTime());

  VCMTiming::VideoDelayTimings timings = timing.GetTimings();
  EXPECT_EQ(timings.num_decoded_frames, 1u);
  EXPECT_EQ(timings.minimum_delay, minimum_delay);
  // A single decoded frame is not enough to calculate p95.
  EXPECT_EQ(timings.estimated_max_decode_time, TimeDelta::Zero());
  EXPECT_EQ(timings.render_delay, render_delay);
  EXPECT_EQ(timings.min_playout_delay, min_playout_delay);
  EXPECT_EQ(timings.max_playout_delay, max_playout_delay);
  EXPECT_EQ(timings.target_delay, minimum_delay);
  EXPECT_EQ(timings.current_delay, minimum_delay);
  EXPECT_THAT(timings, HasConsistentVideoDelayTimings());
}

}  // namespace webrtc
