// Copyright 2019 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/public/browser/video_capture_service.h"

#include "base/no_destructor.h"
#include "base/task/thread_pool.h"
#include "base/threading/sequence_local_storage_slot.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/service_sandbox_type.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/video_capture/public/uma/video_capture_service_event.h"
#include "services/video_capture/video_capture_service_impl.h"

#if defined(OS_WIN)
#define CREATE_IN_PROCESS_TASK_RUNNER base::ThreadPool::CreateCOMSTATaskRunner
#else
#define CREATE_IN_PROCESS_TASK_RUNNER \
  base::ThreadPool::CreateSingleThreadTaskRunner
#endif

namespace content {

namespace {

video_capture::mojom::VideoCaptureService* g_service_override = nullptr;

void BindInProcessInstance(
    mojo::PendingReceiver<video_capture::mojom::VideoCaptureService> receiver) {
  static base::NoDestructor<video_capture::VideoCaptureServiceImpl> service(
      std::move(receiver), GetUIThreadTaskRunner({}));
}

mojo::Remote<video_capture::mojom::VideoCaptureService>& GetUIThreadRemote() {
  // NOTE: This use of sequence-local storage is only to ensure that the Remote
  // only lives as long as the UI-thread sequence, since the UI-thread sequence
  // may be torn down and reinitialized e.g. between unit tests.
  static base::NoDestructor<base::SequenceLocalStorageSlot<
      mojo::Remote<video_capture::mojom::VideoCaptureService>>>
      remote_slot;
  return remote_slot->GetOrCreateValue();
}

// This is a custom traits type we use in conjunction with mojo::ReceiverSetBase
// so that all dispatched messages can be forwarded to the currently bound UI
// thread Remote.
struct ForwardingImplRefTraits {
  using PointerType = void*;
  static bool IsNull(PointerType) { return false; }
  static video_capture::mojom::VideoCaptureService* GetRawPointer(PointerType) {
    return &GetVideoCaptureService();
  }
};

// If |GetVideoCaptureService()| is called from off the UI thread, return a
// sequence-local Remote. Its corresponding receiver will be bound in this set,
// forwarding to the current UI-thread Remote.
void BindProxyRemoteOnUIThread(
    mojo::PendingReceiver<video_capture::mojom::VideoCaptureService> receiver) {
  static base::NoDestructor<mojo::ReceiverSetBase<
      mojo::Receiver<video_capture::mojom::VideoCaptureService,
                     ForwardingImplRefTraits>,
      void>>
      receivers;
  receivers->Add(nullptr, std::move(receiver));
}

}  // namespace

video_capture::mojom::VideoCaptureService& GetVideoCaptureService() {
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    static base::NoDestructor<base::SequenceLocalStorageSlot<
        mojo::Remote<video_capture::mojom::VideoCaptureService>>>
        storage;
    auto& remote = storage->GetOrCreateValue();
    if (!remote.is_bound()) {
      GetUIThreadTaskRunner({})->PostTask(
          FROM_HERE, base::BindOnce(&BindProxyRemoteOnUIThread,
                                    remote.BindNewPipeAndPassReceiver()));
    }
    return *remote.get();
  }

  if (g_service_override)
    return *g_service_override;

  auto& remote = GetUIThreadRemote();
  if (!remote.is_bound()) {
    auto receiver = remote.BindNewPipeAndPassReceiver();
    if (features::IsVideoCaptureServiceEnabledForBrowserProcess()) {
      auto dedicated_task_runner = CREATE_IN_PROCESS_TASK_RUNNER(
          {base::MayBlock(), base::WithBaseSyncPrimitives(),
           base::TaskPriority::BEST_EFFORT},
          base::SingleThreadTaskRunnerThreadMode::DEDICATED);
      dedicated_task_runner->PostTask(
          FROM_HERE,
          base::BindOnce(&BindInProcessInstance, std::move(receiver)));
    } else {
      ServiceProcessHost::Launch(
          std::move(receiver),
          ServiceProcessHost::Options()
              .WithDisplayName("Video Capture")
#if defined(OS_MAC)
              // On Mac, the service requires a CFRunLoop which is provided by a
              // UI message loop. See https://crbug.com/834581.
              .WithExtraCommandLineSwitches({switches::kMessageLoopTypeUi})
              // On Mac, the service also needs to have a different set of
              // entitlements, the reason being that some virtual cameras
              // are not signed or are signed by a different Team ID. Hence,
              // library validation has to be disabled (see
              // http://crbug.com/990381#c21).
              .WithChildFlags(ChildProcessHost::CHILD_PLUGIN)
#endif
              .Pass());

#if !defined(OS_ANDROID)
      // On Android, we do not use automatic service shutdown, because when
      // shutting down the service, we lose caching of the supported formats,
      // and re-querying these can take several seconds on certain Android
      // devices.
      remote.set_idle_handler(
          base::TimeDelta::FromSeconds(5),
          base::BindRepeating(
              [](mojo::Remote<video_capture::mojom::VideoCaptureService>*
                     remote) {
                video_capture::uma::LogVideoCaptureServiceEvent(
                    video_capture::uma ::
                        SERVICE_SHUTTING_DOWN_BECAUSE_NO_CLIENT);
                remote->reset();
              },
              &remote));
#endif  // !defined(OS_ANDROID)

      // Make sure the Remote is also reset in case of e.g. service crash so we
      // can restart it as needed.
      remote.reset_on_disconnect();
    }
  }

  return *remote.get();
}

void OverrideVideoCaptureServiceForTesting(
    video_capture::mojom::VideoCaptureService* service) {
  g_service_override = service;
}

}  // namespace content
