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

#include <stdint.h>

#include <limits>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "absl/types/optional.h"
#include "test/gtest.h"

namespace webrtc {
namespace {

// The information about an RTP packet that is relevant in these tests.
struct Packet {
  uint16_t seq_num;
  bool first_in_frame;
  bool is_keyframe;
  int64_t frame_id;
  std::vector<int64_t> frame_dependencies;
};

Packet CreatePacket(
    bool first_in_frame,
    bool last_in_frame,
    uint16_t seq_num,
    uint16_t frame_id,
    bool is_key_frame,
    std::vector<int64_t> ref_frame_ids = std::vector<int64_t>()) {
  Packet packet;
  packet.seq_num = seq_num;
  packet.first_in_frame = first_in_frame;
  if (first_in_frame) {
    packet.is_keyframe = is_key_frame;
    packet.frame_id = frame_id;
    RTC_DCHECK(!is_key_frame || ref_frame_ids.empty());
    packet.frame_dependencies = std::move(ref_frame_ids);
  }
  return packet;
}

class PacketStreamCreator final {
 public:
  PacketStreamCreator() : seq_num_(0), frame_id_(0), next_is_key_frame_(true) {}

  Packet NextPacket() {
    std::vector<int64_t> ref_frame_ids;
    if (!next_is_key_frame_) {
      ref_frame_ids.push_back(frame_id_ - 1);
    }

    Packet packet = CreatePacket(true, true, seq_num_++, frame_id_++,
                                 next_is_key_frame_, ref_frame_ids);

    next_is_key_frame_ = false;

    return packet;
  }

 private:
  uint16_t seq_num_;
  int64_t frame_id_;
  bool next_is_key_frame_;
};
}  // namespace

// Most of the logic for the tests is here. Subclasses allow parameterizing
// the test, or adding some more specific logic.
class LossNotificationControllerBaseTest : public ::testing::Test,
                                           public KeyFrameRequestSender,
                                           public LossNotificationSender {
 protected:
  LossNotificationControllerBaseTest()
      : uut_(this, this), key_frame_requested_(false) {}

  ~LossNotificationControllerBaseTest() override {
    EXPECT_FALSE(LastKeyFrameRequest());
    EXPECT_FALSE(LastLossNotification());
  }

  // KeyFrameRequestSender implementation.
  void RequestKeyFrame() override {
    EXPECT_FALSE(LastKeyFrameRequest());
    EXPECT_FALSE(LastLossNotification());
    key_frame_requested_ = true;
  }

  // LossNotificationSender implementation.
  void SendLossNotification(uint16_t last_decoded_seq_num,
                            uint16_t last_received_seq_num,
                            bool decodability_flag,
                            bool buffering_allowed) override {
    EXPECT_TRUE(buffering_allowed);  // (Flag useful elsewhere.)
    EXPECT_FALSE(LastKeyFrameRequest());
    EXPECT_FALSE(LastLossNotification());
    last_loss_notification_.emplace(last_decoded_seq_num, last_received_seq_num,
                                    decodability_flag);
  }

  void OnReceivedPacket(const Packet& packet) {
    EXPECT_FALSE(LastKeyFrameRequest());
    EXPECT_FALSE(LastLossNotification());

    if (packet.first_in_frame) {
      previous_first_packet_in_frame_ = packet;
      LossNotificationController::FrameDetails frame;
      frame.is_keyframe = packet.is_keyframe;
      frame.frame_id = packet.frame_id;
      frame.frame_dependencies = packet.frame_dependencies;
      uut_.OnReceivedPacket(packet.seq_num, &frame);
    } else {
      uut_.OnReceivedPacket(packet.seq_num, nullptr);
    }
  }

  void OnAssembledFrame(uint16_t first_seq_num,
                        int64_t frame_id,
                        bool discardable) {
    EXPECT_FALSE(LastKeyFrameRequest());
    EXPECT_FALSE(LastLossNotification());

    ASSERT_TRUE(previous_first_packet_in_frame_);
    uut_.OnAssembledFrame(first_seq_num, frame_id, discardable,
                          previous_first_packet_in_frame_->frame_dependencies);
  }

  void ExpectKeyFrameRequest() {
    EXPECT_EQ(LastLossNotification(), absl::nullopt);
    EXPECT_TRUE(LastKeyFrameRequest());
  }

  void ExpectLossNotification(uint16_t last_decoded_seq_num,
                              uint16_t last_received_seq_num,
                              bool decodability_flag) {
    EXPECT_FALSE(LastKeyFrameRequest());
    const auto last_ln = LastLossNotification();
    ASSERT_TRUE(last_ln);
    const LossNotification expected_ln(
        last_decoded_seq_num, last_received_seq_num, decodability_flag);
    EXPECT_EQ(expected_ln, *last_ln)
        << "Expected loss notification (" << expected_ln.ToString()
        << ") != received loss notification (" << last_ln->ToString() + ")";
  }

  struct LossNotification {
    LossNotification(uint16_t last_decoded_seq_num,
                     uint16_t last_received_seq_num,
                     bool decodability_flag)
        : last_decoded_seq_num(last_decoded_seq_num),
          last_received_seq_num(last_received_seq_num),
          decodability_flag(decodability_flag) {}

    LossNotification& operator=(const LossNotification& other) = default;

    bool operator==(const LossNotification& other) const {
      return last_decoded_seq_num == other.last_decoded_seq_num &&
             last_received_seq_num == other.last_received_seq_num &&
             decodability_flag == other.decodability_flag;
    }

    std::string ToString() const {
      return std::to_string(last_decoded_seq_num) + ", " +
             std::to_string(last_received_seq_num) + ", " +
             std::to_string(decodability_flag);
    }

    uint16_t last_decoded_seq_num;
    uint16_t last_received_seq_num;
    bool decodability_flag;
  };

  bool LastKeyFrameRequest() {
    const bool result = key_frame_requested_;
    key_frame_requested_ = false;
    return result;
  }

  absl::optional<LossNotification> LastLossNotification() {
    const absl::optional<LossNotification> result = last_loss_notification_;
    last_loss_notification_ = absl::nullopt;
    return result;
  }

  LossNotificationController uut_;  // Unit under test.

  bool key_frame_requested_;

  absl::optional<LossNotification> last_loss_notification_;

  // First packet of last frame. (Note that if a test skips the first packet
  // of a subsequent frame, OnAssembledFrame is not called, and so this is
  // note read. Therefore, it's not a problem if it is not cleared when
  // the frame changes.)
  absl::optional<Packet> previous_first_packet_in_frame_;
};

class LossNotificationControllerTest
    : public LossNotificationControllerBaseTest,
      public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
 protected:
  // Arbitrary parameterized values, to be used by the tests whenever they
  // wish to either check some combinations, or wish to demonstrate that
  // a particular arbitrary value is unimportant.
  template <size_t N>
  bool Bool() const {
    return std::get<N>(GetParam());
  }
};

INSTANTIATE_TEST_SUITE_P(_,
                         LossNotificationControllerTest,
                         ::testing::Combine(::testing::Bool(),
                                            ::testing::Bool(),
                                            ::testing::Bool()));

// If the first frame, which is a key frame, is lost, then a new key frame
// is requested.
TEST_P(LossNotificationControllerTest,
       PacketLossBeforeFirstFrameAssembledTriggersKeyFrameRequest) {
  OnReceivedPacket(CreatePacket(true, false, 100, 0, true));
  OnReceivedPacket(CreatePacket(Bool<0>(), Bool<1>(), 103, 1, false, {0}));
  ExpectKeyFrameRequest();
}

// If packet loss occurs (but not of the first packet), then a loss notification
// is issued.
TEST_P(LossNotificationControllerTest,
       PacketLossAfterFirstFrameAssembledTriggersLossNotification) {
  OnReceivedPacket(CreatePacket(true, true, 100, 0, true));
  OnAssembledFrame(100, 0, false);
  const bool first = Bool<0>();
  const bool last = Bool<1>();
  OnReceivedPacket(CreatePacket(first, last, 103, 1, false, {0}));
  const bool expected_decodability_flag = first;
  ExpectLossNotification(100, 103, expected_decodability_flag);
}

// No key frame or loss notifications issued due to an innocuous wrap-around
// of the sequence number.
TEST_P(LossNotificationControllerTest, SeqNumWrapAround) {
  uint16_t seq_num = std::numeric_limits<uint16_t>::max();
  OnReceivedPacket(CreatePacket(true, true, seq_num, 0, true));
  OnAssembledFrame(seq_num, 0, false);
  const bool first = Bool<0>();
  const bool last = Bool<1>();
  OnReceivedPacket(CreatePacket(first, last, ++seq_num, 1, false, {0}));
}

TEST_F(LossNotificationControllerTest,
       KeyFrameAfterPacketLossProducesNoLossNotifications) {
  OnReceivedPacket(CreatePacket(true, true, 100, 1, true));
  OnAssembledFrame(100, 1, false);
  OnReceivedPacket(CreatePacket(true, true, 108, 8, true));
}

TEST_P(LossNotificationControllerTest, LostReferenceProducesLossNotification) {
  OnReceivedPacket(CreatePacket(true, true, 100, 0, true));
  OnAssembledFrame(100, 0, false);
  uint16_t last_decodable_non_discardable_seq_num = 100;

  // RTP gap produces loss notification - not the focus of this test.
  const bool first = Bool<0>();
  const bool last = Bool<1>();
  const bool discardable = Bool<2>();
  const bool decodable = first;  // Depends on assemblability.
  OnReceivedPacket(CreatePacket(first, last, 107, 3, false, {0}));
  ExpectLossNotification(100, 107, decodable);
  OnAssembledFrame(107, 3, discardable);
  if (!discardable) {
    last_decodable_non_discardable_seq_num = 107;
  }

  // Test focus - a loss notification is produced because of the missing
  // dependency (frame ID 2), despite the RTP sequence number being the
  // next expected one.
  OnReceivedPacket(CreatePacket(true, true, 108, 4, false, {2, 0}));
  ExpectLossNotification(last_decodable_non_discardable_seq_num, 108, false);
}

// The difference between this test and the previous one, is that in this test,
// although the reference frame was received, it was not decodable.
TEST_P(LossNotificationControllerTest,
       UndecodableReferenceProducesLossNotification) {
  OnReceivedPacket(CreatePacket(true, true, 100, 0, true));
  OnAssembledFrame(100, 0, false);
  uint16_t last_decodable_non_discardable_seq_num = 100;

  // RTP gap produces loss notification - not the focus of this test.
  // Also, not decodable; this is important for later in the test.
  OnReceivedPacket(CreatePacket(true, true, 107, 3, false, {2}));
  ExpectLossNotification(100, 107, false);
  const bool discardable = Bool<0>();
  OnAssembledFrame(107, 3, discardable);

  // Test focus - a loss notification is produced because of the undecodable
  // dependency (frame ID 3, which depended on the missing frame ID 2).
  OnReceivedPacket(CreatePacket(true, true, 108, 4, false, {3, 0}));
  ExpectLossNotification(last_decodable_non_discardable_seq_num, 108, false);
}

TEST_P(LossNotificationControllerTest, RobustnessAgainstHighInitialRefFrameId) {
  constexpr uint16_t max_uint16_t = std::numeric_limits<uint16_t>::max();
  OnReceivedPacket(CreatePacket(true, true, 100, 0, true));
  OnAssembledFrame(100, 0, false);
  OnReceivedPacket(CreatePacket(true, true, 101, 1, false, {max_uint16_t}));
  ExpectLossNotification(100, 101, false);
  OnAssembledFrame(101, max_uint16_t, Bool<0>());
}

TEST_P(LossNotificationControllerTest, RepeatedPacketsAreIgnored) {
  PacketStreamCreator packet_stream;

  const auto key_frame_packet = packet_stream.NextPacket();
  OnReceivedPacket(key_frame_packet);
  OnAssembledFrame(key_frame_packet.seq_num, key_frame_packet.frame_id, false);

  const bool gap = Bool<0>();

  if (gap) {
    // Lose one packet.
    packet_stream.NextPacket();
  }

  auto repeated_packet = packet_stream.NextPacket();
  OnReceivedPacket(repeated_packet);
  if (gap) {
    // Loss notification issued because of the gap. This is not the focus of
    // the test.
    ExpectLossNotification(key_frame_packet.seq_num, repeated_packet.seq_num,
                           false);
  }
  OnReceivedPacket(repeated_packet);
}

TEST_F(LossNotificationControllerTest,
       RecognizesDependencyAcrossIntraFrameThatIsNotAKeyframe) {
  int last_seq_num = 1;
  auto receive = [&](bool is_key_frame, int64_t frame_id,
                     std::vector<int64_t> ref_frame_ids) {
    ++last_seq_num;
    OnReceivedPacket(CreatePacket(
        /*first_in_frame=*/true, /*last_in_frame=*/true, last_seq_num, frame_id,
        is_key_frame, std::move(ref_frame_ids)));
    OnAssembledFrame(last_seq_num, frame_id, /*discardable=*/false);
  };
  //  11 -- 13
  //   |     |
  //  10    12
  receive(/*is_key_frame=*/true, /*frame_id=*/10, /*ref_frame_ids=*/{});
  receive(/*is_key_frame=*/false, /*frame_id=*/11, /*ref_frame_ids=*/{10});
  receive(/*is_key_frame=*/false, /*frame_id=*/12, /*ref_frame_ids=*/{});
  receive(/*is_key_frame=*/false, /*frame_id=*/13, /*ref_frame_ids=*/{11, 12});
  EXPECT_FALSE(LastLossNotification());
}

class LossNotificationControllerTestDecodabilityFlag
    : public LossNotificationControllerBaseTest {
 protected:
  LossNotificationControllerTestDecodabilityFlag()
      : key_frame_seq_num_(100),
        key_frame_frame_id_(0),
        never_received_frame_id_(key_frame_frame_id_ + 1),
        seq_num_(0),
        frame_id_(0) {}

  void ReceiveKeyFrame() {
    RTC_DCHECK_NE(key_frame_frame_id_, never_received_frame_id_);
    OnReceivedPacket(CreatePacket(true, true, key_frame_seq_num_,
                                  key_frame_frame_id_, true));
    OnAssembledFrame(key_frame_seq_num_, key_frame_frame_id_, false);
    seq_num_ = key_frame_seq_num_;
    frame_id_ = key_frame_frame_id_;
  }

  void ReceivePacket(bool first_packet_in_frame,
                     bool last_packet_in_frame,
                     const std::vector<int64_t>& ref_frame_ids) {
    if (first_packet_in_frame) {
      frame_id_ += 1;
    }
    RTC_DCHECK_NE(frame_id_, never_received_frame_id_);
    constexpr bool is_key_frame = false;
    OnReceivedPacket(CreatePacket(first_packet_in_frame, last_packet_in_frame,
                                  ++seq_num_, frame_id_, is_key_frame,
                                  ref_frame_ids));
  }

  void CreateGap() {
    seq_num_ += 50;
    frame_id_ += 10;
  }

  const uint16_t key_frame_seq_num_;
  const uint16_t key_frame_frame_id_;

  // The tests intentionally never receive this, and can therefore always
  // use this as an unsatisfied dependency.
  const int64_t never_received_frame_id_ = 123;

  uint16_t seq_num_;
  int64_t frame_id_;
};

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       SinglePacketFrameWithDecodableDependencies) {
  ReceiveKeyFrame();
  CreateGap();

  const std::vector<int64_t> ref_frame_ids = {key_frame_frame_id_};
  ReceivePacket(true, true, ref_frame_ids);

  const bool expected_decodability_flag = true;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       SinglePacketFrameWithUndecodableDependencies) {
  ReceiveKeyFrame();
  CreateGap();

  const std::vector<int64_t> ref_frame_ids = {never_received_frame_id_};
  ReceivePacket(true, true, ref_frame_ids);

  const bool expected_decodability_flag = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       FirstPacketOfMultiPacketFrameWithDecodableDependencies) {
  ReceiveKeyFrame();
  CreateGap();

  const std::vector<int64_t> ref_frame_ids = {key_frame_frame_id_};
  ReceivePacket(true, false, ref_frame_ids);

  const bool expected_decodability_flag = true;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       FirstPacketOfMultiPacketFrameWithUndecodableDependencies) {
  ReceiveKeyFrame();
  CreateGap();

  const std::vector<int64_t> ref_frame_ids = {never_received_frame_id_};
  ReceivePacket(true, false, ref_frame_ids);

  const bool expected_decodability_flag = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       MiddlePacketOfMultiPacketFrameWithDecodableDependenciesIfFirstMissed) {
  ReceiveKeyFrame();
  CreateGap();

  const std::vector<int64_t> ref_frame_ids = {key_frame_frame_id_};
  ReceivePacket(false, false, ref_frame_ids);

  const bool expected_decodability_flag = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       MiddlePacketOfMultiPacketFrameWithUndecodableDependenciesIfFirstMissed) {
  ReceiveKeyFrame();
  CreateGap();

  const std::vector<int64_t> ref_frame_ids = {never_received_frame_id_};
  ReceivePacket(false, false, ref_frame_ids);

  const bool expected_decodability_flag = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       MiddlePacketOfMultiPacketFrameWithDecodableDependenciesIfFirstReceived) {
  ReceiveKeyFrame();
  CreateGap();

  // First packet in multi-packet frame. A loss notification is produced
  // because of the gap in RTP sequence numbers.
  const std::vector<int64_t> ref_frame_ids = {key_frame_frame_id_};
  ReceivePacket(true, false, ref_frame_ids);
  const bool expected_decodability_flag_first = true;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag_first);

  // Middle packet in multi-packet frame. No additional gap and the frame is
  // still potentially decodable, so no additional loss indication.
  ReceivePacket(false, false, ref_frame_ids);
  EXPECT_FALSE(LastKeyFrameRequest());
  EXPECT_FALSE(LastLossNotification());
}

TEST_F(
    LossNotificationControllerTestDecodabilityFlag,
    MiddlePacketOfMultiPacketFrameWithUndecodableDependenciesIfFirstReceived) {
  ReceiveKeyFrame();
  CreateGap();

  // First packet in multi-packet frame. A loss notification is produced
  // because of the gap in RTP sequence numbers. The frame is also recognized
  // as having non-decodable dependencies.
  const std::vector<int64_t> ref_frame_ids = {never_received_frame_id_};
  ReceivePacket(true, false, ref_frame_ids);
  const bool expected_decodability_flag_first = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag_first);

  // Middle packet in multi-packet frame. No additional gap, but the frame is
  // known to be non-decodable, so we keep issuing loss indications.
  ReceivePacket(false, false, ref_frame_ids);
  const bool expected_decodability_flag_middle = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag_middle);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       LastPacketOfMultiPacketFrameWithDecodableDependenciesIfAllPrevMissed) {
  ReceiveKeyFrame();
  CreateGap();

  const std::vector<int64_t> ref_frame_ids = {key_frame_frame_id_};
  ReceivePacket(false, true, ref_frame_ids);

  const bool expected_decodability_flag = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       LastPacketOfMultiPacketFrameWithUndecodableDependenciesIfAllPrevMissed) {
  ReceiveKeyFrame();
  CreateGap();

  const std::vector<int64_t> ref_frame_ids = {never_received_frame_id_};
  ReceivePacket(false, true, ref_frame_ids);

  const bool expected_decodability_flag = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag);
}

TEST_F(LossNotificationControllerTestDecodabilityFlag,
       LastPacketOfMultiPacketFrameWithDecodableDependenciesIfAllPrevReceived) {
  ReceiveKeyFrame();
  CreateGap();

  // First packet in multi-packet frame. A loss notification is produced
  // because of the gap in RTP sequence numbers.
  const std::vector<int64_t> ref_frame_ids = {key_frame_frame_id_};
  ReceivePacket(true, false, ref_frame_ids);
  const bool expected_decodability_flag_first = true;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag_first);

  // Last packet in multi-packet frame. No additional gap and the frame is
  // still potentially decodable, so no additional loss indication.
  ReceivePacket(false, true, ref_frame_ids);
  EXPECT_FALSE(LastKeyFrameRequest());
  EXPECT_FALSE(LastLossNotification());
}

TEST_F(
    LossNotificationControllerTestDecodabilityFlag,
    LastPacketOfMultiPacketFrameWithUndecodableDependenciesIfAllPrevReceived) {
  ReceiveKeyFrame();
  CreateGap();

  // First packet in multi-packet frame. A loss notification is produced
  // because of the gap in RTP sequence numbers. The frame is also recognized
  // as having non-decodable dependencies.
  const std::vector<int64_t> ref_frame_ids = {never_received_frame_id_};
  ReceivePacket(true, false, ref_frame_ids);
  const bool expected_decodability_flag_first = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag_first);

  // Last packet in multi-packet frame. No additional gap, but the frame is
  // known to be non-decodable, so we keep issuing loss indications.
  ReceivePacket(false, true, ref_frame_ids);
  const bool expected_decodability_flag_last = false;
  ExpectLossNotification(key_frame_seq_num_, seq_num_,
                         expected_decodability_flag_last);
}

}  //  namespace webrtc
