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

#include "components/permissions/bluetooth_delegate_impl.h"

#include "base/observer_list.h"
#include "base/strings/utf_string_conversions.h"
#include "components/permissions/contexts/bluetooth_chooser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"

using blink::WebBluetoothDeviceId;
using content::RenderFrameHost;
using device::BluetoothUUID;

namespace permissions {

BluetoothDelegateImpl::BluetoothDelegateImpl(std::unique_ptr<Client> client)
    : client_(std::move(client)) {}

BluetoothDelegateImpl::~BluetoothDelegateImpl() = default;

std::unique_ptr<content::BluetoothChooser>
BluetoothDelegateImpl::RunBluetoothChooser(
    content::RenderFrameHost* frame,
    const content::BluetoothChooser::EventHandler& event_handler) {
  return client_->RunBluetoothChooser(frame, event_handler);
}

std::unique_ptr<content::BluetoothScanningPrompt>
BluetoothDelegateImpl::ShowBluetoothScanningPrompt(
    content::RenderFrameHost* frame,
    const content::BluetoothScanningPrompt::EventHandler& event_handler) {
  return client_->ShowBluetoothScanningPrompt(frame, event_handler);
}

void BluetoothDelegateImpl::ShowDevicePairPrompt(
    RenderFrameHost* frame,
    const std::u16string& device_identifier,
    PairPromptCallback callback,
    PairingKind pairing_kind,
    const absl::optional<std::u16string>& pin) {
  client_->ShowBluetoothDevicePairDialog(
      frame, device_identifier, std::move(callback), pairing_kind, pin);
}

WebBluetoothDeviceId BluetoothDelegateImpl::GetWebBluetoothDeviceId(
    RenderFrameHost* frame,
    const std::string& device_address) {
  return client_->GetBluetoothChooserContext(frame)->GetWebBluetoothDeviceId(
      frame->GetMainFrame()->GetLastCommittedOrigin(), device_address);
}

std::string BluetoothDelegateImpl::GetDeviceAddress(
    RenderFrameHost* frame,
    const WebBluetoothDeviceId& device_id) {
  return client_->GetBluetoothChooserContext(frame)->GetDeviceAddress(
      frame->GetMainFrame()->GetLastCommittedOrigin(), device_id);
}

WebBluetoothDeviceId BluetoothDelegateImpl::AddScannedDevice(
    RenderFrameHost* frame,
    const std::string& device_address) {
  return client_->GetBluetoothChooserContext(frame)->AddScannedDevice(
      frame->GetMainFrame()->GetLastCommittedOrigin(), device_address);
}

WebBluetoothDeviceId BluetoothDelegateImpl::GrantServiceAccessPermission(
    RenderFrameHost* frame,
    const device::BluetoothDevice* device,
    const blink::mojom::WebBluetoothRequestDeviceOptions* options) {
  return client_->GetBluetoothChooserContext(frame)
      ->GrantServiceAccessPermission(
          frame->GetMainFrame()->GetLastCommittedOrigin(), device, options);
}

bool BluetoothDelegateImpl::HasDevicePermission(
    RenderFrameHost* frame,
    const WebBluetoothDeviceId& device_id) {
  return client_->GetBluetoothChooserContext(frame)->HasDevicePermission(
      frame->GetMainFrame()->GetLastCommittedOrigin(), device_id);
}

void BluetoothDelegateImpl::RevokeDevicePermissionWebInitiated(
    RenderFrameHost* frame,
    const WebBluetoothDeviceId& device_id) {
  client_->GetBluetoothChooserContext(frame)
      ->RevokeDevicePermissionWebInitiated(
          frame->GetMainFrame()->GetLastCommittedOrigin(), device_id);
}

bool BluetoothDelegateImpl::IsAllowedToAccessService(
    RenderFrameHost* frame,
    const WebBluetoothDeviceId& device_id,
    const BluetoothUUID& service) {
  return client_->GetBluetoothChooserContext(frame)->IsAllowedToAccessService(
      frame->GetMainFrame()->GetLastCommittedOrigin(), device_id, service);
}

bool BluetoothDelegateImpl::IsAllowedToAccessAtLeastOneService(
    RenderFrameHost* frame,
    const WebBluetoothDeviceId& device_id) {
  return client_->GetBluetoothChooserContext(frame)
      ->IsAllowedToAccessAtLeastOneService(
          frame->GetMainFrame()->GetLastCommittedOrigin(), device_id);
}

bool BluetoothDelegateImpl::IsAllowedToAccessManufacturerData(
    RenderFrameHost* frame,
    const WebBluetoothDeviceId& device_id,
    uint16_t manufacturer_code) {
  return client_->GetBluetoothChooserContext(frame)
      ->IsAllowedToAccessManufacturerData(
          frame->GetMainFrame()->GetLastCommittedOrigin(), device_id,
          manufacturer_code);
}

void BluetoothDelegateImpl::AddFramePermissionObserver(
    FramePermissionObserver* observer) {
  std::unique_ptr<ChooserContextPermissionObserver>& chooser_observer =
      chooser_observers_[observer->GetRenderFrameHost()];
  if (!chooser_observer) {
    chooser_observer = std::make_unique<ChooserContextPermissionObserver>(
        this,
        client_->GetBluetoothChooserContext(observer->GetRenderFrameHost()));
  }

  chooser_observer->AddFramePermissionObserver(observer);
}

void BluetoothDelegateImpl::RemoveFramePermissionObserver(
    FramePermissionObserver* observer) {
  auto it = chooser_observers_.find(observer->GetRenderFrameHost());
  if (it == chooser_observers_.end())
    return;
  it->second->RemoveFramePermissionObserver(observer);
}

std::vector<blink::mojom::WebBluetoothDevicePtr>
BluetoothDelegateImpl::GetPermittedDevices(content::RenderFrameHost* frame) {
  auto* context = client_->GetBluetoothChooserContext(frame);
  std::vector<std::unique_ptr<ObjectPermissionContextBase::Object>> objects =
      context->GetGrantedObjects(
          frame->GetMainFrame()->GetLastCommittedOrigin());
  std::vector<blink::mojom::WebBluetoothDevicePtr> permitted_devices;

  for (const auto& object : objects) {
    auto permitted_device = blink::mojom::WebBluetoothDevice::New();
    permitted_device->id =
        BluetoothChooserContext::GetObjectDeviceId(object->value);
    permitted_device->name =
        base::UTF16ToUTF8(context->GetObjectDisplayName(object->value));
    permitted_devices.push_back(std::move(permitted_device));
  }

  return permitted_devices;
}

BluetoothDelegateImpl::ChooserContextPermissionObserver::
    ChooserContextPermissionObserver(BluetoothDelegateImpl* owning_delegate,
                                     ObjectPermissionContextBase* context)
    : owning_delegate_(owning_delegate) {
  observer_.Observe(context);
}

BluetoothDelegateImpl::ChooserContextPermissionObserver::
    ~ChooserContextPermissionObserver() = default;

void BluetoothDelegateImpl::ChooserContextPermissionObserver::
    OnPermissionRevoked(const url::Origin& origin) {
  observers_pending_removal_.clear();
  is_traversing_observers_ = true;

  for (auto& observer : observer_list_)
    observer.OnPermissionRevoked(origin);

  is_traversing_observers_ = false;
  for (FramePermissionObserver* observer : observers_pending_removal_)
    RemoveFramePermissionObserver(observer);
}

void BluetoothDelegateImpl::ChooserContextPermissionObserver::
    AddFramePermissionObserver(FramePermissionObserver* observer) {
  observer_list_.AddObserver(observer);
}

void BluetoothDelegateImpl::ChooserContextPermissionObserver::
    RemoveFramePermissionObserver(FramePermissionObserver* observer) {
  if (is_traversing_observers_) {
    observers_pending_removal_.emplace_back(observer);
    return;
  }

  observer_list_.RemoveObserver(observer);
  if (observer_list_.empty())
    owning_delegate_->chooser_observers_.erase(observer->GetRenderFrameHost());
  // Previous call destructed this instance. Don't add code after this.
}

}  // namespace permissions