/*
 *  Copyright (c) 2022 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/frame_decode_timing.h"

#include <cstdint>
#include <optional>

#include "api/environment/environment.h"
#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "modules/video_coding/timing/timing.h"
#include "system_wrappers/include/clock.h"
#include "test/create_test_environment.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "video/video_receive_stream2.h"

namespace webrtc {

using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Optional;

namespace {

constexpr uint32_t kNextRtp = 90000;
constexpr uint32_t kLastRtp = 180000;
constexpr Frequency k25Fps = Frequency::Hertz(25);
constexpr TimeDelta kDecodeTime = TimeDelta::Millis(20);
constexpr TimeDelta kRenderDelay = TimeDelta::Millis(15);

void UpdateDecodeTimer(VCMTiming& timing,
                       SimulatedClock& clock,
                       TimeDelta decode_time) {
  for (int i = 0; i < k25Fps.hertz(); ++i) {
    clock.AdvanceTime(decode_time);
    timing.UpdateDecodeTimeEstimate(decode_time, clock.CurrentTime());
    clock.AdvanceTime(1 / k25Fps - decode_time);
  }
}

class FrameDecodeTimingTest : public ::testing::Test {
 public:
  FrameDecodeTimingTest()
      : clock_(Timestamp::Millis(1000)),
        env_(CreateTestEnvironment({.time = &clock_})),
        timing_(env_, /*render_delay=*/TimeDelta::Zero()),
        frame_decode_scheduler_(&clock_, &timing_) {
    timing_.OnCompleteFrame({.rtp_timestamp = kNextRtp,
                             .time = clock_.CurrentTime(),
                             .last_spatial_layer = true});
  }

 protected:
  SimulatedClock clock_;
  Environment env_;
  VCMTiming timing_;
  FrameDecodeTiming frame_decode_scheduler_;
};

TEST_F(FrameDecodeTimingTest, ReturnsWaitTimesWhenValid) {
  const TimeDelta kDecodeDelay = TimeDelta::Millis(42);
  timing_.SetMinimumDelay(kDecodeDelay);

  EXPECT_THAT(frame_decode_scheduler_.OnFrameBufferUpdated(
                  kNextRtp, kLastRtp, kMaxWaitForFrame, false),
              Optional(AllOf(
                  Field(&FrameDecodeTiming::FrameSchedule::latest_decode_time,
                        Eq(clock_.CurrentTime() + kDecodeDelay)),
                  Field(&FrameDecodeTiming::FrameSchedule::render_time,
                        Eq(clock_.CurrentTime() + kDecodeDelay)))));
}

TEST_F(FrameDecodeTimingTest, FastForwardsFrameTooFarInThePast) {
  const TimeDelta kDecodeDelay =
      -FrameDecodeTiming::kMaxAllowedFrameDelay - TimeDelta::Millis(1);
  timing_.SetMinimumDelay(kDecodeDelay);
  timing_.set_min_playout_delay(kDecodeDelay);

  EXPECT_THAT(frame_decode_scheduler_.OnFrameBufferUpdated(
                  kNextRtp, kLastRtp, kMaxWaitForFrame, false),
              Eq(std::nullopt));
}

TEST_F(FrameDecodeTimingTest, NoFastForwardIfOnlyFrameToDecode) {
  const TimeDelta kDecodeDelay =
      -FrameDecodeTiming::kMaxAllowedFrameDelay - TimeDelta::Millis(1);
  timing_.SetMinimumDelay(kDecodeDelay);
  timing_.set_min_playout_delay(kDecodeDelay);

  // Negative `kDecodeDelay` means that `latest_decode_time` is now.
  EXPECT_THAT(frame_decode_scheduler_.OnFrameBufferUpdated(
                  kNextRtp, kNextRtp, kMaxWaitForFrame, false),
              Optional(AllOf(
                  Field(&FrameDecodeTiming::FrameSchedule::latest_decode_time,
                        Eq(clock_.CurrentTime())),
                  Field(&FrameDecodeTiming::FrameSchedule::render_time,
                        Eq(clock_.CurrentTime() + kDecodeDelay)))));
}

TEST_F(FrameDecodeTimingTest, MaxWaitCapped) {
  const TimeDelta kDecodeDelay = kMaxWaitForFrame * 2;
  timing_.SetMinimumDelay(kDecodeDelay);

  EXPECT_THAT(frame_decode_scheduler_.OnFrameBufferUpdated(
                  kNextRtp, kLastRtp, kMaxWaitForFrame, false),
              Optional(AllOf(
                  Field(&FrameDecodeTiming::FrameSchedule::latest_decode_time,
                        Eq(clock_.CurrentTime() + kMaxWaitForFrame)),
                  Field(&FrameDecodeTiming::FrameSchedule::render_time,
                        Eq(clock_.CurrentTime() + kDecodeDelay)))));
}

TEST_F(FrameDecodeTimingTest, MaxWaitCappedForKey) {
  const TimeDelta kDecodeDelay = kMaxWaitForKeyFrame * 2;
  timing_.SetMinimumDelay(kDecodeDelay);

  EXPECT_THAT(frame_decode_scheduler_.OnFrameBufferUpdated(
                  kNextRtp, kLastRtp, kMaxWaitForKeyFrame, false),
              Optional(AllOf(
                  Field(&FrameDecodeTiming::FrameSchedule::latest_decode_time,
                        Eq(clock_.CurrentTime() + kMaxWaitForKeyFrame)),
                  Field(&FrameDecodeTiming::FrameSchedule::render_time,
                        Eq(clock_.CurrentTime() + kDecodeDelay)))));
}

TEST(FrameDecodeTimingMaxWaitingTimeTest, IsZeroForZeroRenderTime) {
  // 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);
  Environment env = CreateTestEnvironment({.time = &clock});

  VCMTiming timing(env, kRenderDelay);
  timing.set_playout_delay({TimeDelta::Zero(), TimeDelta::Zero()});
  FrameDecodeTiming decode_timing(&clock, &timing);

  for (int i = 0; i < 10; ++i) {
    clock.AdvanceTime(kTimeDelta);
    Timestamp now = clock.CurrentTime();
    EXPECT_LT(decode_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(decode_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(decode_timing.MaxWaitingTime(kZeroRenderTime, now,
                                         /*too_many_frames_queued=*/false),
            TimeDelta::Zero());
  EXPECT_LT(decode_timing.MaxWaitingTime(kZeroRenderTime, now,
                                         /*too_many_frames_queued=*/false),
            TimeDelta::Zero());
  EXPECT_LT(decode_timing.MaxWaitingTime(kZeroRenderTime, now,
                                         /*too_many_frames_queued=*/false),
            TimeDelta::Zero());
}

TEST(FrameDecodeTimingMaxWaitingTimeTest, WithZeroDelayPacingActive) {
  // The minimum pacing is active if the RTP playout delay header extension
  // is set to min==0 and max>0.
  constexpr TimeDelta kMinPacing =
      FrameDecodeTiming::kZeroPlayoutDelayMinPacing;
  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);
  Environment env = CreateTestEnvironment({.time = &clock});
  VCMTiming timing(env, kRenderDelay);
  FrameDecodeTiming decode_timing(&clock, &timing);

  // MaxWaitingTime() returns zero for evenly spaced video frames.
  for (int i = 0; i < 10; ++i) {
    clock.AdvanceTime(kTimeDelta);
    Timestamp now = clock.CurrentTime();
    EXPECT_EQ(decode_timing.MaxWaitingTime(kZeroRenderTime, now,
                                           /*too_many_frames_queued=*/false),
              TimeDelta::Zero());
    decode_timing.SetLastDecodeScheduledTimestamp(now);
  }
  // Another frame submitted at the same time is paced according to the default
  // pacing setting.
  Timestamp now = clock.CurrentTime();
  EXPECT_EQ(decode_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(decode_timing.MaxWaitingTime(kZeroRenderTime, now,
                                         /*too_many_frames_queued=*/false),
            kMinPacing);
  EXPECT_EQ(decode_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(decode_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.
  decode_timing.SetLastDecodeScheduledTimestamp(now);
  EXPECT_EQ(decode_timing.MaxWaitingTime(kZeroRenderTime, now,
                                         /*too_many_frames_queued=*/false),
            kMinPacing);
}

TEST(FrameDecodeTimingMaxWaitingTimeTest,
     DefaultMaxWaitingTimeUnaffectedByZeroPlayoutPacing) {
  // The minimum pacing should not have any effect if render_time is greater
  // than 0.
  constexpr int64_t kStartTimeUs = 3.15e13;  // About one year in us.
  const TimeDelta kTimeDelta = TimeDelta::Millis(1000.0 / 60.0);
  SimulatedClock clock(kStartTimeUs);
  Environment env = CreateTestEnvironment({.time = &clock});
  VCMTiming timing(env, kRenderDelay);
  FrameDecodeTiming decode_timing(&clock, &timing);

  clock.AdvanceTime(kTimeDelta);
  Timestamp 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) -
      decode_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(decode_timing.MaxWaitingTime(render_time, now,
                                           /*too_many_frames_queued=*/false),
              render_time - now - estimated_processing_delay);
  }
}

TEST(FrameDecodeTimingMaxWaitingTimeTest, ReturnsZeroIfTooManyFramesAreQueued) {
  // The minimum pacing is active if the RTP playout delay header extension is
  // set to min==0 and max>0.
  constexpr TimeDelta kMinPacing =
      FrameDecodeTiming::kZeroPlayoutDelayMinPacing;
  constexpr int64_t kStartTimeUs = 3.15e13;  // About one year in us.
  const TimeDelta kTimeDelta = TimeDelta::Millis(1000.0 / 60.0);
  constexpr Timestamp kZeroRenderTime = Timestamp::Zero();
  SimulatedClock clock(kStartTimeUs);
  Environment env = CreateTestEnvironment({.time = &clock});
  VCMTiming timing(env, kRenderDelay);
  FrameDecodeTiming decode_timing(&clock, &timing);

  // MaxWaitingTime() returns zero for evenly spaced video frames.
  for (int i = 0; i < 10; ++i) {
    clock.AdvanceTime(kTimeDelta);
    Timestamp now = clock.CurrentTime();
    EXPECT_EQ(decode_timing.MaxWaitingTime(kZeroRenderTime, now,
                                           /*too_many_frames_queued=*/false),
              TimeDelta::Zero());
    decode_timing.SetLastDecodeScheduledTimestamp(now);
  }
  // Another frame submitted at the same time is paced according to the
  // default pacing setting.
  Timestamp now_ms = clock.CurrentTime();
  EXPECT_EQ(decode_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(decode_timing.MaxWaitingTime(kZeroRenderTime, now_ms,
                                         /*too_many_frames_queued=*/true),
            TimeDelta::Zero());
  EXPECT_EQ(decode_timing.MaxWaitingTime(kZeroRenderTime, now_ms,
                                         /*too_many_frames_queued=*/true),
            TimeDelta::Zero());
}

TEST(FrameDecodeTimingMaxWaitingTimeTest, WithVaryingRenderTimes) {
  SimulatedClock clock(0);
  Environment env = CreateTestEnvironment({.time = &clock});
  VCMTiming timing(env, kRenderDelay);
  UpdateDecodeTimer(timing, clock, kDecodeTime);
  FrameDecodeTiming decode_timing(&clock, &timing);

  Timestamp on_time = clock.CurrentTime() + kDecodeTime + kRenderDelay;

  // Early frame.
  Timestamp render_time = on_time + TimeDelta::Millis(1);
  EXPECT_EQ(decode_timing.MaxWaitingTime(render_time, clock.CurrentTime(),
                                         /*too_many_frames_queued=*/false),
            TimeDelta::Millis(1));

  // Exactly on time.
  render_time = on_time;
  EXPECT_EQ(decode_timing.MaxWaitingTime(render_time, clock.CurrentTime(),
                                         /*too_many_frames_queued=*/false),
            TimeDelta::Zero());

  // Late frame.
  render_time = on_time - TimeDelta::Millis(1);
  EXPECT_EQ(decode_timing.MaxWaitingTime(render_time, clock.CurrentTime(),
                                         /*too_many_frames_queued=*/false),
            TimeDelta::Millis(-1));
}

}  // namespace
}  // namespace webrtc
