// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/mirroring/service/remoting_sender.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/time/default_tick_clock.h"
#include "media/cast/common/encoded_frame.h"
#include "media/cast/common/frame_id.h"
#include "media/cast/common/sender_encoded_frame.h"
#include "media/cast/constants.h"
#include "media/cast/openscreen/remoting_proto_utils.h"
#include "media/cast/sender/frame_sender.h"
#include "media/cast/test/test_with_cast_environment.h"
#include "media/cast/test/utility/default_config.h"
#include "media/mojo/common/media_type_converters.h"
#include "media/mojo/common/mojo_data_pipe_read_write.h"
#include "media/mojo/mojom/remoting.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using Dependency = openscreen::cast::EncodedFrame::Dependency;

using ::testing::_;
using ::testing::Return;
using ::testing::StrictMock;

namespace mirroring {
namespace {

void AreEqualExceptKeyframeImpl(const media::cast::SenderEncodedFrame& frame,
                                const media::DecoderBuffer& buffer,
                                base::test::TaskEnvironment& environment) {
  if (buffer.is_key_frame()) {
    EXPECT_TRUE(frame.is_key_frame);
  }

  scoped_refptr<media::DecoderBuffer> received_buffer =
      media::cast::ByteArrayToDecoderBuffer(base::as_byte_span(frame.data));
  EXPECT_EQ(base::span(buffer), base::span(*received_buffer));

  // The reference time and encode completion time should be the same, and
  // is based on the clock time.
  EXPECT_EQ(frame.reference_time, frame.encode_completion_time);

  // The reference time should use the current environment time. Since we are
  // using a mock clock that is only manually advanced, it should be equal to
  // the environment's current time.
  EXPECT_EQ(frame.reference_time, environment.NowTicks());
}

ACTION_P(AreEqualNotFirstFrame, buffer, env) {
  AreEqualExceptKeyframeImpl(*arg0, *buffer, *env);
  return media::cast::CastStreamingFrameDropReason::kNotDropped;
}

ACTION_P(AreEqualFirstFrame, buffer, env) {
  EXPECT_TRUE(arg0->is_key_frame);
  AreEqualExceptKeyframeImpl(*arg0, *buffer, *env);
  return media::cast::CastStreamingFrameDropReason::kNotDropped;
}

// Data pipe capacity is 1KB.
constexpr int kDataPipeCapacity = 1024;

class FakeSender : public media::cast::FrameSender {
 public:
  ~FakeSender() override = default;

  MOCK_METHOD1(SetTargetPlayoutDelay, void(base::TimeDelta));
  MOCK_CONST_METHOD0(GetTargetPlayoutDelay, base::TimeDelta());
  MOCK_CONST_METHOD0(NeedsKeyFrame, bool());
  MOCK_METHOD1(EnqueueFrame,
               media::cast::CastStreamingFrameDropReason(
                   std::unique_ptr<media::cast::SenderEncodedFrame>));
  MOCK_METHOD1(ShouldDropNextFrame,
               media::cast::CastStreamingFrameDropReason(base::TimeDelta));
  MOCK_CONST_METHOD1(GetRecordedRtpTimestamp,
                     media::cast::RtpTimeTicks(media::cast::FrameId));
  MOCK_CONST_METHOD0(GetUnacknowledgedFrameCount, int());
  MOCK_METHOD2(GetSuggestedBitrate, int(base::TimeTicks, base::TimeDelta));
  MOCK_CONST_METHOD0(MaxFrameRate, double());
  MOCK_METHOD1(SetMaxFrameRate, void(double));
  MOCK_CONST_METHOD0(TargetPlayoutDelay, base::TimeDelta());
  MOCK_CONST_METHOD0(CurrentRoundTripTime, base::TimeDelta());
  MOCK_CONST_METHOD0(LastSendTime, base::TimeTicks());
  MOCK_CONST_METHOD0(LastAckedFrameId, media::cast::FrameId());
};

class MojoSenderWrapper {
 public:
  MojoSenderWrapper(
      mojo::ScopedDataPipeProducerHandle handle,
      mojo::PendingRemote<media::mojom::RemotingDataStreamSender> sender)
      : data_pipe_writer_(std::move(handle)),
        stream_sender_(std::move(sender)) {}

  void SendFrame(scoped_refptr<media::DecoderBuffer> buffer) {
    SendFrame(std::move(buffer), base::OnceCallback<void()>{});
  }

  void SendFrame(scoped_refptr<media::DecoderBuffer> buffer,
                 base::OnceCallback<void()> on_read_complete) {
    ASSERT_FALSE(is_frame_in_flight_);
    is_frame_in_flight_ = true;

    data_pipe_writer_.Write(
        *buffer, base::BindOnce(&MojoSenderWrapper::OnPipeWriteComplete,
                                weak_factory_.GetWeakPtr()));
    stream_sender_->SendFrame(
        media::mojom::DecoderBuffer::From(*buffer),
        base::BindOnce(&MojoSenderWrapper::OnFrameReadComplete,
                       weak_factory_.GetWeakPtr(),
                       std::move(on_read_complete)));
  }

  void CancelInFlightData() { stream_sender_->CancelInFlightData(); }

  bool is_frame_in_flight() const { return is_frame_in_flight_; }

 private:
  void OnFrameReadComplete(base::OnceCallback<void()> on_read_complete) {
    is_frame_in_flight_ = false;
    if (on_read_complete) {
      std::move(on_read_complete).Run();
    }
  }

  void OnPipeWriteComplete(bool success) { ASSERT_TRUE(success); }

  bool is_frame_in_flight_ = false;

  media::MojoDataPipeWriter data_pipe_writer_;
  mojo::Remote<media::mojom::RemotingDataStreamSender> stream_sender_;

  base::WeakPtrFactory<MojoSenderWrapper> weak_factory_{this};
};

}  // namespace

class RemotingSenderTest : public media::cast::TestWithCastEnvironment {
 public:
  RemotingSenderTest() {
    media::cast::FrameSenderConfig video_config =
        media::cast::GetDefaultVideoSenderConfig();
    std::unique_ptr<testing::StrictMock<FakeSender>> fake_sender =
        std::make_unique<testing::StrictMock<FakeSender>>();
    sender_ = fake_sender.get();

    mojo::PendingRemote<media::mojom::RemotingDataStreamSender> sender;
    const MojoCreateDataPipeOptions data_pipe_options{
        sizeof(MojoCreateDataPipeOptions), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1,
        kDataPipeCapacity};
    mojo::ScopedDataPipeProducerHandle producer_end;
    mojo::ScopedDataPipeConsumerHandle consumer_end;
    CHECK_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe(&data_pipe_options,
                                                  producer_end, consumer_end));

    remoting_sender_ = base::WrapUnique(new RemotingSender(
        cast_environment(), std::move(fake_sender), video_config,
        std::move(consumer_end), sender.InitWithNewPipeAndPassReceiver(),
        base::BindOnce(
            [](bool expecting_error_callback_run) {
              CHECK(expecting_error_callback_run);
            },
            expecting_error_callback_run_)));

    mojo_sender_wrapper_ = std::make_unique<MojoSenderWrapper>(
        std::move(producer_end), std::move(sender));

    std::vector<uint8_t> data = {1, 2, 3};
    first_buffer_ = media::DecoderBuffer::CopyFrom(data);
    first_buffer_->set_duration(base::Seconds(1));
    first_buffer_->set_timestamp(base::Seconds(2));
    first_buffer_->set_is_key_frame(false);

    data = {42, 43, 44};
    second_buffer_ = media::DecoderBuffer::CopyFrom(data);
    second_buffer_->set_duration(base::Seconds(32));
    second_buffer_->set_timestamp(base::Seconds(42));
    second_buffer_->set_is_key_frame(false);

    data = {7, 8, 9};
    third_buffer_ = media::DecoderBuffer::CopyFrom(data);
    third_buffer_->set_duration(base::Seconds(10));
    third_buffer_->set_timestamp(base::Seconds(11));
    third_buffer_->set_is_key_frame(true);
  }

 protected:
  // Allow pending tasks, such as Mojo method calls, to execute.
  void RunPendingTasks() { RunUntilIdle(); }

  void SendFrameCancelled(media::cast::FrameId id) {
    remoting_sender_->OnFrameCanceled(id);
  }

  raw_ptr<testing::StrictMock<FakeSender>, DanglingUntriaged> sender_;
  bool expecting_error_callback_run_ = false;

  std::unique_ptr<MojoSenderWrapper> mojo_sender_wrapper_;

  std::unique_ptr<RemotingSender> remoting_sender_;

  scoped_refptr<media::DecoderBuffer> first_buffer_;
  scoped_refptr<media::DecoderBuffer> second_buffer_;
  scoped_refptr<media::DecoderBuffer> third_buffer_;

  media::cast::FrameId first_frame_id_ = media::cast::FrameId::first();
};

TEST_F(RemotingSenderTest, SendsFramesViaMojoDataPipe) {
  EXPECT_CALL(*sender_, LastSendTime)
      .WillRepeatedly(Return(base::TimeTicks::Now() - base::Milliseconds(100)));

  EXPECT_CALL(*sender_, GetRecordedRtpTimestamp(media::cast::FrameId(0)))
      .WillRepeatedly(Return(media::cast::RtpTimeTicks(1111)));
  EXPECT_CALL(*sender_, GetRecordedRtpTimestamp(media::cast::FrameId(1)))
      .WillRepeatedly(Return(media::cast::RtpTimeTicks(2222)));

  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount).WillOnce(Return(0));
  EXPECT_CALL(*sender_, EnqueueFrame(_))
      .WillOnce(AreEqualFirstFrame(first_buffer_, &task_environment()));
  mojo_sender_wrapper_->SendFrame(first_buffer_);
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());
  RunPendingTasks();
  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());

  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount).WillOnce(Return(0));
  EXPECT_CALL(*sender_, EnqueueFrame(_))
      .WillOnce(AreEqualNotFirstFrame(second_buffer_, &task_environment()));
  mojo_sender_wrapper_->SendFrame(second_buffer_);
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());
  RunPendingTasks();
  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());

  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount).WillOnce(Return(0));
  EXPECT_CALL(*sender_, EnqueueFrame(_))
      .WillOnce(AreEqualNotFirstFrame(third_buffer_, &task_environment()));
  mojo_sender_wrapper_->SendFrame(third_buffer_);
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());
  RunPendingTasks();
  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());
}

TEST_F(RemotingSenderTest, CancelsUnsentFrame) {
  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount)
      .WillOnce(Return(media::cast::kMaxUnackedFrames));
  mojo_sender_wrapper_->SendFrame(first_buffer_);
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());
  RunPendingTasks();
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());

  mojo_sender_wrapper_->CancelInFlightData();
  RunPendingTasks();
  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());

  SendFrameCancelled(first_frame_id_);
  RunPendingTasks();
  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());
}

TEST_F(RemotingSenderTest, CancelsOrAcksFramesInFlight) {
  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount)
      .WillOnce(Return(media::cast::kMaxUnackedFrames));
  mojo_sender_wrapper_->SendFrame(first_buffer_);
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());
  RunPendingTasks();
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());

  EXPECT_CALL(*sender_, EnqueueFrame(_))
      .WillOnce(AreEqualFirstFrame(first_buffer_, &task_environment()));
  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount)
      .WillOnce(Return(media::cast::kMaxUnackedFrames - 1));
  SendFrameCancelled(first_frame_id_);
  RunPendingTasks();
  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());
}

TEST_F(RemotingSenderTest, FramesWaitWhenEnqueueFails) {
  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount).WillOnce(Return(0));
  EXPECT_CALL(*sender_, EnqueueFrame(_))
      .WillOnce(Return(
          media::cast::CastStreamingFrameDropReason::kTooManyFramesInFlight));
  mojo_sender_wrapper_->SendFrame(first_buffer_);
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());
  RunPendingTasks();
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());

  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount).WillOnce(Return(0));
  EXPECT_CALL(*sender_, EnqueueFrame(_))
      .WillOnce(AreEqualFirstFrame(first_buffer_, &task_environment()));
  SendFrameCancelled(first_frame_id_);
  RunPendingTasks();
  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());
}

TEST_F(RemotingSenderTest, FramesDroppedWhenEnqueueFailsRepeatedly) {
  int enqueue_attempts = 0;

  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount).WillOnce(Return(0));
  EXPECT_CALL(*sender_, EnqueueFrame(_))
      .WillOnce(Return(
          media::cast::CastStreamingFrameDropReason::kTooManyFramesInFlight));
  mojo_sender_wrapper_->SendFrame(first_buffer_);
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());
  RunPendingTasks();
  EXPECT_TRUE(mojo_sender_wrapper_->is_frame_in_flight());
  enqueue_attempts++;

  constexpr int kMaxEnqueueFrameFailures = 3;
  for (; enqueue_attempts <= kMaxEnqueueFrameFailures; enqueue_attempts++) {
    EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount).WillOnce(Return(0));
    EXPECT_CALL(*sender_, EnqueueFrame(_))
        .WillOnce(Return(
            media::cast::CastStreamingFrameDropReason::kTooManyFramesInFlight));
    SendFrameCancelled(first_frame_id_);
    RunPendingTasks();
  }

  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());
  RunPendingTasks();
}

TEST_F(RemotingSenderTest, FramesDroppedWhenEnqueueFailsDueToFormatting) {
  EXPECT_CALL(*sender_, GetUnacknowledgedFrameCount).WillOnce(Return(0));
  EXPECT_CALL(*sender_, EnqueueFrame(_))
      .WillOnce(
          Return(media::cast::CastStreamingFrameDropReason::kPayloadTooLarge));
  mojo_sender_wrapper_->SendFrame(first_buffer_);
  RunPendingTasks();
  EXPECT_FALSE(mojo_sender_wrapper_->is_frame_in_flight());
}

}  // namespace mirroring
