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

#include "content/browser/renderer_host/media/service_video_capture_device_launcher.h"

#include "content/browser/renderer_host/media/service_launched_video_capture_device.h"
#include "content/public/browser/browser_thread.h"
#include "media/capture/video/video_frame_receiver_on_task_runner.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "services/video_capture/public/cpp/receiver_media_to_mojo_adapter.h"

namespace content {

namespace {

void ConcludeLaunchDeviceWithSuccess(
    bool abort_requested,
    const media::VideoCaptureParams& params,
    video_capture::mojom::DevicePtr device,
    base::WeakPtr<media::VideoFrameReceiver> receiver,
    base::OnceClosure connection_lost_cb,
    VideoCaptureDeviceLauncher::Callbacks* callbacks,
    base::OnceClosure done_cb) {
  if (abort_requested) {
    device.reset();
    callbacks->OnDeviceLaunchAborted();
    base::ResetAndReturn(&done_cb).Run();
    return;
  }

  auto receiver_adapter =
      base::MakeUnique<video_capture::ReceiverMediaToMojoAdapter>(
          base::MakeUnique<media::VideoFrameReceiverOnTaskRunner>(
              std::move(receiver),
              BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)));
  video_capture::mojom::ReceiverPtr receiver_proxy;
  mojo::MakeStrongBinding<video_capture::mojom::Receiver>(
      std::move(receiver_adapter), mojo::MakeRequest(&receiver_proxy));
  device->Start(params, std::move(receiver_proxy));
  callbacks->OnDeviceLaunched(
      base::MakeUnique<ServiceLaunchedVideoCaptureDevice>(
          std::move(device), std::move(connection_lost_cb)));
  base::ResetAndReturn(&done_cb).Run();
}

void ConcludeLaunchDeviceWithFailure(
    bool abort_requested,
    VideoCaptureDeviceLauncher::Callbacks* callbacks,
    base::OnceClosure done_cb) {
  if (abort_requested)
    callbacks->OnDeviceLaunchAborted();
  else
    callbacks->OnDeviceLaunchFailed();
  base::ResetAndReturn(&done_cb).Run();
}

}  // anonymous namespace

ServiceVideoCaptureDeviceLauncher::ServiceVideoCaptureDeviceLauncher(
    video_capture::mojom::DeviceFactoryPtr* device_factory,
    base::OnceClosure destruction_cb)
    : device_factory_(device_factory),
      destruction_cb_(std::move(destruction_cb)),
      state_(State::READY_TO_LAUNCH),
      callbacks_(nullptr) {}

ServiceVideoCaptureDeviceLauncher::~ServiceVideoCaptureDeviceLauncher() {
  DCHECK(sequence_checker_.CalledOnValidSequence());
  DCHECK(state_ == State::READY_TO_LAUNCH);
  base::ResetAndReturn(&destruction_cb_).Run();
}

void ServiceVideoCaptureDeviceLauncher::LaunchDeviceAsync(
    const std::string& device_id,
    MediaStreamType stream_type,
    const media::VideoCaptureParams& params,
    base::WeakPtr<media::VideoFrameReceiver> receiver,
    base::OnceClosure connection_lost_cb,
    Callbacks* callbacks,
    base::OnceClosure done_cb) {
  DCHECK(sequence_checker_.CalledOnValidSequence());
  DCHECK(state_ == State::READY_TO_LAUNCH);

  if (stream_type != content::MEDIA_DEVICE_VIDEO_CAPTURE) {
    // This launcher only supports MEDIA_DEVICE_VIDEO_CAPTURE.
    NOTREACHED();
    return;
  }

  if (!device_factory_->is_bound()) {
    // This can happen when the ServiceVideoCaptureProvider owning
    // |device_factory_| loses connection to the service process and resets
    // |device_factory_|.
    ConcludeLaunchDeviceWithFailure(false, callbacks, std::move(done_cb));
    return;
  }
  video_capture::mojom::DevicePtr device;
  auto device_request = mojo::MakeRequest(&device);
  // Ownership of |done_cb| is moved to |this|. It is not sufficient to attach
  // it to the callback passed to |(*device_factory_)->CreateDevice()|, because
  // |device_factory_| may get torn down before the callback is invoked.
  done_cb_ = std::move(done_cb);
  callbacks_ = callbacks;
  // Use of Unretained(this) is safe, because |done_cb_| guarantees that |this|
  // stays alive.
  device.set_connection_error_handler(
      base::Bind(&ServiceVideoCaptureDeviceLauncher::
                     OnConnectionLostWhileWaitingForCallback,
                 base::Unretained(this)));
  (*device_factory_)
      ->CreateDevice(
          device_id, std::move(device_request),
          base::Bind(
              // Use of Unretained |this| is safe, because |done_cb_| guarantees
              // that |this| stays alive.
              &ServiceVideoCaptureDeviceLauncher::OnCreateDeviceCallback,
              base::Unretained(this), params, base::Passed(&device),
              std::move(receiver), base::Passed(&connection_lost_cb)));
  state_ = State::DEVICE_START_IN_PROGRESS;
}

void ServiceVideoCaptureDeviceLauncher::AbortLaunch() {
  DCHECK(sequence_checker_.CalledOnValidSequence());
  if (state_ == State::DEVICE_START_IN_PROGRESS)
    state_ = State::DEVICE_START_ABORTING;
}

void ServiceVideoCaptureDeviceLauncher::OnCreateDeviceCallback(
    const media::VideoCaptureParams& params,
    video_capture::mojom::DevicePtr device,
    base::WeakPtr<media::VideoFrameReceiver> receiver,
    base::OnceClosure connection_lost_cb,
    video_capture::mojom::DeviceAccessResultCode result_code) {
  DCHECK(sequence_checker_.CalledOnValidSequence());
  DCHECK(callbacks_);
  DCHECK(done_cb_);
  device.set_connection_error_handler(base::Bind(&base::DoNothing));
  const bool abort_requested = (state_ == State::DEVICE_START_ABORTING);
  state_ = State::READY_TO_LAUNCH;
  Callbacks* callbacks = callbacks_;
  callbacks_ = nullptr;
  switch (result_code) {
    case video_capture::mojom::DeviceAccessResultCode::SUCCESS:
      ConcludeLaunchDeviceWithSuccess(
          abort_requested, params, std::move(device), std::move(receiver),
          std::move(connection_lost_cb), callbacks, std::move(done_cb_));
      return;
    case video_capture::mojom::DeviceAccessResultCode::ERROR_DEVICE_NOT_FOUND:
    case video_capture::mojom::DeviceAccessResultCode::NOT_INITIALIZED:
      ConcludeLaunchDeviceWithFailure(abort_requested, callbacks,
                                      std::move(done_cb_));
      return;
  }
}

void ServiceVideoCaptureDeviceLauncher::
    OnConnectionLostWhileWaitingForCallback() {
  DCHECK(sequence_checker_.CalledOnValidSequence());
  DCHECK(callbacks_);
  const bool abort_requested = (state_ == State::DEVICE_START_ABORTING);
  state_ = State::READY_TO_LAUNCH;
  Callbacks* callbacks = callbacks_;
  callbacks_ = nullptr;
  ConcludeLaunchDeviceWithFailure(abort_requested, callbacks,
                                  std::move(done_cb_));
}

}  // namespace content
