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

#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/public/browser/video_capture_service.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "media/base/media_switches.h"
#include "media/capture/capture_switches.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
#include "services/video_capture/public/mojom/device.mojom.h"
#include "services/video_capture/public/mojom/video_capture_service.mojom.h"
#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
#include "services/video_capture/public/mojom/video_source.mojom.h"
#include "services/video_capture/public/mojom/video_source_provider.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"

using testing::_;
using testing::AtLeast;
using testing::InvokeWithoutArgs;
using testing::Return;

namespace content {

namespace {

using GetSourceInfosResult =
    video_capture::mojom::VideoSourceProvider::GetSourceInfosResult;

static const char kVideoCaptureHtmlFile[] = "/media/video_capture_test.html";
static const char kStartVideoCaptureAndVerify[] =
    "startVideoCaptureAndVerifySize(%d, %d)";
static const gfx::Size kVideoSize(320, 200);

}  // namespace

// Integration test sets up a single fake device and obtains a connection to the
// video capture service via the Browser process' service manager. It then
// opens the device from clients. One client is the test calling into the
// video capture service directly. The second client is the Browser, which the
// test exercises through JavaScript.
class WebRtcVideoCaptureSharedDeviceBrowserTest : public ContentBrowserTest {
 public:
  WebRtcVideoCaptureSharedDeviceBrowserTest() = default;
  WebRtcVideoCaptureSharedDeviceBrowserTest(
      const WebRtcVideoCaptureSharedDeviceBrowserTest&) = delete;
  WebRtcVideoCaptureSharedDeviceBrowserTest& operator=(
      const WebRtcVideoCaptureSharedDeviceBrowserTest&) = delete;

  ~WebRtcVideoCaptureSharedDeviceBrowserTest() override {}

  void OpenDeviceViaService() {
    GetVideoCaptureService().ConnectToVideoSourceProvider(
        video_source_provider_.BindNewPipeAndPassReceiver());
    video_source_provider_->GetSourceInfos(base::BindOnce(
        &WebRtcVideoCaptureSharedDeviceBrowserTest::OnSourceInfosReceived,
        weak_factory_.GetWeakPtr(),
        media::VideoCaptureBufferType::kSharedMemory));
  }

  void OpenDeviceInRendererAndWaitForPlaying() {
    DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
    embedded_test_server()->StartAcceptingConnections();
    GURL url(embedded_test_server()->GetURL(kVideoCaptureHtmlFile));
    EXPECT_TRUE(NavigateToURL(shell(), url));

    const std::string javascript_to_execute = base::StringPrintf(
        kStartVideoCaptureAndVerify, kVideoSize.width(), kVideoSize.height());
    // Start video capture and wait until it started rendering
    ASSERT_TRUE(ExecJs(shell(), javascript_to_execute));
  }

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
    command_line->AppendSwitch(
        switches::kDisableVideoCaptureUseGpuMemoryBuffer);
  }

  void SetUp() override {
    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
    EnablePixelOutput();
    ContentBrowserTest::SetUp();
  }

  void Initialize() {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    main_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
    mock_video_frame_handler_ =
        std::make_unique<video_capture::MockVideoFrameHandler>(
            subscriber_.InitWithNewPipeAndPassReceiver());
  }

  scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
  std::unique_ptr<video_capture::MockVideoFrameHandler>
      mock_video_frame_handler_;

 private:
  void OnSourceInfosReceived(
      media::VideoCaptureBufferType buffer_type_to_request,
      GetSourceInfosResult result,
      const std::vector<media::VideoCaptureDeviceInfo>& infos) {
    ASSERT_EQ(result, GetSourceInfosResult::kSuccess);
    ASSERT_FALSE(infos.empty());
    video_source_provider_->GetVideoSource(
        infos[0].descriptor.device_id,
        video_source_.BindNewPipeAndPassReceiver());

    media::VideoCaptureParams requestable_settings;
    ASSERT_FALSE(infos[0].supported_formats.empty());
    requestable_settings.requested_format = infos[0].supported_formats[0];
    requestable_settings.requested_format.frame_size = kVideoSize;
    requestable_settings.buffer_type = buffer_type_to_request;

    video_source_->CreatePushSubscription(
        std::move(subscriber_), requestable_settings,
        false /*force_reopen_with_new_settings*/,
        subscription_.BindNewPipeAndPassReceiver(),
        base::BindOnce(&WebRtcVideoCaptureSharedDeviceBrowserTest::
                           OnCreatePushSubscriptionCallback,
                       weak_factory_.GetWeakPtr()));
  }

  void OnCreatePushSubscriptionCallback(
      video_capture::mojom::CreatePushSubscriptionResultCodePtr result_code,
      const media::VideoCaptureParams& params) {
    ASSERT_TRUE(result_code->is_success_code());
    subscription_->Activate();
  }

  mojo::Remote<video_capture::mojom::VideoSourceProvider>
      video_source_provider_;
  mojo::Remote<video_capture::mojom::VideoSource> video_source_;
  mojo::Remote<video_capture::mojom::PushVideoStreamSubscription> subscription_;

  mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> subscriber_;
  base::WeakPtrFactory<WebRtcVideoCaptureSharedDeviceBrowserTest> weak_factory_{
      this};
};

// Tests that a single fake video capture device can be opened via JavaScript
// by the Renderer while it is already in use by a direct client of the
// video capture service.
IN_PROC_BROWSER_TEST_F(
    WebRtcVideoCaptureSharedDeviceBrowserTest,
    ReceiveFrameInRendererWhileDeviceAlreadyInUseViaDirectServiceClient) {
  Initialize();

  base::RunLoop receive_frame_from_service_wait_loop;
  auto expected_buffer_handle_tag =
      media::mojom::VideoBufferHandle::Tag::kUnsafeShmemRegion;
  ON_CALL(*mock_video_frame_handler_, DoOnNewBuffer(_, _))
      .WillByDefault(
          [expected_buffer_handle_tag](
              int32_t, media::mojom::VideoBufferHandlePtr* buffer_handle) {
            ASSERT_EQ(expected_buffer_handle_tag, (*buffer_handle)->which());
          });
  EXPECT_CALL(*mock_video_frame_handler_, DoOnFrameReadyInBuffer(_, _, _))
      .WillOnce(InvokeWithoutArgs([&receive_frame_from_service_wait_loop]() {
        receive_frame_from_service_wait_loop.Quit();
      }))
      .WillRepeatedly(Return());

  OpenDeviceViaService();
  // Note, if we do not wait for the first frame to arrive before opening the
  // device in the Renderer, it could happen that the Renderer takes ove access
  // to the device before a first frame is received by
  // mock_video_frame_handler_.
  receive_frame_from_service_wait_loop.Run();

  OpenDeviceInRendererAndWaitForPlaying();
}

// Tests that a single fake video capture device can be opened by a direct
// client of the video capture service while it is already in use via JavaScript
// by the Renderer.
IN_PROC_BROWSER_TEST_F(
    WebRtcVideoCaptureSharedDeviceBrowserTest,
    ReceiveFrameViaDirectServiceClientWhileDeviceAlreadyInUseViaRenderer) {
  Initialize();

  base::RunLoop receive_frame_from_service_wait_loop;
  auto expected_buffer_handle_tag =
      media::mojom::VideoBufferHandle::Tag::kUnsafeShmemRegion;
  ON_CALL(*mock_video_frame_handler_, DoOnNewBuffer(_, _))
      .WillByDefault(
          [expected_buffer_handle_tag](
              int32_t, media::mojom::VideoBufferHandlePtr* buffer_handle) {
            ASSERT_EQ(expected_buffer_handle_tag, (*buffer_handle)->which());
          });
  EXPECT_CALL(*mock_video_frame_handler_, DoOnFrameReadyInBuffer(_, _, _))
      .WillOnce(InvokeWithoutArgs([&receive_frame_from_service_wait_loop]() {
        receive_frame_from_service_wait_loop.Quit();
      }))
      .WillRepeatedly(Return());

  OpenDeviceInRendererAndWaitForPlaying();

  OpenDeviceViaService();
  receive_frame_from_service_wait_loop.Run();
}

}  // namespace content
