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

#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"

#include <map>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "content/public/browser/browser_thread.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_discovery_session.h"
#include "extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h"
#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
#include "extensions/browser/api/bluetooth/bluetooth_private_api.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_host.h"
#include "extensions/common/api/bluetooth.h"
#include "extensions/common/api/bluetooth_private.h"
#include "extensions/common/extension_id.h"

namespace extensions {

namespace {

constexpr char kScanClientName[] = "Chrome Extension";

void IgnoreAdapterResult(scoped_refptr<device::BluetoothAdapter> adapter) {}

void IgnoreAdapterResultAndThen(
    base::OnceClosure callback,
    scoped_refptr<device::BluetoothAdapter> adapter) {
  std::move(callback).Run();
}

std::string GetListenerId(const extensions::EventListenerInfo& details) {
  return !details.extension_id.empty() ? details.extension_id
                                       : details.listener_url.GetHost();
}

}  // namespace

namespace bluetooth = api::bluetooth;
namespace bt_private = api::bluetooth_private;

BluetoothEventRouter::BluetoothEventRouter(content::BrowserContext* context)
    : browser_context_(context), adapter_(nullptr) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  BLUETOOTH_LOG(USER) << "BluetoothEventRouter()";
  DCHECK(browser_context_);
  extension_registry_observation_.Observe(
      ExtensionRegistry::Get(browser_context_));
  extension_host_registry_observation_.Observe(
      ExtensionHostRegistry::Get(browser_context_));
}

BluetoothEventRouter::~BluetoothEventRouter() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  BLUETOOTH_LOG(USER) << "~BluetoothEventRouter()";
  if (adapter_.get()) {
    adapter_->RemoveObserver(this);
    adapter_ = nullptr;
  }
  CleanUpAllExtensions();
}

bool BluetoothEventRouter::IsBluetoothSupported() const {
  return device::BluetoothAdapterFactory::IsBluetoothSupported();
}

void BluetoothEventRouter::GetAdapter(
    device::BluetoothAdapterFactory::AdapterCallback callback) {
  if (adapter_.get()) {
    std::move(callback).Run(adapter_);
    return;
  }

  // Note: On ChromeOS this will return an adapter that also supports Bluetooth
  // Low Energy.
  device::BluetoothAdapterFactory::Get()->GetClassicAdapter(
      base::BindOnce(&BluetoothEventRouter::OnAdapterInitialized,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void BluetoothEventRouter::StartDiscoverySession(
    device::BluetoothAdapter* adapter,
    const ExtensionId& extension_id,
    base::OnceClosure callback,
    base::OnceClosure error_callback) {
  if (!adapter_.get() && IsBluetoothSupported()) {
    // If |adapter_| isn't set yet, call GetAdapter() which will synchronously
    // invoke the callback (StartDiscoverySessionImpl).
    GetAdapter(base::BindOnce(
        &IgnoreAdapterResultAndThen,
        base::BindOnce(&BluetoothEventRouter::StartDiscoverySessionImpl,
                       weak_ptr_factory_.GetWeakPtr(),
                       base::RetainedRef(adapter), extension_id,
                       std::move(callback), std::move(error_callback))));
    return;
  }
  StartDiscoverySessionImpl(adapter, extension_id, std::move(callback),
                            std::move(error_callback));
}

void BluetoothEventRouter::StartDiscoverySessionImpl(
    device::BluetoothAdapter* adapter,
    const ExtensionId& extension_id,
    base::OnceClosure callback,
    base::OnceClosure error_callback) {
  if (!adapter_.get()) {
    BLUETOOTH_LOG(ERROR) << "Unable to get Bluetooth adapter.";
    std::move(error_callback).Run();
    return;
  }
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(ERROR) << "Bluetooth adapter mismatch.";
    std::move(error_callback).Run();
    return;
  }
  auto iter = discovery_session_map_.find(extension_id);
  if (iter != discovery_session_map_.end() && iter->second->IsActive()) {
    BLUETOOTH_LOG(DEBUG) << "An active discovery session exists for extension: "
                         << extension_id;
    std::move(error_callback).Run();
    return;
  }

  BLUETOOTH_LOG(USER) << "StartDiscoverySession: " << extension_id;

  // Check whether user pre set discovery filter by calling SetDiscoveryFilter
  // before. If the user has set a discovery filter then start a filtered
  // discovery session, otherwise start a regular session
  auto pre_set_iter = pre_set_filter_map_.find(extension_id);
  if (pre_set_iter != pre_set_filter_map_.end()) {
    adapter->StartDiscoverySessionWithFilter(
        std::unique_ptr<device::BluetoothDiscoveryFilter>(pre_set_iter->second),
        kScanClientName,
        base::BindOnce(&BluetoothEventRouter::OnStartDiscoverySession,
                       weak_ptr_factory_.GetWeakPtr(), extension_id,
                       std::move(callback)),
        std::move(error_callback));
    pre_set_filter_map_.erase(pre_set_iter);
    return;
  }
  adapter->StartDiscoverySession(
      kScanClientName,
      base::BindOnce(&BluetoothEventRouter::OnStartDiscoverySession,
                     weak_ptr_factory_.GetWeakPtr(), extension_id,
                     std::move(callback)),
      std::move(error_callback));
}

void BluetoothEventRouter::StopDiscoverySession(
    device::BluetoothAdapter* adapter,
    const ExtensionId& extension_id,
    base::OnceClosure callback,
    base::OnceClosure error_callback) {
  if (adapter != adapter_.get()) {
    std::move(error_callback).Run();
    return;
  }
  auto iter = discovery_session_map_.find(extension_id);
  if (iter == discovery_session_map_.end() || !iter->second->IsActive()) {
    BLUETOOTH_LOG(DEBUG) << "No active discovery session exists for extension.";
    std::move(error_callback).Run();
    return;
  }
  BLUETOOTH_LOG(USER) << "StopDiscoverySession: " << extension_id;
  device::BluetoothDiscoverySession* session = iter->second;
  session->Stop();
  std::move(callback).Run();
}

void BluetoothEventRouter::SetDiscoveryFilter(
    std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter,
    device::BluetoothAdapter* adapter,
    const ExtensionId& extension_id,
    base::OnceClosure callback,
    base::OnceClosure error_callback) {
  BLUETOOTH_LOG(USER) << "SetDiscoveryFilter";
  if (!adapter_.get()) {
    BLUETOOTH_LOG(ERROR) << "Unable to get Bluetooth adapter.";
    std::move(error_callback).Run();
    return;
  }
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(ERROR) << "Bluetooth adapter mismatch.";
    std::move(error_callback).Run();
    return;
  }

  auto iter = discovery_session_map_.find(extension_id);
  if (iter == discovery_session_map_.end() || !iter->second->IsActive()) {
    BLUETOOTH_LOG(DEBUG) << "No active discovery session exists for extension, "
                         << "so caching filter for later use.";
    pre_set_filter_map_[extension_id] = discovery_filter.release();
    std::move(callback).Run();
    return;
  }

  // If the session has already started simply start a new one. The callback
  // will automatically delete the old session and put the new session (with its
  // new filter) in as this extension's session
  adapter->StartDiscoverySessionWithFilter(
      std::move(discovery_filter), kScanClientName,
      base::BindOnce(&BluetoothEventRouter::OnStartDiscoverySession,
                     weak_ptr_factory_.GetWeakPtr(), extension_id,
                     std::move(callback)),
      std::move(error_callback));
}

BluetoothApiPairingDelegate* BluetoothEventRouter::GetPairingDelegate(
    const ExtensionId& extension_id) {
  if (auto it = pairing_delegate_map_.find(extension_id);
      it != pairing_delegate_map_.end()) {
    return it->second;
  }
  return nullptr;
}

void BluetoothEventRouter::OnAdapterInitialized(
    device::BluetoothAdapterFactory::AdapterCallback callback,
    scoped_refptr<device::BluetoothAdapter> adapter) {
  if (!adapter_.get()) {
    adapter_ = adapter;
    adapter_->AddObserver(this);
  }

  std::move(callback).Run(adapter);
}

void BluetoothEventRouter::MaybeReleaseAdapter() {
  if (adapter_.get() && event_listener_count_.empty() &&
      pairing_delegate_map_.empty()) {
    BLUETOOTH_LOG(EVENT) << "Releasing Adapter.";
    adapter_->RemoveObserver(this);
    adapter_ = nullptr;
  }
}

void BluetoothEventRouter::AddPairingDelegate(const ExtensionId& extension_id) {
  if (!adapter_.get() && IsBluetoothSupported()) {
    GetAdapter(base::BindOnce(
        &IgnoreAdapterResultAndThen,
        base::BindOnce(&BluetoothEventRouter::AddPairingDelegateImpl,
                       weak_ptr_factory_.GetWeakPtr(), extension_id)));
    return;
  }
  AddPairingDelegateImpl(extension_id);
}

void BluetoothEventRouter::AddPairingDelegateImpl(
    const ExtensionId& extension_id) {
  if (!adapter_.get()) {
    LOG(ERROR) << "Unable to get adapter for extension_id: " << extension_id;
    return;
  }

  auto [it, inserted] =
      pairing_delegate_map_.try_emplace(extension_id, nullptr);
  if (!inserted) {
    // For WebUI there may be more than one page open to the same url
    // (e.g. chrome://settings). These will share the same pairing delegate.
    BLUETOOTH_LOG(EVENT) << "Pairing delegate already exists for extension_id: "
                         << extension_id;
    return;
  }
  it->second = new BluetoothApiPairingDelegate(browser_context_);
  DCHECK(adapter_.get());
  adapter_->AddPairingDelegate(
      it->second, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH);
}

void BluetoothEventRouter::RemovePairingDelegate(
    const ExtensionId& extension_id) {
  if (auto it = pairing_delegate_map_.find(extension_id);
      it != pairing_delegate_map_.end()) {
    BluetoothApiPairingDelegate* delegate = it->second;
    if (adapter_.get())
      adapter_->RemovePairingDelegate(delegate);
    pairing_delegate_map_.erase(it);
    delete delegate;
    MaybeReleaseAdapter();
  }
}

void BluetoothEventRouter::AdapterPresentChanged(
    device::BluetoothAdapter* adapter,
    bool present) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(DEBUG) << "Ignoring event for adapter "
                         << adapter->GetAddress();
    return;
  }
  DispatchAdapterStateEvent();
}

void BluetoothEventRouter::AdapterPoweredChanged(
    device::BluetoothAdapter* adapter,
    bool has_power) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(DEBUG) << "Ignoring event for adapter "
                         << adapter->GetAddress();
    return;
  }
  DispatchAdapterStateEvent();
}

void BluetoothEventRouter::AdapterDiscoveringChanged(
    device::BluetoothAdapter* adapter,
    bool discovering) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(DEBUG) << "Ignoring event for adapter "
                         << adapter->GetAddress();
    return;
  }

  if (!discovering) {
    // If any discovery sessions are inactive, clean them up.
    DiscoverySessionMap active_session_map;
    for (auto iter = discovery_session_map_.begin();
         iter != discovery_session_map_.end(); ++iter) {
      device::BluetoothDiscoverySession* session = iter->second;
      if (session->IsActive()) {
        active_session_map[iter->first] = session;
        continue;
      }
      delete session;
    }
    discovery_session_map_.swap(active_session_map);
  }

  DispatchAdapterStateEvent();

  // Release the adapter after dispatching the event.
  if (!discovering) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&BluetoothEventRouter::MaybeReleaseAdapter,
                                  weak_ptr_factory_.GetWeakPtr()));
  }
}

void BluetoothEventRouter::DeviceAdded(device::BluetoothAdapter* adapter,
                                       device::BluetoothDevice* device) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(DEBUG) << "Ignoring event for adapter "
                         << adapter->GetAddress();
    return;
  }

  DispatchDeviceEvent(events::BLUETOOTH_ON_DEVICE_ADDED,
                      bluetooth::OnDeviceAdded::kEventName, device);
}

void BluetoothEventRouter::DeviceChanged(device::BluetoothAdapter* adapter,
                                         device::BluetoothDevice* device) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(DEBUG) << "Ignoring event for adapter "
                         << adapter->GetAddress();
    return;
  }

  DispatchDeviceEvent(events::BLUETOOTH_ON_DEVICE_CHANGED,
                      bluetooth::OnDeviceChanged::kEventName, device);
}

void BluetoothEventRouter::DeviceRemoved(device::BluetoothAdapter* adapter,
                                         device::BluetoothDevice* device) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(DEBUG) << "Ignoring event for adapter "
                         << adapter->GetAddress();
    return;
  }

  DispatchDeviceEvent(events::BLUETOOTH_ON_DEVICE_REMOVED,
                      bluetooth::OnDeviceRemoved::kEventName, device);
}

void BluetoothEventRouter::DeviceAddressChanged(
    device::BluetoothAdapter* adapter,
    device::BluetoothDevice* device,
    const std::string& old_address) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (adapter != adapter_.get()) {
    BLUETOOTH_LOG(DEBUG) << "Ignoring event for adapter "
                         << adapter->GetAddress();
    return;
  }
  DCHECK(device);

  bluetooth::Device extension_device;
  bluetooth::BluetoothDeviceToApiDevice(*device, &extension_device);

  auto args =
      bt_private::OnDeviceAddressChanged::Create(extension_device, old_address);
  auto event = std::make_unique<Event>(
      events::BLUETOOTH_PRIVATE_ON_DEVICE_ADDRESS_CHANGED,
      bt_private::OnDeviceAddressChanged::kEventName, std::move(args));
  EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
}

void BluetoothEventRouter::OnListenerAdded(const EventListenerInfo& details) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  std::string id = GetListenerId(details);
  int count = ++event_listener_count_[id];
  BLUETOOTH_LOG(EVENT) << "Event Listener Added: " << id << " Count: " << count;
  if (!adapter_.get())
    GetAdapter(base::BindOnce(&IgnoreAdapterResult));
}

void BluetoothEventRouter::OnListenerRemoved(const EventListenerInfo& details) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  std::string id = GetListenerId(details);
  auto iter = event_listener_count_.find(id);
  CHECK(iter != event_listener_count_.end());
  int count = --(iter->second);
  BLUETOOTH_LOG(EVENT) << "Event Listener Removed: " << id
                       << " Count: " << count;
  if (count == 0) {
    event_listener_count_.erase(iter);
    // When all listeners for a listener id have been removed, remove any
    // pairing delegate or discovery session and filters.
    CleanUpForExtension(id);
  }
  MaybeReleaseAdapter();
}

void BluetoothEventRouter::DispatchAdapterStateEvent() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  api::bluetooth::AdapterState state;
  CHECK(adapter_.get());
  PopulateAdapterState(*adapter_, &state);

  auto args = bluetooth::OnAdapterStateChanged::Create(state);
  std::unique_ptr<Event> event(
      new Event(events::BLUETOOTH_ON_ADAPTER_STATE_CHANGED,
                bluetooth::OnAdapterStateChanged::kEventName, std::move(args)));
  EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
}

void BluetoothEventRouter::DispatchDeviceEvent(
    events::HistogramValue histogram_value,
    const std::string& event_name,
    device::BluetoothDevice* device) {
  bluetooth::Device extension_device;
  CHECK(device);
  bluetooth::BluetoothDeviceToApiDevice(*device, &extension_device);

  auto args = bluetooth::OnDeviceAdded::Create(extension_device);
  std::unique_ptr<Event> event(
      new Event(histogram_value, event_name, std::move(args)));
  EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
}

void BluetoothEventRouter::CleanUpForExtension(
    const ExtensionId& extension_id) {
  BLUETOOTH_LOG(DEBUG) << "CleanUpForExtension: " << extension_id;
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  RemovePairingDelegate(extension_id);

  auto pre_set_iter = pre_set_filter_map_.find(extension_id);
  if (pre_set_iter != pre_set_filter_map_.end()) {
    delete pre_set_iter->second;
    pre_set_filter_map_.erase(pre_set_iter);
  }

  // Remove any discovery session initiated by the extension.
  auto session_iter = discovery_session_map_.find(extension_id);
  if (session_iter == discovery_session_map_.end())
    return;

  // discovery_session_map.erase() should happen before
  // delete session_iter->second, because deleting the
  // BluetoothDiscoverySession object may trigger a chain reaction
  // (see http://crbug.com/40515577#c9) which will modify
  // discovery_session_map_ itself.
  device::BluetoothDiscoverySession* discovery_session = session_iter->second;
  discovery_session_map_.erase(session_iter);
  delete discovery_session;
}

void BluetoothEventRouter::CleanUpAllExtensions() {
  BLUETOOTH_LOG(DEBUG) << "CleanUpAllExtensions";

  for (auto& it : pre_set_filter_map_)
    delete it.second;
  pre_set_filter_map_.clear();

  for (auto& it : discovery_session_map_) {
    BLUETOOTH_LOG(DEBUG) << "Clean up Discovery Session: " << it.first;
    delete it.second;
  }
  discovery_session_map_.clear();

  auto pairing_iter = pairing_delegate_map_.begin();
  while (pairing_iter != pairing_delegate_map_.end())
    RemovePairingDelegate(pairing_iter++->first);
}

void BluetoothEventRouter::OnStartDiscoverySession(
    const ExtensionId& extension_id,
    base::OnceClosure callback,
    std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
  BLUETOOTH_LOG(EVENT) << "OnStartDiscoverySession: " << extension_id;
  // Clean up any existing session instance for the extension.
  auto iter = discovery_session_map_.find(extension_id);
  if (iter != discovery_session_map_.end())
    delete iter->second;
  discovery_session_map_[extension_id] = discovery_session.release();
  std::move(callback).Run();
}

void BluetoothEventRouter::OnExtensionHostDestroyed(
    content::BrowserContext* browser_context,
    ExtensionHost* host) {
  // The BluetoothEventRouter has its own context in incognito, so check
  // the context.
  if (browser_context != browser_context_)
    return;

  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  BLUETOOTH_LOG(DEBUG) << "Host Destroyed: " << host->extension_id();
  CleanUpForExtension(host->extension_id());
}

void BluetoothEventRouter::OnExtensionUnloaded(
    content::BrowserContext* browser_context,
    const Extension* extension,
    UnloadedExtensionReason reason) {
  CleanUpForExtension(extension->id());
}

}  // namespace extensions
