// 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 "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"

#import <AVFoundation/AVFoundation.h>

#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#import "base/task/sequenced_task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/media/webrtc/media_authorization_wrapper_mac.h"
#include "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
#include "chrome/common/chrome_features.h"
#include "media/base/media_switches.h"
#include "ui/base/cocoa/permissions_utils.h"

namespace system_media_permissions {

namespace {

bool UsingFakeMediaDevices() {
  return base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kUseFakeDeviceForMediaStream);
}

// Pointer to OS call wrapper that tests can set.
MediaAuthorizationWrapper* g_media_authorization_wrapper_for_tests = nullptr;

// Implementation of OS call wrapper that does the actual OS calls.
class MediaAuthorizationWrapperImpl final : public MediaAuthorizationWrapper {
 public:
  MediaAuthorizationWrapperImpl() = default;

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

  ~MediaAuthorizationWrapperImpl() override = default;

  AVAuthorizationStatus AuthorizationStatusForMediaType(
      AVMediaType media_type) override {
    return [AVCaptureDevice authorizationStatusForMediaType:media_type];
  }

  void RequestAccessForMediaType(AVMediaType media_type,
                                 base::OnceClosure callback) override {
    __block base::OnceClosure block_callback = std::move(callback);
    __block scoped_refptr<base::SequencedTaskRunner> requesting_thread =
        base::SequencedTaskRunner::GetCurrentDefault();
    [AVCaptureDevice requestAccessForMediaType:media_type
                             completionHandler:^(BOOL granted) {
                               requesting_thread->PostTask(
                                   FROM_HERE, std::move(block_callback));
                             }];
  }
};

MediaAuthorizationWrapper& GetMediaAuthorizationWrapper() {
  if (g_media_authorization_wrapper_for_tests)
    return *g_media_authorization_wrapper_for_tests;

  static base::NoDestructor<MediaAuthorizationWrapperImpl>
      media_authorization_wrapper;
  return *media_authorization_wrapper;
}

AVAuthorizationStatus MediaAuthorizationStatus(AVMediaType media_type) {
  return GetMediaAuthorizationWrapper().AuthorizationStatusForMediaType(
      media_type);
}

SystemPermission CheckSystemMediaCapturePermission(AVMediaType media_type) {
  if (UsingFakeMediaDevices()) {
    return SystemPermission::kAllowed;
  }

  AVAuthorizationStatus auth_status = MediaAuthorizationStatus(media_type);
  switch (auth_status) {
    case AVAuthorizationStatusNotDetermined:
      return SystemPermission::kNotDetermined;
    case AVAuthorizationStatusRestricted:
      return SystemPermission::kRestricted;
    case AVAuthorizationStatusDenied:
      return SystemPermission::kDenied;
    case AVAuthorizationStatusAuthorized:
      return SystemPermission::kAllowed;
    default:
      NOTREACHED_NORETURN();
  }
}

void RequestSystemMediaCapturePermission(AVMediaType media_type,
                                         base::OnceClosure callback) {
  if (UsingFakeMediaDevices()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(callback));
    return;
  }

  GetMediaAuthorizationWrapper().RequestAccessForMediaType(media_type,
                                                           std::move(callback));
}

bool IsScreenCaptureAllowed() {
  if (!base::FeatureList::IsEnabled(
          features::kMacSystemScreenCapturePermissionCheck)) {
    return true;
  }

  bool allowed = ui::IsScreenCaptureAllowed();
  LogSystemScreenCapturePermission(allowed);
  return allowed;
}

}  // namespace

SystemPermission CheckSystemAudioCapturePermission() {
  return CheckSystemMediaCapturePermission(AVMediaTypeAudio);
}

SystemPermission CheckSystemVideoCapturePermission() {
  return CheckSystemMediaCapturePermission(AVMediaTypeVideo);
}

SystemPermission CheckSystemScreenCapturePermission() {
  return IsScreenCaptureAllowed() ? SystemPermission::kAllowed
                                  : SystemPermission::kDenied;
}

void RequestSystemAudioCapturePermission(base::OnceClosure callback) {
  RequestSystemMediaCapturePermission(AVMediaTypeAudio, std::move(callback));
}

void RequestSystemVideoCapturePermission(base::OnceClosure callback) {
  RequestSystemMediaCapturePermission(AVMediaTypeVideo, std::move(callback));
}

void SetMediaAuthorizationWrapperForTesting(
    MediaAuthorizationWrapper* wrapper) {
  CHECK(!g_media_authorization_wrapper_for_tests);
  g_media_authorization_wrapper_for_tests = wrapper;
}

}  // namespace system_media_permissions
