#include "ble_characteristic.h"
#include "ble_client_base.h"
#include "ble_service.h"

#include "esphome/core/log.h"

#ifdef USE_ESP32
#ifdef USE_ESP32_BLE_DEVICE

namespace esphome::esp32_ble_client {

static const char *const TAG = "esp32_ble_client";

BLECharacteristic::~BLECharacteristic() {
  for (auto &desc : this->descriptors)
    delete desc;  // NOLINT(cppcoreguidelines-owning-memory)
}

void BLECharacteristic::release_descriptors() {
  this->parsed = false;
  for (auto &desc : this->descriptors)
    delete desc;  // NOLINT(cppcoreguidelines-owning-memory)
  this->descriptors.clear();
}

void BLECharacteristic::parse_descriptors() {
  this->parsed = true;
  uint16_t offset = 0;
  esp_gattc_descr_elem_t result;

  while (true) {
    uint16_t count = 1;
    esp_gatt_status_t status =
        esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(),
                                    this->handle, &result, &count, offset);
    if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
      break;
    }
    if (status != ESP_GATT_OK) {
      ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d",
               this->service->client->get_connection_index(), this->service->client->address_str(), status);
      break;
    }
    if (count == 0) {
      break;
    }

    BLEDescriptor *desc = new BLEDescriptor();  // NOLINT(cppcoreguidelines-owning-memory)
    desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
    desc->handle = result.handle;
    desc->characteristic = this;
    this->descriptors.push_back(desc);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
    char uuid_buf[espbt::UUID_STR_LEN];
    desc->uuid.to_str(uuid_buf);
    ESP_LOGV(TAG, "[%d] [%s]    descriptor %s, handle 0x%x", this->service->client->get_connection_index(),
             this->service->client->address_str(), uuid_buf, desc->handle);
#endif
    offset++;
  }
}

BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
  if (!this->parsed)
    this->parse_descriptors();
  for (auto &desc : this->descriptors) {
    if (desc->uuid == uuid)
      return desc;
  }
  return nullptr;
}
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
  return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
}
BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
  if (!this->parsed)
    this->parse_descriptors();
  for (auto &desc : this->descriptors) {
    if (desc->handle == handle)
      return desc;
  }
  return nullptr;
}

esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
  auto *client = this->service->client;
  auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size,
                                         new_val, write_type, ESP_GATT_AUTH_REQ_NONE);
  if (status) {
    ESP_LOGW(TAG, "[%d] [%s] Error sending write value to BLE gattc server, status=%d",
             this->service->client->get_connection_index(), this->service->client->address_str(), status);
  }
  return status;
}

esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
  return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
}

}  // namespace esphome::esp32_ble_client

#endif  // USE_ESP32_BLE_DEVICE
#endif  // USE_ESP32
