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

#include "device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.h"

#include <memory>

#include "base/containers/to_vector.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "components/device_event_log/device_event_log.h"
#include "device/bluetooth/floss/bluetooth_adapter_floss.h"
#include "device/bluetooth/floss/bluetooth_remote_gatt_characteristic_floss.h"
#include "device/bluetooth/floss/bluetooth_remote_gatt_service_floss.h"
#include "device/bluetooth/floss/floss_dbus_manager.h"
#include "device/bluetooth/floss/floss_gatt_manager_client.h"

namespace floss {

const int kGattTimeoutMs = 2000;

// static
std::unique_ptr<BluetoothRemoteGattDescriptorFloss>
BluetoothRemoteGattDescriptorFloss::Create(
    BluetoothRemoteGattServiceFloss* service,
    BluetoothRemoteGattCharacteristicFloss* characteristic,
    GattDescriptor* descriptor) {
  return base::WrapUnique(new BluetoothRemoteGattDescriptorFloss(
      service, characteristic, descriptor));
}

BluetoothRemoteGattDescriptorFloss::BluetoothRemoteGattDescriptorFloss(
    BluetoothRemoteGattServiceFloss* service,
    BluetoothRemoteGattCharacteristicFloss* characteristic,
    GattDescriptor* descriptor)
    : characteristic_(characteristic),
      descriptor_(descriptor),
      service_(service) {
  DCHECK(service);
  DCHECK(characteristic);
  DCHECK(descriptor);

  service_->AddObserverForHandle(descriptor_->instance_id, this);
}

BluetoothRemoteGattDescriptorFloss::~BluetoothRemoteGattDescriptorFloss() {
  service_->RemoveObserverForHandle(descriptor_->instance_id);
}

std::string BluetoothRemoteGattDescriptorFloss::GetIdentifier() const {
  return base::StringPrintf("%s/%04x", characteristic_->GetIdentifier().c_str(),
                            descriptor_->instance_id);
}

device::BluetoothUUID BluetoothRemoteGattDescriptorFloss::GetUUID() const {
  return descriptor_->uuid;
}

const std::vector<uint8_t>& BluetoothRemoteGattDescriptorFloss::GetValue()
    const {
  return cached_data_;
}

device::BluetoothRemoteGattCharacteristic*
BluetoothRemoteGattDescriptorFloss::GetCharacteristic() const {
  return static_cast<device::BluetoothRemoteGattCharacteristic*>(
      characteristic_.get());
}

device::BluetoothRemoteGattCharacteristic::Permissions
BluetoothRemoteGattDescriptorFloss::GetPermissions() const {
  const auto& [props, perms] =
      BluetoothGattCharacteristicFloss::ConvertPropsAndPermsFromFloss(
          /*properties=*/0, descriptor_->permissions);
  return perms;
}

void BluetoothRemoteGattDescriptorFloss::ReadRemoteDescriptor(
    ValueCallback callback) {
  DCHECK_GE(num_of_reads_in_progress_, 0);
  ++num_of_reads_in_progress_;

  AuthRequired auth = characteristic_->GetAuthForRead();

  FlossDBusManager::Get()->GetGattManagerClient()->ReadDescriptor(
      base::BindOnce(&BluetoothRemoteGattDescriptorFloss::OnReadDescriptor,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
      service_->GetDevice()->GetAddress(), descriptor_->instance_id, auth);
}

void BluetoothRemoteGattDescriptorFloss::WriteRemoteDescriptor(
    base::span<const uint8_t> new_value,
    base::OnceClosure callback,
    ErrorCallback error_callback) {
  AuthRequired auth = characteristic_->GetAuthForWrite();

  FlossDBusManager::Get()->GetGattManagerClient()->WriteDescriptor(
      base::BindOnce(&BluetoothRemoteGattDescriptorFloss::OnWriteDescriptor,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                     std::move(error_callback), base::ToVector(new_value)),
      service_->GetDevice()->GetAddress(), descriptor_->instance_id, auth,
      new_value);
}

void BluetoothRemoteGattDescriptorFloss::GattDescriptorRead(
    std::string address,
    GattStatus status,
    int32_t handle,
    const std::vector<uint8_t>& data) {
  if (handle != descriptor_->instance_id ||
      address != service_->GetDevice()->GetAddress()) {
    return;
  }

  --num_of_reads_in_progress_;
  DCHECK_GE(num_of_reads_in_progress_, 0);

  if (status == GattStatus::kSuccess) {
    cached_data_ = data;

    std::move(pending_read_callback_)
        .Run(/*error_code=*/std::nullopt, cached_data_);
  } else {
    std::move(pending_read_callback_)
        .Run(BluetoothGattServiceFloss::GattStatusToServiceError(status), {});
  }
}

void BluetoothRemoteGattDescriptorFloss::GattDescriptorWrite(
    std::string address,
    GattStatus status,
    int32_t handle) {
  if (handle != descriptor_->instance_id ||
      address != service_->GetDevice()->GetAddress()) {
    return;
  }

  // Only handle if there is a write callback pending.
  auto [callback, error_callback, data] = std::move(pending_write_callbacks_);
  if (!callback) {
    return;
  }

  if (status == GattStatus::kSuccess) {
    cached_data_ = data;

    std::move(callback).Run();
  } else {
    std::move(error_callback)
        .Run(BluetoothGattServiceFloss::GattStatusToServiceError(status));
  }
}

void BluetoothRemoteGattDescriptorFloss::GattNotify(
    std::string address,
    int32_t handle,
    const std::vector<uint8_t>& data) {
  if (handle != descriptor_->instance_id ||
      address != service_->GetDevice()->GetAddress()) {
    return;
  }

  cached_data_ = data;
  NotifyValueChanged();
}

void BluetoothRemoteGattDescriptorFloss::OnReadDescriptor(
    ValueCallback callback,
    DBusResult<Void> result) {
  if (!result.has_value()) {
    --num_of_reads_in_progress_;
    DCHECK_GE(num_of_reads_in_progress_, 0);

    std::move(callback).Run(BluetoothGattServiceFloss::GattErrorCode::kFailed,
                            {});
    return;
  }

  pending_read_callback_ = std::move(callback);
}

void BluetoothRemoteGattDescriptorFloss::OnWriteDescriptor(
    base::OnceClosure callback,
    ErrorCallback error_callback,
    std::vector<uint8_t> data,
    DBusResult<Void> result) {
  if (!result.has_value()) {
    std::move(error_callback)
        .Run(BluetoothGattServiceFloss::GattErrorCode::kFailed);
    return;
  }

  pending_write_callbacks_ = std::make_tuple(
      std::move(callback), std::move(error_callback), std::move(data));

  // Ensure callbacks don't get dropped if no |GattDescriptorWrite| received.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&BluetoothRemoteGattDescriptorFloss::OnWriteTimeout,
                     weak_ptr_factory_.GetWeakPtr()),
      base::Milliseconds(kGattTimeoutMs));
}

void BluetoothRemoteGattDescriptorFloss::OnWriteTimeout() {
  if (std::get<0>(pending_write_callbacks_)) {
    BLUETOOTH_LOG(ERROR)
        << "Timeout waiting for GattDescriptorWrite for Device "
        << service_->GetDevice()->GetAddress();
    GattDescriptorWrite(service_->GetDevice()->GetAddress(), GattStatus::kError,
                        descriptor_->instance_id);
  }
}

void BluetoothRemoteGattDescriptorFloss::NotifyValueChanged() {
  DCHECK(service_->GetAdapter());

  service_->GetAdapter()->NotifyGattDescriptorValueChanged(this, cached_data_);
}

}  // namespace floss
