// Copyright (c) 2012 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 "device/bluetooth/bluetooth_device_win.h"

#include <string>
#include <unordered_map>

#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/stringprintf.h"
#include "device/bluetooth/bluetooth_adapter_win.h"
#include "device/bluetooth/bluetooth_remote_gatt_service_win.h"
#include "device/bluetooth/bluetooth_service_record_win.h"
#include "device/bluetooth/bluetooth_socket_thread.h"
#include "device/bluetooth/bluetooth_socket_win.h"
#include "device/bluetooth/bluetooth_task_manager_win.h"
#include "device/bluetooth/bluetooth_uuid.h"

namespace {

const char kApiUnavailable[] = "This API is not implemented on this platform.";

}  // namespace

namespace device {

BluetoothDeviceWin::BluetoothDeviceWin(
    BluetoothAdapterWin* adapter,
    const BluetoothTaskManagerWin::DeviceState& device_state,
    scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
    scoped_refptr<BluetoothSocketThread> socket_thread)
    : BluetoothDevice(adapter),
      ui_task_runner_(std::move(ui_task_runner)),
      socket_thread_(std::move(socket_thread)) {
  Update(device_state);
}

BluetoothDeviceWin::~BluetoothDeviceWin() {
  // Explicitly take and erase GATT services one by one to ensure that calling
  // GetGattService on removed service in GattServiceRemoved returns null.
  std::vector<std::string> service_keys;
  for (const auto& gatt_service : gatt_services_) {
    service_keys.push_back(gatt_service.first);
  }
  for (const auto& key : service_keys) {
    std::unique_ptr<BluetoothRemoteGattService> service =
        std::move(gatt_services_[key]);
    gatt_services_.erase(key);
  }
}

uint32_t BluetoothDeviceWin::GetBluetoothClass() const {
  return bluetooth_class_;
}

std::string BluetoothDeviceWin::GetAddress() const {
  return address_;
}

BluetoothDevice::VendorIDSource
BluetoothDeviceWin::GetVendorIDSource() const {
  return VENDOR_ID_UNKNOWN;
}

uint16_t BluetoothDeviceWin::GetVendorID() const {
  return 0;
}

uint16_t BluetoothDeviceWin::GetProductID() const {
  return 0;
}

uint16_t BluetoothDeviceWin::GetDeviceID() const {
  return 0;
}

uint16_t BluetoothDeviceWin::GetAppearance() const {
  // TODO(crbug.com/588083): Implementing GetAppearance()
  // on mac, win, and android platforms for chrome
  NOTIMPLEMENTED();
  return 0;
}

base::Optional<std::string> BluetoothDeviceWin::GetName() const {
  return name_;
}

bool BluetoothDeviceWin::IsPaired() const {
  return paired_;
}

bool BluetoothDeviceWin::IsConnected() const {
  return connected_;
}

bool BluetoothDeviceWin::IsGattConnected() const {
  return gatt_connected_;
}

bool BluetoothDeviceWin::IsConnectable() const {
  return false;
}

bool BluetoothDeviceWin::IsConnecting() const {
  return false;
}

BluetoothDevice::UUIDSet BluetoothDeviceWin::GetUUIDs() const {
  return uuids_;
}

base::Optional<int8_t> BluetoothDeviceWin::GetInquiryRSSI() const {
  // In windows, we can only get connected devices and connected
  // devices don't have an Inquiry RSSI.
  return base::nullopt;
}

base::Optional<int8_t> BluetoothDeviceWin::GetInquiryTxPower() const {
  // In windows, we can only get connected devices and connected
  // devices don't have an Inquiry Tx Power.
  return base::nullopt;
}

bool BluetoothDeviceWin::ExpectingPinCode() const {
  NOTIMPLEMENTED();
  return false;
}

bool BluetoothDeviceWin::ExpectingPasskey() const {
  NOTIMPLEMENTED();
  return false;
}

bool BluetoothDeviceWin::ExpectingConfirmation() const {
  NOTIMPLEMENTED();
  return false;
}

void BluetoothDeviceWin::GetConnectionInfo(
    const ConnectionInfoCallback& callback) {
  NOTIMPLEMENTED();
  callback.Run(ConnectionInfo());
}

void BluetoothDeviceWin::SetConnectionLatency(
    ConnectionLatency connection_latency,
    const base::Closure& callback,
    const ErrorCallback& error_callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::Connect(
    PairingDelegate* pairing_delegate,
    const base::Closure& callback,
    const ConnectErrorCallback& error_callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::SetPinCode(const std::string& pincode) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::SetPasskey(uint32_t passkey) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::ConfirmPairing() {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::RejectPairing() {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::CancelPairing() {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::Disconnect(
    const base::Closure& callback,
    const ErrorCallback& error_callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::Forget(const base::Closure& callback,
                                const ErrorCallback& error_callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWin::ConnectToService(
    const BluetoothUUID& uuid,
    const ConnectToServiceCallback& callback,
    const ConnectToServiceErrorCallback& error_callback) {
  scoped_refptr<BluetoothSocketWin> socket(
      BluetoothSocketWin::CreateBluetoothSocket(
          ui_task_runner_, socket_thread_));
  socket->Connect(this, uuid, base::Bind(callback, socket), error_callback);
}

void BluetoothDeviceWin::ConnectToServiceInsecurely(
    const BluetoothUUID& uuid,
    const ConnectToServiceCallback& callback,
    const ConnectToServiceErrorCallback& error_callback) {
  error_callback.Run(kApiUnavailable);
}

const BluetoothServiceRecordWin* BluetoothDeviceWin::GetServiceRecord(
    const device::BluetoothUUID& uuid) const {
  for (const auto& record : service_record_list_)
    if (record->uuid() == uuid)
      return record.get();

  return nullptr;
}

bool BluetoothDeviceWin::IsEqual(
    const BluetoothTaskManagerWin::DeviceState& device_state) {
  if (address_ != device_state.address || name_ != device_state.name ||
      bluetooth_class_ != device_state.bluetooth_class ||
      visible_ != device_state.visible ||
      connected_ != device_state.connected ||
      gatt_connected_ == device_state.is_bluetooth_classic() ||
      paired_ != device_state.authenticated) {
    return false;
  }

  // Checks service collection
  UUIDSet new_services;
  std::unordered_map<std::string, std::unique_ptr<BluetoothServiceRecordWin>>
      new_service_records;
  for (auto iter = device_state.service_record_states.begin();
       iter != device_state.service_record_states.end(); ++iter) {
    auto service_record = std::make_unique<BluetoothServiceRecordWin>(
        address_, (*iter)->name, (*iter)->sdp_bytes, (*iter)->gatt_uuid);
    new_services.insert(service_record->uuid());
    new_service_records[service_record->uuid().canonical_value()] =
        std::move(service_record);
  }

  // Check that no new services have been added or removed.
  if (uuids_ != new_services) {
    return false;
  }

  for (const auto& service_record : service_record_list_) {
    BluetoothServiceRecordWin* new_service_record =
        new_service_records[service_record->uuid().canonical_value()].get();
    if (!service_record->IsEqual(*new_service_record))
      return false;
  }
  return true;
}

void BluetoothDeviceWin::Update(
    const BluetoothTaskManagerWin::DeviceState& device_state) {
  address_ = device_state.address;
  // Note: Callers are responsible for providing a canonicalized address.
  DCHECK_EQ(address_, BluetoothDevice::CanonicalizeAddress(address_));
  name_ = device_state.name;
  bluetooth_class_ = device_state.bluetooth_class;
  visible_ = device_state.visible;
  connected_ = device_state.connected;
  // If a BLE device is not GATT connected, Windows will automatically
  // reconnect.
  gatt_connected_ = !device_state.is_bluetooth_classic();
  paired_ = device_state.authenticated;
  UpdateServices(device_state);
}

void BluetoothDeviceWin::GattServiceDiscoveryComplete(
    BluetoothRemoteGattServiceWin* service) {
  DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
  DCHECK(BluetoothDeviceWin::IsGattServiceDiscovered(
      service->GetUUID(), service->GetAttributeHandle()));

  discovery_completed_included_services_.insert(
      {service->GetUUID(), service->GetAttributeHandle()});
  if (discovery_completed_included_services_.size() != gatt_services_.size())
    return;

  SetGattServicesDiscoveryComplete(true);
  adapter_->NotifyGattServicesDiscovered(this);
}

void BluetoothDeviceWin::CreateGattConnectionImpl() {
  // Windows will create the Gatt connection as needed.  See:
  // https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/gatt-client#connecting-to-the-device
}

void BluetoothDeviceWin::DisconnectGatt() {
  // On Windows, the adapter cannot force a disconnection.
}

void BluetoothDeviceWin::SetVisible(bool visible) {
  visible_ = visible;
}

void BluetoothDeviceWin::UpdateServices(
    const BluetoothTaskManagerWin::DeviceState& device_state) {
  uuids_.clear();
  service_record_list_.clear();

  for (const auto& record_state : device_state.service_record_states) {
    auto service_record = std::make_unique<BluetoothServiceRecordWin>(
        device_state.address, record_state->name, record_state->sdp_bytes,
        record_state->gatt_uuid);
    uuids_.insert(service_record->uuid());
    service_record_list_.push_back(std::move(service_record));
  }

  if (!device_state.is_bluetooth_classic())
    UpdateGattServices(device_state.service_record_states);
}

bool BluetoothDeviceWin::IsGattServiceDiscovered(const BluetoothUUID& uuid,
                                                 uint16_t attribute_handle) {
  for (const auto& gatt_service : gatt_services_) {
    uint16_t it_att_handle =
        static_cast<BluetoothRemoteGattServiceWin*>(gatt_service.second.get())
            ->GetAttributeHandle();
    BluetoothUUID it_uuid = gatt_service.second->GetUUID();
    if (attribute_handle == it_att_handle && uuid == it_uuid) {
      return true;
    }
  }
  return false;
}

bool BluetoothDeviceWin::DoesGattServiceExist(
    const std::vector<std::unique_ptr<
        BluetoothTaskManagerWin::ServiceRecordState>>& service_state,
    BluetoothRemoteGattService* service) {
  uint16_t attribute_handle =
      static_cast<BluetoothRemoteGattServiceWin*>(service)
          ->GetAttributeHandle();
  BluetoothUUID uuid = service->GetUUID();
  for (const auto& record_state : service_state) {
    if (attribute_handle == record_state->attribute_handle &&
        uuid == record_state->gatt_uuid) {
      return true;
    }
  }
  return false;
}

void BluetoothDeviceWin::UpdateGattServices(
    const std::vector<
        std::unique_ptr<BluetoothTaskManagerWin::ServiceRecordState>>&
        service_state) {
  // First, remove no longer existent GATT service.
  {
    std::vector<std::string> to_be_removed_services;
    for (const auto& gatt_service : gatt_services_) {
      if (!DoesGattServiceExist(service_state, gatt_service.second.get())) {
        to_be_removed_services.push_back(gatt_service.first);
      }
    }
    for (const auto& service : to_be_removed_services) {
      std::unique_ptr<BluetoothRemoteGattService> service_ptr =
          std::move(gatt_services_[service]);
      gatt_services_.erase(service);
    }
    // Update previously discovered services.
    for (const auto& gatt_service : gatt_services_) {
      static_cast<BluetoothRemoteGattServiceWin*>(gatt_service.second.get())
          ->Update();
    }
  }

  // Return if no new services have been added.
  if (gatt_services_.size() == service_state.size())
    return;

  // Add new services.
  for (const auto& record_state : service_state) {
    if (!IsGattServiceDiscovered(record_state->gatt_uuid,
                                 record_state->attribute_handle)) {
      BluetoothRemoteGattServiceWin* primary_service =
          new BluetoothRemoteGattServiceWin(
              this, record_state->path, record_state->gatt_uuid,
              record_state->attribute_handle, true, nullptr, ui_task_runner_);
      gatt_services_[primary_service->GetIdentifier()] =
          base::WrapUnique(primary_service);
      adapter_->NotifyGattServiceAdded(primary_service);
    }
  }
}

}  // namespace device
