
#include "adc_sensor.h"
#ifdef USE_ZEPHYR
#include "esphome/core/log.h"

#include "hal/nrf_saadc.h"

namespace esphome::adc {

static const char *const TAG = "adc.zephyr";

void ADCSensor::setup() {
  if (!adc_is_ready_dt(this->channel_)) {
    ESP_LOGE(TAG, "ADC controller device %s not ready", this->channel_->dev->name);
    return;
  }

  auto err = adc_channel_setup_dt(this->channel_);
  if (err < 0) {
    ESP_LOGE(TAG, "Could not setup channel %s (%d)", this->channel_->dev->name, err);
    return;
  }
}

#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
static const LogString *gain_to_str(enum adc_gain gain) {
  switch (gain) {
    case ADC_GAIN_1_6:
      return LOG_STR("1/6");
    case ADC_GAIN_1_5:
      return LOG_STR("1/5");
    case ADC_GAIN_1_4:
      return LOG_STR("1/4");
    case ADC_GAIN_1_3:
      return LOG_STR("1/3");
    case ADC_GAIN_2_5:
      return LOG_STR("2/5");
    case ADC_GAIN_1_2:
      return LOG_STR("1/2");
    case ADC_GAIN_2_3:
      return LOG_STR("2/3");
    case ADC_GAIN_4_5:
      return LOG_STR("4/5");
    case ADC_GAIN_1:
      return LOG_STR("1");
    case ADC_GAIN_2:
      return LOG_STR("2");
    case ADC_GAIN_3:
      return LOG_STR("3");
    case ADC_GAIN_4:
      return LOG_STR("4");
    case ADC_GAIN_6:
      return LOG_STR("6");
    case ADC_GAIN_8:
      return LOG_STR("8");
    case ADC_GAIN_12:
      return LOG_STR("12");
    case ADC_GAIN_16:
      return LOG_STR("16");
    case ADC_GAIN_24:
      return LOG_STR("24");
    case ADC_GAIN_32:
      return LOG_STR("32");
    case ADC_GAIN_64:
      return LOG_STR("64");
    case ADC_GAIN_128:
      return LOG_STR("128");
  }
  return LOG_STR("undefined gain");
}

static const LogString *reference_to_str(enum adc_reference reference) {
  switch (reference) {
    case ADC_REF_VDD_1:
      return LOG_STR("VDD");
    case ADC_REF_VDD_1_2:
      return LOG_STR("VDD/2");
    case ADC_REF_VDD_1_3:
      return LOG_STR("VDD/3");
    case ADC_REF_VDD_1_4:
      return LOG_STR("VDD/4");
    case ADC_REF_INTERNAL:
      return LOG_STR("INTERNAL");
    case ADC_REF_EXTERNAL0:
      return LOG_STR("External, input 0");
    case ADC_REF_EXTERNAL1:
      return LOG_STR("External, input 1");
  }
  return LOG_STR("undefined reference");
}

static const LogString *input_to_str(uint8_t input) {
  switch (input) {
    case NRF_SAADC_INPUT_AIN0:
      return LOG_STR("AIN0");
    case NRF_SAADC_INPUT_AIN1:
      return LOG_STR("AIN1");
    case NRF_SAADC_INPUT_AIN2:
      return LOG_STR("AIN2");
    case NRF_SAADC_INPUT_AIN3:
      return LOG_STR("AIN3");
    case NRF_SAADC_INPUT_AIN4:
      return LOG_STR("AIN4");
    case NRF_SAADC_INPUT_AIN5:
      return LOG_STR("AIN5");
    case NRF_SAADC_INPUT_AIN6:
      return LOG_STR("AIN6");
    case NRF_SAADC_INPUT_AIN7:
      return LOG_STR("AIN7");
    case NRF_SAADC_INPUT_VDD:
      return LOG_STR("VDD");
    case NRF_SAADC_INPUT_VDDHDIV5:
      return LOG_STR("VDDHDIV5");
  }
  return LOG_STR("undefined input");
}
#endif  // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE

void ADCSensor::dump_config() {
  LOG_SENSOR("", "ADC Sensor", this);
  LOG_PIN("  Pin: ", this->pin_);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
  ESP_LOGV(TAG,
           "  Name: %s\n"
           "  Channel: %d\n"
           "  vref_mv: %d\n"
           "  Resolution %d\n"
           "  Oversampling %d",
           this->channel_->dev->name, this->channel_->channel_id, this->channel_->vref_mv, this->channel_->resolution,
           this->channel_->oversampling);

  ESP_LOGV(TAG,
           "  Gain: %s\n"
           "  reference: %s\n"
           "  acquisition_time: %d\n"
           "  differential %s",
           LOG_STR_ARG(gain_to_str(this->channel_->channel_cfg.gain)),
           LOG_STR_ARG(reference_to_str(this->channel_->channel_cfg.reference)),
           this->channel_->channel_cfg.acquisition_time, YESNO(this->channel_->channel_cfg.differential));
  if (this->channel_->channel_cfg.differential) {
    ESP_LOGV(TAG,
             "  Positive: %s\n"
             "  Negative: %s",
             LOG_STR_ARG(input_to_str(this->channel_->channel_cfg.input_positive)),
             LOG_STR_ARG(input_to_str(this->channel_->channel_cfg.input_negative)));
  } else {
    ESP_LOGV(TAG, "  Positive: %s", LOG_STR_ARG(input_to_str(this->channel_->channel_cfg.input_positive)));
  }
#endif

  LOG_UPDATE_INTERVAL(this);
}

float ADCSensor::sample() {
  auto aggr = Aggregator<int32_t>(this->sampling_mode_);
  int err;
  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
    int16_t buf = 0;
    struct adc_sequence sequence = {
        .buffer = &buf,
        /* buffer size in bytes, not number of samples */
        .buffer_size = sizeof(buf),
    };
    int32_t val_raw;

    err = adc_sequence_init_dt(this->channel_, &sequence);
    if (err < 0) {
      ESP_LOGE(TAG, "Could sequence init %s (%d)", this->channel_->dev->name, err);
      return 0.0;
    }

    err = adc_read(this->channel_->dev, &sequence);
    if (err < 0) {
      ESP_LOGE(TAG, "Could not read %s (%d)", this->channel_->dev->name, err);
      return 0.0;
    }

    val_raw = (int32_t) buf;
    if (!this->channel_->channel_cfg.differential) {
      // https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/0ed4d9ffc674ae407be7cacf5696a02f5e789861/cores/nRF5/wiring_analog_nRF52.c#L222
      if (val_raw < 0) {
        val_raw = 0;
      }
    }
    aggr.add_sample(val_raw);
  }

  int32_t val_mv = aggr.aggregate();

  if (this->output_raw_) {
    return val_mv;
  }

  err = adc_raw_to_millivolts_dt(this->channel_, &val_mv);
  /* conversion to mV may not be supported, skip if not */
  if (err < 0) {
    ESP_LOGE(TAG, "Value in mV not available %s (%d)", this->channel_->dev->name, err);
    return 0.0;
  }

  return val_mv / 1000.0f;
}

}  // namespace esphome::adc
#endif
