// 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 "content/browser/media/capture/fake_video_capture_stack.h"

#include <stdint.h>

#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/media/capture/frame_test_util.h"
#include "content/test/gpu_browsertest_helpers.h"
#include "gpu/ipc/client/client_shared_image_interface.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "media/base/video_util.h"
#include "media/capture/video/video_frame_receiver.h"
#include "media/capture/video_capture_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "ui/gfx/geometry/rect.h"

namespace content {

namespace {

scoped_refptr<gpu::SharedImageInterface> GetSharedImageInterface() {
  auto gpu_channel = GpuBrowsertestEstablishGpuChannelSyncRunLoop();
  return gpu_channel->CreateClientSharedImageInterface();
}

}  // namespace

FakeVideoCaptureStack::FakeVideoCaptureStack() = default;

FakeVideoCaptureStack::~FakeVideoCaptureStack() = default;

void FakeVideoCaptureStack::Reset() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  frames_.clear();
  last_frame_timestamp_ = base::TimeDelta::Min();
}

// A minimal implementation of VideoFrameReceiver that wraps buffers into
// VideoFrame instances and forwards all relevant callbacks and data to the
// parent FakeVideoCaptureStack. The implemented `media::VideoFrameReceiver`
// methods will be forwarded to a separate thread for processing in order to,
// unblock the thread on which they arrive, and the results of this processing
// will then be posted to the `FakeVideoCaptureStack` owning this instance,
// on the task runner that constructed this instance.
class FakeVideoCaptureStackReceiver final : public media::VideoFrameReceiver {
 public:
  // Creates VideoFrameReceiver that notifies `capture_stack` when new frames
  // arrive (among other things). The `capture_stack` will be called into from
  // the current sequence.
  explicit FakeVideoCaptureStackReceiver(
      base::WeakPtr<FakeVideoCaptureStack> capture_stack)
      : capture_stack_(std::move(capture_stack)),
        capture_stack_task_runner_(
            base::SequencedTaskRunner::GetCurrentDefault()) {
    DETACH_FROM_SEQUENCE(receiver_sequence_checker_);

    // This will DCHECK if we're not currently on UI thread (due to use of
    // `content::BrowserMainLoop::GetInstance()` in the impl.), but that is
    // fine since the tests currently call us on UI thread:
    sii_ = GetSharedImageInterface();
    receiver_thread_ =
        std::make_unique<base::Thread>("fake-capture-stack-receiver");
    receiver_thread_->StartAndWaitForTesting();
  }

  void WaitForReceiver() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_);

    base::RunLoop runLoop(base::RunLoop::Type::kNestableTasksAllowed);

    receiver_thread_->task_runner()->PostTask(FROM_HERE, runLoop.QuitClosure());

    runLoop.Run();
  }

  base::WeakPtr<FakeVideoCaptureStackReceiver> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

  FakeVideoCaptureStackReceiver(const FakeVideoCaptureStackReceiver&) = delete;
  FakeVideoCaptureStackReceiver& operator=(
      const FakeVideoCaptureStackReceiver&) = delete;

  ~FakeVideoCaptureStackReceiver() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_);

    receiver_thread_ = nullptr;
  }

 private:
  using Buffer = media::VideoCaptureDevice::Client::Buffer;

  void OnCaptureConfigurationChanged() override {}

  void OnNewBuffer(int buffer_id,
                   media::mojom::VideoBufferHandlePtr buffer_handle) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_);

    // Unretained is safe since we own the thread to which we're posting.
    receiver_thread_->task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &FakeVideoCaptureStackReceiver::OnNewBufferOnReceiverThread,
            base::Unretained(this), buffer_id, std::move(buffer_handle)));
  }

  void OnNewBufferOnReceiverThread(
      int buffer_id,
      media::mojom::VideoBufferHandlePtr buffer_handle) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(receiver_sequence_checker_);

    buffers_[buffer_id] = std::move(buffer_handle);
  }

  scoped_refptr<media::VideoFrame> GetVideoFrameFromSharedMemory(
      media::ReadyFrameInBuffer frame,
      base::ReadOnlySharedMemoryMapping mapping) {
    CHECK(mapping.IsValid());

    const auto& frame_format = media::VideoCaptureFormat(
        frame.frame_info->coded_size, 0.0f, frame.frame_info->pixel_format);
    CHECK_LE(media::VideoFrame::AllocationSize(frame_format.pixel_format,
                                               frame_format.frame_size),
             mapping.size());

    auto video_frame = media::VideoFrame::WrapExternalData(
        frame.frame_info->pixel_format, frame.frame_info->coded_size,
        frame.frame_info->visible_rect, frame.frame_info->visible_rect.size(),
        mapping, frame.frame_info->timestamp);
    CHECK(video_frame);

    video_frame->set_metadata(frame.frame_info->metadata);
    video_frame->set_color_space(frame.frame_info->color_space);

    // This destruction observer will unmap the shared memory when the
    // VideoFrame goes out-of-scope.
    video_frame->AddDestructionObserver(base::BindOnce(
        [](base::ReadOnlySharedMemoryMapping) {}, std::move(mapping)));
    // This destruction observer will notify the video capture device once all
    // downstream code is done using the VideoFrame.
    video_frame->AddDestructionObserver(base::BindOnce(
        [](std::unique_ptr<Buffer::ScopedAccessPermission> access) {},
        std::move(frame.buffer_read_permission)));

    return video_frame;
  }

  scoped_refptr<media::VideoFrame> GetVideoFrameFromGMBHandle(
      media::ReadyFrameInBuffer frame,
      const gfx::GpuMemoryBufferHandle& gmb_handle) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(receiver_sequence_checker_);

    CHECK(!gmb_handle.is_null());
    CHECK_EQ(frame.frame_info->pixel_format,
             media::VideoPixelFormat::PIXEL_FORMAT_NV12);

    const auto si_usage = gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
    CHECK(sii_);
    auto shared_image = sii_->CreateSharedImage(
        {viz::MultiPlaneFormat::kNV12, frame.frame_info->coded_size,
         frame.frame_info->color_space, gpu::SharedImageUsageSet(si_usage),
         "FakeVideoCaptureStack"},
        gpu::kNullSurfaceHandle, gfx::BufferUsage::SCANOUT_VEA_CPU_READ,
        gmb_handle.Clone());
    auto creation_sync_token = shared_image->creation_sync_token();
    sii_->VerifySyncToken(creation_sync_token);
    auto video_frame = media::VideoFrame::WrapMappableSharedImage(
        std::move(shared_image), creation_sync_token, base::NullCallback(),
        frame.frame_info->visible_rect, frame.frame_info->coded_size,
        frame.frame_info->timestamp);
    CHECK(video_frame);

    video_frame->set_metadata(frame.frame_info->metadata);
    video_frame->set_color_space(frame.frame_info->color_space);

    auto mapped_frame = media::ConvertToMemoryMappedFrame(video_frame);
    CHECK(mapped_frame);

    // This destruction observer will notify the video capture device once all
    // downstream code is done using the VideoFrame.
    mapped_frame->AddDestructionObserver(base::BindOnce(
        [](std::unique_ptr<Buffer::ScopedAccessPermission> access) {},
        std::move(frame.buffer_read_permission)));

    return mapped_frame;
  }

  void OnFrameReadyInBuffer(media::ReadyFrameInBuffer frame) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_);

    // Unretained is safe since we own the thread to which we're posting.
    // This implementation does not forward scaled frames.
    receiver_thread_->task_runner()->PostTask(
        FROM_HERE, base::BindOnce(&FakeVideoCaptureStackReceiver::
                                      OnFrameReadyInBufferOnReceiverThread,
                                  base::Unretained(this), std::move(frame)));
  }

  void OnFrameReadyInBufferOnReceiverThread(media::ReadyFrameInBuffer frame) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(receiver_sequence_checker_);

    const auto it = buffers_.find(frame.buffer_id);
    CHECK(it != buffers_.end());

    CHECK(it->second->is_read_only_shmem_region() ||
          it->second->is_gpu_memory_buffer_handle());

    scoped_refptr<media::VideoFrame> video_frame = nullptr;
    if (it->second->is_read_only_shmem_region()) {
      video_frame = GetVideoFrameFromSharedMemory(
          std::move(frame), it->second->get_read_only_shmem_region().Map());
    } else {
      video_frame = GetVideoFrameFromGMBHandle(
          std::move(frame), it->second->get_gpu_memory_buffer_handle());
    }

    // This implementation does not forward scaled frames.
    capture_stack_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&FakeVideoCaptureStack::OnReceivedFrame,
                                  capture_stack_, std::move(video_frame)));
  }

  void OnBufferRetired(int buffer_id) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_);

    // Unretained is safe since we own the thread to which we're posting.
    receiver_thread_->task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &FakeVideoCaptureStackReceiver::OnBufferRetiredOnReceiverThread,
            base::Unretained(this), buffer_id));
  }

  void OnBufferRetiredOnReceiverThread(int buffer_id) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(receiver_sequence_checker_);

    const auto it = buffers_.find(buffer_id);
    CHECK(it != buffers_.end());
    buffers_.erase(it);
  }

  void OnError(media::VideoCaptureError) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_);

    capture_stack_->SetErrorOccurred();
  }

  void OnFrameDropped(media::VideoCaptureFrameDropReason) override {}

  void OnNewCaptureVersion(media::CaptureVersion capture_version) override {}

  void OnFrameWithEmptyRegionCapture() override {}

  void OnLog(const std::string& message) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_);

    capture_stack_->OnLog(message);
  }

  void OnStarted() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(capture_stack_sequence_checker_);

    capture_stack_->SetStarted();
  }

  void OnStartedUsingGpuDecode() override { NOTREACHED(); }

  void OnStopped() override {}

  // WeakPtr is thread-safe to copy/move around, but the deref of
  // `capture_stack_` needs to happen on `capture_stack_sequence_checker_`.
  // Since there's one instance where we copy the pointer around that doesn't
  // happen on the right sequence, we cannot use `GUARDED_BY_CONTEXT()` here.
  const base::WeakPtr<FakeVideoCaptureStack> capture_stack_;

  base::flat_map<int, media::mojom::VideoBufferHandlePtr> buffers_
      GUARDED_BY_CONTEXT(receiver_sequence_checker_);

  scoped_refptr<gpu::SharedImageInterface> sii_;

  // Task runner on which we should be calling into capture stack:
  scoped_refptr<base::SequencedTaskRunner> capture_stack_task_runner_;

  // Thread that will be processing the `media::VideoFrameReceiver` methods.
  // This is needed for Windows if using GpuMemoryBuffers, since mapping GMBs
  // incurs a mojo call and is supposed to be a synchronous operation, which
  // means that the mapping thread will be blocked. If it so happens that the
  // mapping thread is also responsible for processing mojo responses (as is
  // the case if we don't introduce `receiver_thread_`), we will cause a
  // deadlock.
  // As per `base::Thread` documentation, can only be used from a sequence it
  // was started on (in our case, same as `capture_stack_task_runner_`).
  std::unique_ptr<base::Thread> receiver_thread_
      GUARDED_BY_CONTEXT(capture_stack_sequence_checker_);

  // Sequence checker for parts of the class that can be accessed only on
  // the `receiver_thread_`:
  SEQUENCE_CHECKER(receiver_sequence_checker_);

  // Sequence checker for the `receiver_thread_` member variable, since
  // `base::Thread` documents that it can only be accessed from a sequence that
  // started the thread. The name reflects the fact that we're created by
  // `FakeVideoCaptureStack` and that's where we start the thread.
  SEQUENCE_CHECKER(capture_stack_sequence_checker_);

  base::WeakPtrFactory<FakeVideoCaptureStackReceiver> weak_ptr_factory_{this};
};

std::unique_ptr<media::VideoFrameReceiver>
FakeVideoCaptureStack::CreateFrameReceiver() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!receiver_);

  auto result = std::make_unique<FakeVideoCaptureStackReceiver>(
      weak_ptr_factory_.GetWeakPtr());

  receiver_ = result->GetWeakPtr();

  return result;
}

SkBitmap FakeVideoCaptureStack::NextCapturedFrame() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  CHECK(!frames_.empty());
  media::VideoFrame& frame = *(frames_.front());
  SkBitmap bitmap = FrameTestUtil::ConvertToBitmap(frame);
  frames_.pop_front();
  return bitmap;
}

void FakeVideoCaptureStack::ClearCapturedFramesQueue() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  frames_.clear();
}

void FakeVideoCaptureStack::ExpectHasLogMessages() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  EXPECT_FALSE(log_messages_.empty());
  while (!log_messages_.empty()) {
    VLOG(1) << "Next log message: " << log_messages_.front();
    log_messages_.pop_front();
  }
}

void FakeVideoCaptureStack::ExpectNoLogMessages() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  while (!log_messages_.empty()) {
    ADD_FAILURE() << "Unexpected log message: " << log_messages_.front();
    log_messages_.pop_front();
  }
}

void FakeVideoCaptureStack::OnReceivedFrame(
    scoped_refptr<media::VideoFrame> frame) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Frame timestamps should be monotionically increasing.
  EXPECT_LT(last_frame_timestamp_, frame->timestamp());
  last_frame_timestamp_ = frame->timestamp();

  EXPECT_TRUE(frame->ColorSpace().IsValid());

  if (on_frame_received_) {
    on_frame_received_.Run(frame.get());
  }

  frames_.emplace_back(std::move(frame));
}

// Returns true if the device called VideoFrameReceiver::OnStarted().
bool FakeVideoCaptureStack::Started() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  WaitForReceiver();

  return started_;
}

// Returns true if the device called VideoFrameReceiver::OnError().
bool FakeVideoCaptureStack::ErrorOccurred() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  WaitForReceiver();

  return error_occurred_;
}

// Accessors to capture frame queue.
bool FakeVideoCaptureStack::HasCapturedFrames() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  WaitForReceiver();

  return !frames_.empty();
}

void FakeVideoCaptureStack::WaitForReceiver() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!receiver_) {
    return;
  }

  receiver_->WaitForReceiver();
}

}  // namespace content
