/*
 *  Copyright 2021 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 "common_video/framerate_controller.h"

#include <limits>

#include "rtc_base/time_utils.h"
#include "test/gtest.h"

namespace webrtc {
namespace {
constexpr int kInputFps = 30;
constexpr int kNumFrames = 60;
}  // namespace

class FramerateControllerTest : public ::testing::Test {
 protected:
  int64_t GetNextTimestampNs() {
    int64_t interval_us = rtc::kNumMicrosecsPerSec / kInputFps;
    next_timestamp_us_ += interval_us;
    return next_timestamp_us_ * rtc::kNumNanosecsPerMicrosec;
  }

  int64_t next_timestamp_us_ = rtc::TimeMicros();
  FramerateController controller_;
};

TEST_F(FramerateControllerTest, NoFramesDroppedIfNothingRequested) {
  // Default max framerate is maxdouble.
  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_FALSE(controller_.ShouldDropFrame(GetNextTimestampNs()));
}

TEST_F(FramerateControllerTest, AllFramesDroppedIfZeroRequested) {
  controller_.SetMaxFramerate(0);

  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_TRUE(controller_.ShouldDropFrame(GetNextTimestampNs()));
}

TEST_F(FramerateControllerTest, AllFramesDroppedIfNegativeRequested) {
  controller_.SetMaxFramerate(-1);

  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_TRUE(controller_.ShouldDropFrame(GetNextTimestampNs()));
}

TEST_F(FramerateControllerTest, EverySecondFrameDroppedIfHalfRequested) {
  controller_.SetMaxFramerate(kInputFps / 2);

  // The first frame should not be dropped.
  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_EQ(i % 2 == 0, controller_.ShouldDropFrame(GetNextTimestampNs()));
}

TEST_F(FramerateControllerTest, EveryThirdFrameDroppedIfTwoThirdsRequested) {
  controller_.SetMaxFramerate(kInputFps * 2 / 3);

  // The first frame should not be dropped.
  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_EQ(i % 3 == 0, controller_.ShouldDropFrame(GetNextTimestampNs()));
}

TEST_F(FramerateControllerTest, NoFrameDroppedIfTwiceRequested) {
  controller_.SetMaxFramerate(kInputFps * 2);

  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_FALSE(controller_.ShouldDropFrame(GetNextTimestampNs()));
}

TEST_F(FramerateControllerTest, TestAverageFramerate) {
  const double kMaxFps = 18.2;
  controller_.SetMaxFramerate(kMaxFps);

  const int kNumSec = 10;
  int frames_kept = 0;
  for (int i = 0; i < kInputFps * kNumSec; ++i) {
    if (!controller_.ShouldDropFrame(GetNextTimestampNs()))
      ++frames_kept;
  }
  double average_fps = static_cast<double>(frames_kept) / kNumSec;
  EXPECT_NEAR(kMaxFps, average_fps, 0.01);
}

TEST_F(FramerateControllerTest, NoFrameDroppedForLargeTimestampOffset) {
  controller_.SetMaxFramerate(kInputFps);
  EXPECT_FALSE(controller_.ShouldDropFrame(0));

  const int64_t kLargeOffsetNs = -987654321LL * 1000;
  EXPECT_FALSE(controller_.ShouldDropFrame(kLargeOffsetNs));

  int64_t input_interval_ns = rtc::kNumNanosecsPerSec / kInputFps;
  EXPECT_FALSE(controller_.ShouldDropFrame(kLargeOffsetNs + input_interval_ns));
}

TEST_F(FramerateControllerTest, NoFrameDroppedIfInputWithJitterRequested) {
  controller_.SetMaxFramerate(kInputFps);

  // Input fps with jitter.
  int64_t input_interval_ns = rtc::kNumNanosecsPerSec / kInputFps;
  EXPECT_FALSE(controller_.ShouldDropFrame(input_interval_ns * 0 / 10));
  EXPECT_FALSE(controller_.ShouldDropFrame(input_interval_ns * 10 / 10 - 1));
  EXPECT_FALSE(controller_.ShouldDropFrame(input_interval_ns * 25 / 10));
  EXPECT_FALSE(controller_.ShouldDropFrame(input_interval_ns * 30 / 10));
  EXPECT_FALSE(controller_.ShouldDropFrame(input_interval_ns * 35 / 10));
  EXPECT_FALSE(controller_.ShouldDropFrame(input_interval_ns * 50 / 10));
}

TEST_F(FramerateControllerTest, FrameDroppedWhenReductionRequested) {
  controller_.SetMaxFramerate(kInputFps);

  // Expect no frame drop.
  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_FALSE(controller_.ShouldDropFrame(GetNextTimestampNs()));

  // Reduce max frame rate.
  controller_.SetMaxFramerate(kInputFps / 2);

  // Verify that every other frame is dropped.
  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_EQ(i % 2 == 0, controller_.ShouldDropFrame(GetNextTimestampNs()));
}

TEST_F(FramerateControllerTest, NoFramesDroppedAfterReset) {
  controller_.SetMaxFramerate(0);

  // All frames dropped.
  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_TRUE(controller_.ShouldDropFrame(GetNextTimestampNs()));

  controller_.Reset();

  // Expect no frame drop after reset.
  for (int i = 1; i < kNumFrames; ++i)
    EXPECT_FALSE(controller_.ShouldDropFrame(GetNextTimestampNs()));
}

TEST_F(FramerateControllerTest, TestKeepFrame) {
  FramerateController controller(kInputFps / 2);

  EXPECT_FALSE(controller.ShouldDropFrame(GetNextTimestampNs()));
  EXPECT_TRUE(controller.ShouldDropFrame(GetNextTimestampNs()));
  EXPECT_FALSE(controller.ShouldDropFrame(GetNextTimestampNs()));
  EXPECT_TRUE(controller.ShouldDropFrame(GetNextTimestampNs()));
  EXPECT_FALSE(controller.ShouldDropFrame(GetNextTimestampNs()));

  // Next frame should be dropped.
  // Keep this frame (e.g. in case of a key frame).
  controller.KeepFrame(GetNextTimestampNs());
  // Expect next frame to be dropped instead.
  EXPECT_TRUE(controller.ShouldDropFrame(GetNextTimestampNs()));
}

}  // namespace webrtc
