/*
 *  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 "modules/video_coding/timing/timestamp_extrapolator.h"

#include <stdint.h>

#include <limits>

#include "absl/types/optional.h"
#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "system_wrappers/include/clock.h"
#include "test/gmock.h"
#include "test/gtest.h"

namespace webrtc {

using ::testing::Eq;
using ::testing::Optional;

namespace {

constexpr Frequency kRtpHz = Frequency::KiloHertz(90);
constexpr Frequency k25Fps = Frequency::Hertz(25);
constexpr TimeDelta k25FpsDelay = 1 / k25Fps;

}  // namespace

TEST(TimestampExtrapolatorTest, ExtrapolationOccursAfter2Packets) {
  SimulatedClock clock(Timestamp::Millis(1337));
  TimestampExtrapolator ts_extrapolator(clock.CurrentTime());

  // No packets so no timestamp.
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(90000), Eq(absl::nullopt));

  uint32_t rtp = 90000;
  clock.AdvanceTime(k25FpsDelay);
  // First result is a bit confusing since it is based off the "start" time,
  // which is arbitrary.
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));

  rtp += kRtpHz / k25Fps;
  clock.AdvanceTime(k25FpsDelay);
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
              Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));
}

TEST(TimestampExtrapolatorTest, ResetsAfter10SecondPause) {
  SimulatedClock clock(Timestamp::Millis(1337));
  TimestampExtrapolator ts_extrapolator(clock.CurrentTime());

  uint32_t rtp = 90000;
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));

  rtp += kRtpHz / k25Fps;
  clock.AdvanceTime(k25FpsDelay);
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));

  rtp += 10 * kRtpHz.hertz();
  clock.AdvanceTime(TimeDelta::Seconds(10) + TimeDelta::Micros(1));
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));
}

TEST(TimestampExtrapolatorTest, TimestampExtrapolatesMultipleRtpWrapArounds) {
  SimulatedClock clock(Timestamp::Millis(1337));
  TimestampExtrapolator ts_extrapolator(clock.CurrentTime());

  uint32_t rtp = std::numeric_limits<uint32_t>::max();
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));

  // One overflow. Static cast to avoid undefined behaviour with +=.
  rtp += static_cast<uint32_t>(kRtpHz / k25Fps);
  clock.AdvanceTime(k25FpsDelay);
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));

  // Assert that extrapolation works across the boundary as expected.
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
              Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));
  // This is not quite 1s since the math always rounds up.
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp - 90000),
              Optional(clock.CurrentTime() - TimeDelta::Millis(999)));

  // In order to avoid a wrap arounds reset, add a packet every 10s until we
  // overflow twice.
  constexpr TimeDelta kRtpOverflowDelay =
      std::numeric_limits<uint32_t>::max() / kRtpHz;
  const Timestamp overflow_time = clock.CurrentTime() + kRtpOverflowDelay * 2;

  while (clock.CurrentTime() < overflow_time) {
    clock.AdvanceTime(TimeDelta::Seconds(10));
    // Static-cast before += to avoid undefined behaviour of overflow.
    rtp += static_cast<uint32_t>(kRtpHz * TimeDelta::Seconds(10));
    ts_extrapolator.Update(clock.CurrentTime(), rtp);
    EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
                Optional(clock.CurrentTime()));
  }
}

TEST(TimestampExtrapolatorTest, NegativeRtpTimestampWrapAround) {
  SimulatedClock clock(Timestamp::Millis(1337));
  TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
  uint32_t rtp = 0;
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));
  // Go backwards! Static cast to avoid undefined behaviour with -=.
  rtp -= static_cast<uint32_t>(kRtpHz.hertz());
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime() - TimeDelta::Seconds(1)));
}

TEST(TimestampExtrapolatorTest, NegativeRtpTimestampWrapAroundSecondScenario) {
  SimulatedClock clock(Timestamp::Millis(1337));
  TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
  uint32_t rtp = 0;
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));
  // Go backwards! Static cast to avoid undefined behaviour with -=.
  rtp -= static_cast<uint32_t>(kRtpHz * TimeDelta::Seconds(10));
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp), absl::nullopt);
}

TEST(TimestampExtrapolatorTest, Slow90KHzClock) {
  // This simulates a slow camera, which produces frames at 24Hz instead of
  // 25Hz. The extrapolator should be able to resolve this with enough data.
  SimulatedClock clock(Timestamp::Millis(1337));
  TimestampExtrapolator ts_extrapolator(clock.CurrentTime());

  constexpr TimeDelta k24FpsDelay = 1 / Frequency::Hertz(24);
  uint32_t rtp = 90000;
  ts_extrapolator.Update(clock.CurrentTime(), rtp);

  // Slow camera will increment RTP at 25 FPS rate even though its producing at
  // 24 FPS. After 25 frames the extrapolator should settle at this rate.
  for (int i = 0; i < 25; ++i) {
    rtp += kRtpHz / k25Fps;
    clock.AdvanceTime(k24FpsDelay);
    ts_extrapolator.Update(clock.CurrentTime(), rtp);
  }

  // The camera would normally produce 25 frames in 90K ticks, but is slow
  // so takes 1s + k24FpsDelay for 90K ticks.
  constexpr Frequency kSlowRtpHz = 90000 / (25 * k24FpsDelay);
  // The extrapolator will be predicting that time at millisecond precision.
  auto ts = ts_extrapolator.ExtrapolateLocalTime(rtp + kSlowRtpHz.hertz());
  ASSERT_TRUE(ts.has_value());
  EXPECT_EQ(ts->ms(), clock.TimeInMilliseconds() + 1000);
}

TEST(TimestampExtrapolatorTest, Fast90KHzClock) {
  // This simulates a fast camera, which produces frames at 26Hz instead of
  // 25Hz. The extrapolator should be able to resolve this with enough data.
  SimulatedClock clock(Timestamp::Millis(1337));
  TimestampExtrapolator ts_extrapolator(clock.CurrentTime());

  constexpr TimeDelta k26FpsDelay = 1 / Frequency::Hertz(26);
  uint32_t rtp = 90000;
  ts_extrapolator.Update(clock.CurrentTime(), rtp);

  // Fast camera will increment RTP at 25 FPS rate even though its producing at
  // 26 FPS. After 25 frames the extrapolator should settle at this rate.
  for (int i = 0; i < 25; ++i) {
    rtp += kRtpHz / k25Fps;
    clock.AdvanceTime(k26FpsDelay);
    ts_extrapolator.Update(clock.CurrentTime(), rtp);
  }

  // The camera would normally produce 25 frames in 90K ticks, but is slow
  // so takes 1s + k24FpsDelay for 90K ticks.
  constexpr Frequency kSlowRtpHz = 90000 / (25 * k26FpsDelay);
  // The extrapolator will be predicting that time at millisecond precision.
  auto ts = ts_extrapolator.ExtrapolateLocalTime(rtp + kSlowRtpHz.hertz());
  ASSERT_TRUE(ts.has_value());
  EXPECT_EQ(ts->ms(), clock.TimeInMilliseconds() + 1000);
}

TEST(TimestampExtrapolatorTest, TimestampJump) {
  // This simulates a jump in RTP timestamp, which could occur if a camera was
  // swapped for example.
  SimulatedClock clock(Timestamp::Millis(1337));
  TimestampExtrapolator ts_extrapolator(clock.CurrentTime());

  uint32_t rtp = 90000;
  clock.AdvanceTime(k25FpsDelay);
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  rtp += kRtpHz / k25Fps;
  clock.AdvanceTime(k25FpsDelay);
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  rtp += kRtpHz / k25Fps;
  clock.AdvanceTime(k25FpsDelay);
  ts_extrapolator.Update(clock.CurrentTime(), rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
              Optional(clock.CurrentTime()));
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
              Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));

  // Jump RTP.
  uint32_t new_rtp = 1337 * 90000;
  clock.AdvanceTime(k25FpsDelay);
  ts_extrapolator.Update(clock.CurrentTime(), new_rtp);
  new_rtp += kRtpHz / k25Fps;
  clock.AdvanceTime(k25FpsDelay);
  ts_extrapolator.Update(clock.CurrentTime(), new_rtp);
  EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(new_rtp),
              Optional(clock.CurrentTime()));
}

}  // namespace webrtc
