#include "ble_service.h"
#include "ble_server.h"
#include "esphome/core/log.h"

#ifdef USE_ESP32

namespace esphome {
namespace esp32_ble_server {

static const char *const TAG = "esp32_ble_server.service";

BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise)
    : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id), advertise_(advertise) {}

BLEService::~BLEService() {
  for (auto &chr : this->characteristics_)
    delete chr;  // NOLINT(cppcoreguidelines-owning-memory)
}

BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) {
  for (auto *chr : this->characteristics_) {
    if (chr->get_uuid() == uuid)
      return chr;
  }
  return nullptr;
}

BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
  return this->get_characteristic(ESPBTUUID::from_uint16(uuid));
}
BLECharacteristic *BLEService::create_characteristic(uint16_t uuid, esp_gatt_char_prop_t properties) {
  return create_characteristic(ESPBTUUID::from_uint16(uuid), properties);
}
BLECharacteristic *BLEService::create_characteristic(const std::string &uuid, esp_gatt_char_prop_t properties) {
  return create_characteristic(ESPBTUUID::from_raw(uuid), properties);
}
BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties) {
  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
  BLECharacteristic *characteristic = new BLECharacteristic(uuid, properties);
  this->characteristics_.push_back(characteristic);
  return characteristic;
}

void BLEService::do_create(BLEServer *server) {
  this->server_ = server;

  esp_gatt_srvc_id_t srvc_id;
  srvc_id.is_primary = true;
  srvc_id.id.inst_id = this->inst_id_;
  srvc_id.id.uuid = this->uuid_.get_uuid();

  esp_err_t err = esp_ble_gatts_create_service(server->get_gatts_if(), &srvc_id, this->num_handles_);
  if (err != ESP_OK) {
    ESP_LOGE(TAG, "esp_ble_gatts_create_service failed: %d", err);
    this->state_ = FAILED;
    return;
  }
  this->state_ = CREATING;
}

void BLEService::do_delete() {
  if (this->state_ == DELETING || this->state_ == DELETED)
    return;
  this->state_ = DELETING;
  this->created_characteristic_count_ = 0;
  this->last_created_characteristic_ = nullptr;
  // Call all characteristics to delete
  for (auto *characteristic : this->characteristics_)
    characteristic->do_delete();
  this->stop_();
  esp_err_t err = esp_ble_gatts_delete_service(this->handle_);
  if (err != ESP_OK) {
    ESP_LOGE(TAG, "esp_ble_gatts_delete_service failed: %d", err);
    return;
  }
}

bool BLEService::do_create_characteristics_() {
  if (this->created_characteristic_count_ >= this->characteristics_.size() &&
      (this->last_created_characteristic_ == nullptr || this->last_created_characteristic_->is_created()))
    return false;  // Signifies there are no characteristics, or they are all finished being created.

  if (this->last_created_characteristic_ != nullptr && !this->last_created_characteristic_->is_created())
    return true;  // Signifies that the previous characteristic is still being created.

  auto *characteristic = this->characteristics_[this->created_characteristic_count_++];
  this->last_created_characteristic_ = characteristic;
  characteristic->do_create(this);
  return true;
}

void BLEService::start() {
  if (this->do_create_characteristics_())
    return;
  should_start_ = true;

  this->state_ = STARTING;
  esp_err_t err = esp_ble_gatts_start_service(this->handle_);
  if (err != ESP_OK) {
    ESP_LOGE(TAG, "esp_ble_gatts_start_service failed: %d", err);
    return;
  }
  if (this->advertise_)
    esp32_ble::global_ble->advertising_add_service_uuid(this->uuid_);
}

void BLEService::stop() {
  should_start_ = false;
  this->stop_();
}

void BLEService::stop_() {
  if (this->state_ == STOPPING || this->state_ == STOPPED)
    return;
  this->state_ = STOPPING;
  esp_err_t err = esp_ble_gatts_stop_service(this->handle_);
  if (err != ESP_OK) {
    ESP_LOGE(TAG, "esp_ble_gatts_stop_service failed: %d", err);
    return;
  }
  if (this->advertise_)
    esp32_ble::global_ble->advertising_remove_service_uuid(this->uuid_);
}

bool BLEService::is_failed() {
  if (this->state_ == FAILED)
    return true;
  bool failed = false;
  for (auto *characteristic : this->characteristics_)
    failed |= characteristic->is_failed();

  if (failed)
    this->state_ = FAILED;
  return this->state_ == FAILED;
}

void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
                                     esp_ble_gatts_cb_param_t *param) {
  switch (event) {
    case ESP_GATTS_CREATE_EVT: {
      if (this->uuid_ == ESPBTUUID::from_uuid(param->create.service_id.id.uuid) &&
          this->inst_id_ == param->create.service_id.id.inst_id) {
        this->handle_ = param->create.service_handle;
        this->state_ = CREATED;
        if (this->should_start_)
          this->start();
      }
      break;
    }
    case ESP_GATTS_DELETE_EVT:
      if (param->del.service_handle == this->handle_) {
        this->state_ = DELETED;
      }
      break;
    case ESP_GATTS_START_EVT: {
      if (param->start.service_handle == this->handle_) {
        this->state_ = RUNNING;
      }
      break;
    }
    case ESP_GATTS_STOP_EVT: {
      if (param->start.service_handle == this->handle_) {
        this->state_ = STOPPED;
      }
      break;
    }
    default:
      break;
  }

  for (auto *characteristic : this->characteristics_) {
    characteristic->gatts_event_handler(event, gatts_if, param);
  }
}

}  // namespace esp32_ble_server
}  // namespace esphome

#endif
