/*
 *  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/decode_synchronizer.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "absl/functional/any_invocable.h"
#include "api/metronome/test/fake_metronome.h"
#include "api/units/time_delta.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/time_controller/simulated_time_controller.h"
#include "video/frame_decode_scheduler.h"
#include "video/frame_decode_timing.h"

using ::testing::_;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::MockFunction;
using ::testing::Return;

namespace webrtc {

class MockMetronome : public Metronome {
 public:
  MOCK_METHOD(void,
              RequestCallOnNextTick,
              (absl::AnyInvocable<void() &&> callback),
              (override));
  MOCK_METHOD(TimeDelta, TickPeriod, (), (const override));
};

class DecodeSynchronizerTest : public ::testing::Test {
 public:
  static constexpr TimeDelta kTickPeriod = TimeDelta::Millis(33);

  DecodeSynchronizerTest()
      : time_controller_(Timestamp::Millis(1337)),
        clock_(time_controller_.GetClock()),
        metronome_(kTickPeriod),
        decode_synchronizer_(clock_,
                             &metronome_,
                             time_controller_.GetMainThread()) {}

 protected:
  GlobalSimulatedTimeController time_controller_;
  Clock* clock_;
  test::ForcedTickMetronome metronome_;
  DecodeSynchronizer decode_synchronizer_;
};

TEST_F(DecodeSynchronizerTest, AllFramesReadyBeforeNextTickDecoded) {
  MockFunction<void(uint32_t, Timestamp)> mock_callback1;
  auto scheduler1 = decode_synchronizer_.CreateSynchronizedFrameScheduler();

  MockFunction<void(unsigned int, Timestamp)> mock_callback2;
  auto scheduler2 = decode_synchronizer_.CreateSynchronizedFrameScheduler();

  {
    uint32_t frame_rtp = 90000;
    FrameDecodeTiming::FrameSchedule frame_sched{
        .latest_decode_time =
            clock_->CurrentTime() + kTickPeriod - TimeDelta::Millis(3),
        .render_time = clock_->CurrentTime() + TimeDelta::Millis(60)};
    scheduler1->ScheduleFrame(frame_rtp, frame_sched,
                              mock_callback1.AsStdFunction());
    EXPECT_CALL(mock_callback1,
                Call(Eq(frame_rtp), Eq(frame_sched.render_time)));
  }
  {
    uint32_t frame_rtp = 123456;
    FrameDecodeTiming::FrameSchedule frame_sched{
        .latest_decode_time =
            clock_->CurrentTime() + kTickPeriod - TimeDelta::Millis(2),
        .render_time = clock_->CurrentTime() + TimeDelta::Millis(70)};
    scheduler2->ScheduleFrame(frame_rtp, frame_sched,
                              mock_callback2.AsStdFunction());
    EXPECT_CALL(mock_callback2,
                Call(Eq(frame_rtp), Eq(frame_sched.render_time)));
  }
  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());

  // Cleanup
  scheduler1->Stop();
  scheduler2->Stop();
}

TEST_F(DecodeSynchronizerTest, FramesNotDecodedIfDecodeTimeIsInNextInterval) {
  MockFunction<void(unsigned int, Timestamp)> mock_callback;
  auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();

  uint32_t frame_rtp = 90000;
  FrameDecodeTiming::FrameSchedule frame_sched{
      .latest_decode_time =
          clock_->CurrentTime() + kTickPeriod + TimeDelta::Millis(10),
      .render_time =
          clock_->CurrentTime() + kTickPeriod + TimeDelta::Millis(30)};
  scheduler->ScheduleFrame(frame_rtp, frame_sched,
                           mock_callback.AsStdFunction());

  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());
  // No decodes should have happened in this tick.
  ::testing::Mock::VerifyAndClearExpectations(&mock_callback);

  // Decode should happen on next tick.
  EXPECT_CALL(mock_callback, Call(Eq(frame_rtp), Eq(frame_sched.render_time)));
  time_controller_.AdvanceTime(kTickPeriod);
  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());

  // Cleanup
  scheduler->Stop();
}

TEST_F(DecodeSynchronizerTest, FrameDecodedOnce) {
  MockFunction<void(unsigned int, Timestamp)> mock_callback;
  auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();

  uint32_t frame_rtp = 90000;
  FrameDecodeTiming::FrameSchedule frame_sched{
      .latest_decode_time = clock_->CurrentTime() + TimeDelta::Millis(30),
      .render_time = clock_->CurrentTime() + TimeDelta::Millis(60)};
  scheduler->ScheduleFrame(frame_rtp, frame_sched,
                           mock_callback.AsStdFunction());
  EXPECT_CALL(mock_callback, Call(_, _)).Times(1);
  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());
  ::testing::Mock::VerifyAndClearExpectations(&mock_callback);

  // Trigger tick again. No frame should be decoded now.
  time_controller_.AdvanceTime(kTickPeriod);
  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());

  // Cleanup
  scheduler->Stop();
}

TEST_F(DecodeSynchronizerTest, FrameWithDecodeTimeInPastDecodedImmediately) {
  MockFunction<void(unsigned int, Timestamp)> mock_callback;
  auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();

  uint32_t frame_rtp = 90000;
  FrameDecodeTiming::FrameSchedule frame_sched{
      .latest_decode_time = clock_->CurrentTime() - TimeDelta::Millis(5),
      .render_time = clock_->CurrentTime() + TimeDelta::Millis(30)};
  EXPECT_CALL(mock_callback, Call(Eq(90000u), _)).Times(1);
  scheduler->ScheduleFrame(frame_rtp, frame_sched,
                           mock_callback.AsStdFunction());
  // Verify the callback was invoked already.
  ::testing::Mock::VerifyAndClearExpectations(&mock_callback);

  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());

  // Cleanup
  scheduler->Stop();
}

TEST_F(DecodeSynchronizerTest,
       FrameWithDecodeTimeFarBeforeNextTickDecodedImmediately) {
  MockFunction<void(unsigned int, Timestamp)> mock_callback;
  auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();

  // Frame which would be behind by more than kMaxAllowedFrameDelay after
  // the next tick.
  FrameDecodeTiming::FrameSchedule frame_sched{
      .latest_decode_time = clock_->CurrentTime() + kTickPeriod -
                            FrameDecodeTiming::kMaxAllowedFrameDelay -
                            TimeDelta::Millis(1),
      .render_time = clock_->CurrentTime() + TimeDelta::Millis(30)};
  EXPECT_CALL(mock_callback, Call(Eq(90000u), _)).Times(1);
  scheduler->ScheduleFrame(90000, frame_sched, mock_callback.AsStdFunction());
  // Verify the callback was invoked already.
  ::testing::Mock::VerifyAndClearExpectations(&mock_callback);

  time_controller_.AdvanceTime(kTickPeriod);
  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());

  // A frame that would be behind by exactly kMaxAllowedFrameDelay after next
  // tick should decode at the next tick.
  FrameDecodeTiming::FrameSchedule queued_frame{
      .latest_decode_time = clock_->CurrentTime() + kTickPeriod -
                            FrameDecodeTiming::kMaxAllowedFrameDelay,
      .render_time = clock_->CurrentTime() + TimeDelta::Millis(30)};
  scheduler->ScheduleFrame(180000, queued_frame, mock_callback.AsStdFunction());
  // Verify the callback was invoked already.
  ::testing::Mock::VerifyAndClearExpectations(&mock_callback);

  EXPECT_CALL(mock_callback, Call(Eq(180000u), _)).Times(1);
  time_controller_.AdvanceTime(kTickPeriod);
  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());

  // Cleanup
  scheduler->Stop();
}

TEST_F(DecodeSynchronizerTest, FramesNotReleasedAfterStop) {
  MockFunction<void(unsigned int, Timestamp)> mock_callback;
  auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();

  uint32_t frame_rtp = 90000;
  FrameDecodeTiming::FrameSchedule frame_sched{
      .latest_decode_time = clock_->CurrentTime() + TimeDelta::Millis(30),
      .render_time = clock_->CurrentTime() + TimeDelta::Millis(60)};
  scheduler->ScheduleFrame(frame_rtp, frame_sched,
                           mock_callback.AsStdFunction());
  // Cleanup
  scheduler->Stop();

  // No callback should occur on this tick since Stop() was called before.
  metronome_.Tick();
  time_controller_.AdvanceTime(TimeDelta::Zero());
}

TEST(DecodeSynchronizerStandaloneTest,
     MetronomeNotListenedWhenNoStreamsAreActive) {
  GlobalSimulatedTimeController time_controller(Timestamp::Millis(4711));
  Clock* clock(time_controller.GetClock());
  MockMetronome metronome;
  ON_CALL(metronome, TickPeriod).WillByDefault(Return(TimeDelta::Seconds(1)));
  DecodeSynchronizer decode_synchronizer_(clock, &metronome,
                                          time_controller.GetMainThread());
  absl::AnyInvocable<void() &&> callback;
  EXPECT_CALL(metronome, RequestCallOnNextTick)
      .WillOnce(Invoke([&callback](absl::AnyInvocable<void() &&> cb) {
        callback = std::move(cb);
      }));
  auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();
  auto scheduler2 = decode_synchronizer_.CreateSynchronizedFrameScheduler();
  scheduler->Stop();
  scheduler2->Stop();
  time_controller.AdvanceTime(TimeDelta::Seconds(1));
  ASSERT_TRUE(callback);
  (std::move)(callback)();
}

TEST(DecodeSynchronizerStandaloneTest,
     RegistersCallbackOnceDuringRepeatedRegistrations) {
  GlobalSimulatedTimeController time_controller(Timestamp::Millis(4711));
  Clock* clock(time_controller.GetClock());
  MockMetronome metronome;
  ON_CALL(metronome, TickPeriod).WillByDefault(Return(TimeDelta::Seconds(1)));
  DecodeSynchronizer decode_synchronizer_(clock, &metronome,
                                          time_controller.GetMainThread());
  // Expect at most 1 call to register a callback.
  EXPECT_CALL(metronome, RequestCallOnNextTick);
  auto scheduler1 = decode_synchronizer_.CreateSynchronizedFrameScheduler();
  scheduler1->Stop();
  auto scheduler2 = decode_synchronizer_.CreateSynchronizedFrameScheduler();
  Mock::VerifyAndClearExpectations(&metronome);
  scheduler2->Stop();
}

}  // namespace webrtc
