// Copyright 2015 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 "chromeos/services/secure_channel/ble_characteristics_finder.h"

#include "chromeos/components/multidevice/logging/logging.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
#include "device/bluetooth/bluetooth_uuid.h"

using device::BluetoothAdapter;
using device::BluetoothDevice;
using device::BluetoothRemoteGattCharacteristic;
using device::BluetoothRemoteGattService;
using device::BluetoothUUID;

namespace chromeos {

namespace secure_channel {

BluetoothLowEnergyCharacteristicsFinder::
    BluetoothLowEnergyCharacteristicsFinder(
        scoped_refptr<BluetoothAdapter> adapter,
        BluetoothDevice* device,
        const RemoteAttribute& remote_service,
        const RemoteAttribute& to_peripheral_char,
        const RemoteAttribute& from_peripheral_char,
        const SuccessCallback& success_callback,
        const ErrorCallback& error_callback)
    : adapter_(adapter),
      bluetooth_device_(device),
      remote_service_(remote_service),
      to_peripheral_char_(to_peripheral_char),
      from_peripheral_char_(from_peripheral_char),
      success_callback_(success_callback),
      error_callback_(error_callback) {
  adapter_->AddObserver(this);
  ScanRemoteCharacteristics(device, remote_service_.uuid);
}

BluetoothLowEnergyCharacteristicsFinder::
    BluetoothLowEnergyCharacteristicsFinder() {}

BluetoothLowEnergyCharacteristicsFinder::
    ~BluetoothLowEnergyCharacteristicsFinder() {
  if (adapter_) {
    adapter_->RemoveObserver(this);
  }
}

void BluetoothLowEnergyCharacteristicsFinder::GattCharacteristicAdded(
    BluetoothAdapter* adapter,
    BluetoothRemoteGattCharacteristic* characteristic) {
  // Ignore events about other devices.
  if (characteristic->GetService()->GetDevice() != bluetooth_device_)
    return;
  HandleCharacteristicUpdate(characteristic);
}

void BluetoothLowEnergyCharacteristicsFinder::GattDiscoveryCompleteForService(
    BluetoothAdapter* adapter,
    BluetoothRemoteGattService* service) {
  if (!service || service->GetUUID() != remote_service_.uuid)
    return;

  OnCharacteristicDiscoveryEnded(service->GetDevice());
}

void BluetoothLowEnergyCharacteristicsFinder::GattServicesDiscovered(
    BluetoothAdapter* adapter,
    BluetoothDevice* device) {
  OnCharacteristicDiscoveryEnded(device);
}

void BluetoothLowEnergyCharacteristicsFinder::OnCharacteristicDiscoveryEnded(
    BluetoothDevice* device) {
  // Ignore events about other devices.
  if (device != bluetooth_device_)
    return;

  if (!to_peripheral_char_.id.empty() && !from_peripheral_char_.id.empty())
    return;

  if (has_error_callback_been_invoked_)
    return;
  // If all GATT services have been discovered and we haven't found the
  // characteristics we are looking for, call the error callback.
  has_error_callback_been_invoked_ = true;
  error_callback_.Run(to_peripheral_char_, from_peripheral_char_);
}

void BluetoothLowEnergyCharacteristicsFinder::ScanRemoteCharacteristics(
    BluetoothDevice* device,
    const BluetoothUUID& service_uuid) {
  if (!device)
    return;

  for (const auto* service : device->GetGattServices()) {
    if (service->GetUUID() != service_uuid)
      continue;

    // Right service found, now scaning its characteristics.
    std::vector<device::BluetoothRemoteGattCharacteristic*> characteristics =
        service->GetCharacteristics();
    for (auto* characteristic : characteristics) {
      if (HandleCharacteristicUpdate(characteristic))
        return;
    }
    break;
  }
}

bool BluetoothLowEnergyCharacteristicsFinder::HandleCharacteristicUpdate(
    BluetoothRemoteGattCharacteristic* characteristic) {
  UpdateCharacteristicsStatus(characteristic);

  if (to_peripheral_char_.id.empty() || from_peripheral_char_.id.empty())
    return false;

  success_callback_.Run(remote_service_, to_peripheral_char_,
                        from_peripheral_char_);
  return true;
}

void BluetoothLowEnergyCharacteristicsFinder::UpdateCharacteristicsStatus(
    BluetoothRemoteGattCharacteristic* characteristic) {
  if (!characteristic ||
      characteristic->GetService()->GetUUID() != remote_service_.uuid) {
    return;
  }

  BluetoothUUID uuid = characteristic->GetUUID();
  if (to_peripheral_char_.uuid == uuid)
    to_peripheral_char_.id = characteristic->GetIdentifier();
  if (from_peripheral_char_.uuid == uuid)
    from_peripheral_char_.id = characteristic->GetIdentifier();

  BluetoothRemoteGattService* service = characteristic->GetService();
  if (service)
    remote_service_.id = service->GetIdentifier();
}

}  // namespace secure_channel

}  // namespace chromeos
