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

#include "services/video_capture/shared_memory_virtual_device_mojo_adapter.h"

#include <algorithm>
#include <utility>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/checked_math.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_layout.h"
#include "media/base/video_types.h"
#include "media/capture/video/scoped_buffer_pool_reservation.h"
#include "media/capture/video/video_capture_buffer_pool_impl.h"
#include "media/capture/video/video_capture_buffer_pool_util.h"
#include "media/capture/video/video_capture_buffer_tracker_factory_impl.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/video_capture/public/mojom/constants.mojom.h"

namespace video_capture {
namespace {

void OnNewBufferAcknowleged(
    video_capture::mojom::SharedMemoryVirtualDevice::RequestFrameBufferCallback
        callback,
    int32_t buffer_id) {
  std::move(callback).Run(buffer_id);
}

bool ValidateVideoFrameInfo(const media::mojom::VideoFrameInfoPtr& frame_info,
                            size_t buffer_size) {
  if (!IsSupportedVideoPixelFormat(frame_info->pixel_format)) {
    return false;
  }

  if (!media::VideoFrame::IsValidConfig(
          frame_info->pixel_format, media::VideoFrame::STORAGE_SHMEM,
          frame_info->coded_size, frame_info->visible_rect,
          frame_info->natural_size)) {
    return false;
  }

  const size_t num_planes =
      media::VideoFrame::NumPlanes(frame_info->pixel_format);

  std::vector<size_t> strides;
  if (frame_info->strides) {
    if (frame_info->strides->stride_by_plane.size() < num_planes) {
      return false;
    }
    strides.assign(frame_info->strides->stride_by_plane.begin(),
                   frame_info->strides->stride_by_plane.begin() + num_planes);
  } else {
    strides.resize(num_planes);
    for (size_t i = 0; i < num_planes; ++i) {
      strides[i] = media::VideoFrame::RowBytes(i, frame_info->pixel_format,
                                               frame_info->coded_size.width());
    }
  }

  std::vector<media::ColorPlaneLayout> planes;
  base::CheckedNumeric<size_t> offset = 0;
  for (size_t i = 0; i < num_planes; ++i) {
    size_t rows = media::VideoFrame::Rows(i, frame_info->pixel_format,
                                          frame_info->coded_size.height());
    base::CheckedNumeric<size_t> plane_size = base::CheckMul(strides[i], rows);
    if (!plane_size.IsValid()) {
      return false;
    }
    planes.emplace_back(strides[i], offset.ValueOrDie(),
                        plane_size.ValueOrDie());
    offset += plane_size;
    if (!offset.IsValid()) {
      return false;
    }
  }
  auto layout = media::VideoFrameLayout::CreateWithPlanes(
      frame_info->pixel_format, frame_info->coded_size, std::move(planes));

  return layout && layout->FitsInContiguousBufferOfSize(buffer_size);
}

}  // anonymous namespace

bool IsSupportedVideoPixelFormat(media::VideoPixelFormat format) {
  if (format == media::VideoPixelFormat::PIXEL_FORMAT_UNKNOWN) {
    return false;
  }
  return media::IsValidVideoPixelFormat(format);
}

SharedMemoryVirtualDeviceMojoAdapter::SharedMemoryVirtualDeviceMojoAdapter(
    mojo::Remote<mojom::Producer> producer)
    : producer_(std::move(producer)),
      buffer_pool_(base::MakeRefCounted<media::VideoCaptureBufferPoolImpl>(
          media::VideoCaptureBufferType::kSharedMemory,
          max_buffer_pool_buffer_count())) {}

SharedMemoryVirtualDeviceMojoAdapter::~SharedMemoryVirtualDeviceMojoAdapter() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

int SharedMemoryVirtualDeviceMojoAdapter::max_buffer_pool_buffer_count() {
  return media::kVideoCaptureDefaultMaxBufferPoolSize;
}

void SharedMemoryVirtualDeviceMojoAdapter::RequestFrameBuffer(
    const gfx::Size& dimension,
    media::VideoPixelFormat pixel_format,
    media::mojom::PlaneStridesPtr strides,
    RequestFrameBufferCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  int buffer_id_to_drop = media::VideoCaptureBufferPool::kInvalidId;
  int buffer_id = media::VideoCaptureBufferPool::kInvalidId;
  const auto reserve_result = buffer_pool_->ReserveForProducer(
      dimension, pixel_format, strides, 0 /* frame_feedback_id */, &buffer_id,
      &buffer_id_to_drop);

  // Remove dropped buffer if there is one.
  if (buffer_id_to_drop != media::VideoCaptureBufferPool::kInvalidId) {
    auto entry_iter = std::ranges::find(known_buffer_ids_, buffer_id_to_drop);
    if (entry_iter != known_buffer_ids_.end()) {
      known_buffer_ids_.erase(entry_iter);
      if (producer_.is_bound())
        producer_->OnBufferRetired(buffer_id_to_drop);
      if (video_frame_handler_.is_bound()) {
        video_frame_handler_->OnBufferRetired(buffer_id_to_drop);
      } else if (video_frame_handler_in_process_) {
        video_frame_handler_in_process_->OnBufferRetired(buffer_id_to_drop);
      }
    }
  }

  if (reserve_result !=
      media::VideoCaptureDevice::Client::ReserveResult::kSucceeded) {
    std::move(callback).Run(mojom::kInvalidBufferId);
    return;
  }

  if (!std::ranges::contains(known_buffer_ids_, buffer_id)) {
    if (video_frame_handler_.is_bound()) {
      media::mojom::VideoBufferHandlePtr buffer_handle =
          media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
              buffer_pool_->DuplicateAsUnsafeRegion(buffer_id));
      video_frame_handler_->OnNewBuffer(buffer_id, std::move(buffer_handle));
    } else if (video_frame_handler_in_process_) {
      media::mojom::VideoBufferHandlePtr buffer_handle =
          media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
              buffer_pool_->DuplicateAsUnsafeRegion(buffer_id));
      video_frame_handler_in_process_->OnNewBuffer(buffer_id,
                                                   std::move(buffer_handle));
    }
    known_buffer_ids_.push_back(buffer_id);

    // Share buffer handle with producer.
    media::mojom::VideoBufferHandlePtr buffer_handle;
    buffer_handle = media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
        buffer_pool_->DuplicateAsUnsafeRegion(buffer_id));
    // Invoke the response back only after the producer have acked
    // that it has received the newly created buffer. This is need
    // because the |producer_| and the |callback| are bound to different
    // message pipes, so the order for calls to |producer_| and |callback|
    // is not guaranteed.
    if (producer_.is_bound())
      producer_->OnNewBuffer(buffer_id, std::move(buffer_handle),
                             base::BindOnce(&OnNewBufferAcknowleged,
                                            std::move(callback), buffer_id));
    return;
  }
  std::move(callback).Run(buffer_id);
}

void SharedMemoryVirtualDeviceMojoAdapter::OnFrameReadyInBuffer(
    int32_t buffer_id,
    ::media::mojom::VideoFrameInfoPtr frame_info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Unknown buffer ID.
  if (!std::ranges::contains(known_buffer_ids_, buffer_id)) {
    return;
  }

  auto handle = buffer_pool_->GetHandleForInProcessAccess(buffer_id);
  if (!ValidateVideoFrameInfo(frame_info, handle->mapped_size())) {
    return;
  }

  // Notify receiver if there is one.
  if (video_frame_handler_.is_bound()) {
    if (!scoped_access_permission_map_) {
      scoped_access_permission_map_ = ScopedAccessPermissionMap::
          CreateMapAndSendVideoFrameAccessHandlerReady(video_frame_handler_);
    }
    buffer_pool_->HoldForConsumers(buffer_id, 1 /* num_clients */);
    auto access_permission = std::make_unique<
        media::ScopedBufferPoolReservation<media::ConsumerReleaseTraits>>(
        buffer_pool_, buffer_id);
    scoped_access_permission_map_->InsertAccessPermission(
        buffer_id, std::move(access_permission));
    video_frame_handler_->OnFrameReadyInBuffer(mojom::ReadyFrameInBuffer::New(
        buffer_id, 0 /* frame_feedback_id */, std::move(frame_info)));
  } else if (video_frame_handler_in_process_) {
    buffer_pool_->HoldForConsumers(buffer_id, 1 /* num_clients */);
    video_frame_handler_in_process_->OnFrameReadyInBuffer(
        media::ReadyFrameInBuffer(
            buffer_id, 0 /* frame_feedback_id */,
            std::make_unique<media::ScopedBufferPoolReservation<
                media::ConsumerReleaseTraits>>(buffer_pool_, buffer_id),
            std::move(frame_info)));
  }
  buffer_pool_->RelinquishProducerReservation(buffer_id);
}

void SharedMemoryVirtualDeviceMojoAdapter::Start(
    const media::VideoCaptureParams& requested_settings,
    mojo::PendingRemote<mojom::VideoFrameHandler> handler) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  video_frame_handler_.Bind(std::move(handler));
  video_frame_handler_.set_disconnect_handler(base::BindOnce(
      &SharedMemoryVirtualDeviceMojoAdapter::OnReceiverConnectionErrorOrClose,
      base::Unretained(this)));
  video_frame_handler_->OnStarted();

  // Notify receiver of known buffers */
  for (auto buffer_id : known_buffer_ids_) {
    media::mojom::VideoBufferHandlePtr buffer_handle =
        media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
            buffer_pool_->DuplicateAsUnsafeRegion(buffer_id));
    video_frame_handler_->OnNewBuffer(buffer_id, std::move(buffer_handle));
  }
}

void SharedMemoryVirtualDeviceMojoAdapter::StartInProcess(
    const media::VideoCaptureParams& requested_settings,
    const base::WeakPtr<media::VideoFrameReceiver>& frame_handler) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  video_frame_handler_in_process_ = std::move(frame_handler);
  video_frame_handler_in_process_->OnStarted();

  // Notify receiver of known buffers */
  for (auto buffer_id : known_buffer_ids_) {
    media::mojom::VideoBufferHandlePtr buffer_handle =
        media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
            buffer_pool_->DuplicateAsUnsafeRegion(buffer_id));
    video_frame_handler_in_process_->OnNewBuffer(buffer_id,
                                                 std::move(buffer_handle));
  }
}

void SharedMemoryVirtualDeviceMojoAdapter::MaybeSuspend() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void SharedMemoryVirtualDeviceMojoAdapter::Resume() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void SharedMemoryVirtualDeviceMojoAdapter::GetPhotoState(
    GetPhotoStateCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  std::move(callback).Run(nullptr);
}

void SharedMemoryVirtualDeviceMojoAdapter::SetPhotoOptions(
    media::mojom::PhotoSettingsPtr settings,
    SetPhotoOptionsCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void SharedMemoryVirtualDeviceMojoAdapter::TakePhoto(
    TakePhotoCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void SharedMemoryVirtualDeviceMojoAdapter::ProcessFeedback(
    const media::VideoCaptureFeedback& feedback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void SharedMemoryVirtualDeviceMojoAdapter::RequestRefreshFrame() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void SharedMemoryVirtualDeviceMojoAdapter::InvalidateBuffers() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void SharedMemoryVirtualDeviceMojoAdapter::Stop() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (video_frame_handler_.is_bound()) {
    // Unsubscribe from connection error callbacks.
    video_frame_handler_.set_disconnect_handler(base::OnceClosure());
    // Send out OnBufferRetired events and OnStopped.
    for (auto buffer_id : known_buffer_ids_)
      video_frame_handler_->OnBufferRetired(buffer_id);
    video_frame_handler_->OnStopped();
    video_frame_handler_.reset();
  } else if (video_frame_handler_in_process_) {
    // Send out OnBufferRetired events and OnStopped.
    for (auto buffer_id : known_buffer_ids_)
      video_frame_handler_in_process_->OnBufferRetired(buffer_id);
    video_frame_handler_in_process_->OnStopped();
    video_frame_handler_in_process_ = nullptr;
  }
}

void SharedMemoryVirtualDeviceMojoAdapter::OnReceiverConnectionErrorOrClose() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  Stop();
}

}  // namespace video_capture
