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

#include <memory>
#include <utility>

#include "api/test/mock_video_decoder.h"
#include "api/units/timestamp.h"
#include "api/video/encoded_frame.h"
#include "common_video/test/utilities.h"
#include "modules/video_coding/decoder_database.h"
#include "modules/video_coding/timing/timing.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 {

using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;

class MockVCMReceiveCallback : public VCMReceiveCallback {
 public:
  MockVCMReceiveCallback() = default;

  MOCK_METHOD(int32_t,
              FrameToRender,
              (VideoFrame&,
               absl::optional<uint8_t>,
               TimeDelta,
               VideoContentType,
               VideoFrameType),
              (override));
  MOCK_METHOD(void, OnIncomingPayloadType, (int), (override));
  MOCK_METHOD(void,
              OnDecoderInfoChanged,
              (const VideoDecoder::DecoderInfo&),
              (override));
};

class TestEncodedFrame : public EncodedFrame {
 public:
  explicit TestEncodedFrame(int payload_type) {
    _payloadType = payload_type;
    SetPacketInfos(CreatePacketInfos(3));
  }

  void SetReceivedTime(webrtc::Timestamp received_time) {
    received_time_ = received_time;
  }

  int64_t ReceivedTime() const override { return received_time_.ms(); }

  int64_t RenderTime() const override { return _renderTimeMs; }

 private:
  webrtc::Timestamp received_time_ = webrtc::Timestamp::Millis(0);
};

class VideoReceiver2Test : public ::testing::Test {
 protected:
  VideoReceiver2Test() {
    receiver_.RegisterReceiveCallback(&receive_callback_);
  }

  void RegisterReceiveCodecSettings(
      int payload_type,
      VideoCodecType codec_type = kVideoCodecVP8) {
    VideoDecoder::Settings settings;
    settings.set_codec_type(codec_type);
    settings.set_max_render_resolution({10, 10});
    settings.set_number_of_cores(4);
    receiver_.RegisterReceiveCodec(payload_type, settings);
  }

  test::ScopedKeyValueConfig field_trials_;
  SimulatedClock clock_{Timestamp::Millis(1337)};
  VCMTiming timing_{&clock_, field_trials_};
  NiceMock<MockVCMReceiveCallback> receive_callback_;
  VideoReceiver2 receiver_{&clock_, &timing_, field_trials_};
};

TEST_F(VideoReceiver2Test, RegisterExternalDecoder) {
  constexpr int kPayloadType = 1;
  ASSERT_FALSE(receiver_.IsExternalDecoderRegistered(kPayloadType));

  // Register a decoder, check for correctness, then unregister and check again.
  auto decoder = std::make_unique<NiceMock<MockVideoDecoder>>();
  bool decoder_deleted = false;
  EXPECT_CALL(*decoder, Destruct).WillOnce([&decoder_deleted] {
    decoder_deleted = true;
  });
  receiver_.RegisterExternalDecoder(std::move(decoder), kPayloadType);
  EXPECT_TRUE(receiver_.IsExternalDecoderRegistered(kPayloadType));
  receiver_.RegisterExternalDecoder(nullptr, kPayloadType);
  EXPECT_TRUE(decoder_deleted);
  EXPECT_FALSE(receiver_.IsExternalDecoderRegistered(kPayloadType));
}

TEST_F(VideoReceiver2Test, RegisterReceiveCodecs) {
  constexpr int kPayloadType = 1;

  RegisterReceiveCodecSettings(kPayloadType);

  TestEncodedFrame frame(kPayloadType);

  // A decoder has not been registered yet, so an attempt to decode should fail.
  EXPECT_EQ(receiver_.Decode(&frame), VCM_NO_CODEC_REGISTERED);

  // Register a decoder that will accept the Decode operation.
  auto decoder = std::make_unique<NiceMock<MockVideoDecoder>>();
  EXPECT_CALL(*decoder, RegisterDecodeCompleteCallback)
      .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK));
  EXPECT_CALL(*decoder, Decode(_, _)).WillOnce(Return(WEBRTC_VIDEO_CODEC_OK));
  EXPECT_CALL(*decoder, Release).WillOnce(Return(WEBRTC_VIDEO_CODEC_OK));

  // Register the decoder. Note that this moves ownership of the mock object
  // to the `receiver_`.
  receiver_.RegisterExternalDecoder(std::move(decoder), kPayloadType);
  EXPECT_TRUE(receiver_.IsExternalDecoderRegistered(kPayloadType));

  EXPECT_CALL(receive_callback_, OnIncomingPayloadType(kPayloadType));
  EXPECT_CALL(receive_callback_, OnDecoderInfoChanged);

  // Call `Decode`. This triggers the above call expectations.
  EXPECT_EQ(receiver_.Decode(&frame), VCM_OK);

  // Unregister the decoder and verify.
  receiver_.RegisterExternalDecoder(nullptr, kPayloadType);
  EXPECT_FALSE(receiver_.IsExternalDecoderRegistered(kPayloadType));

  receiver_.DeregisterReceiveCodec(kPayloadType);
}

}  // namespace
}  // namespace webrtc