#include <cmath>
#include <cstdint>

#include "bme280_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <esphome/components/sensor/sensor.h>
#include <esphome/core/component.h>

#define BME280_ERROR_WRONG_CHIP_ID "Wrong chip ID or no response"

namespace esphome::bme280_base {

static const char *const TAG = "bme280.sensor";

static const uint8_t BME280_REGISTER_DIG_T1 = 0x88;
static const uint8_t BME280_REGISTER_DIG_T2 = 0x8A;
static const uint8_t BME280_REGISTER_DIG_T3 = 0x8C;

static const uint8_t BME280_REGISTER_DIG_P1 = 0x8E;
static const uint8_t BME280_REGISTER_DIG_P2 = 0x90;
static const uint8_t BME280_REGISTER_DIG_P3 = 0x92;
static const uint8_t BME280_REGISTER_DIG_P4 = 0x94;
static const uint8_t BME280_REGISTER_DIG_P5 = 0x96;
static const uint8_t BME280_REGISTER_DIG_P6 = 0x98;
static const uint8_t BME280_REGISTER_DIG_P7 = 0x9A;
static const uint8_t BME280_REGISTER_DIG_P8 = 0x9C;
static const uint8_t BME280_REGISTER_DIG_P9 = 0x9E;

static const uint8_t BME280_REGISTER_DIG_H1 = 0xA1;
static const uint8_t BME280_REGISTER_DIG_H2 = 0xE1;
static const uint8_t BME280_REGISTER_DIG_H3 = 0xE3;
static const uint8_t BME280_REGISTER_DIG_H4 = 0xE4;
static const uint8_t BME280_REGISTER_DIG_H5 = 0xE5;
static const uint8_t BME280_REGISTER_DIG_H6 = 0xE7;

static const uint8_t BME280_REGISTER_CHIPID = 0xD0;
static const uint8_t BME280_REGISTER_RESET = 0xE0;

static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
static const uint8_t BME280_REGISTER_STATUS = 0xF3;
static const uint8_t BME280_REGISTER_CONTROL = 0xF4;
static const uint8_t BME280_REGISTER_CONFIG = 0xF5;
static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7;
static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7;
static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;

static const uint8_t BME280_MODE_FORCED = 0b01;
static const uint8_t BME280_SOFT_RESET = 0xB6;
static const uint8_t BME280_STATUS_IM_UPDATE = 0b01;

inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); }

const char *iir_filter_to_str(BME280IIRFilter filter) {  // NOLINT
  switch (filter) {
    case BME280_IIR_FILTER_OFF:
      return "OFF";
    case BME280_IIR_FILTER_2X:
      return "2x";
    case BME280_IIR_FILTER_4X:
      return "4x";
    case BME280_IIR_FILTER_8X:
      return "8x";
    case BME280_IIR_FILTER_16X:
      return "16x";
    default:
      return "UNKNOWN";
  }
}

const char *oversampling_to_str(BME280Oversampling oversampling) {  // NOLINT
  switch (oversampling) {
    case BME280_OVERSAMPLING_NONE:
      return "None";
    case BME280_OVERSAMPLING_1X:
      return "1x";
    case BME280_OVERSAMPLING_2X:
      return "2x";
    case BME280_OVERSAMPLING_4X:
      return "4x";
    case BME280_OVERSAMPLING_8X:
      return "8x";
    case BME280_OVERSAMPLING_16X:
      return "16x";
    default:
      return "UNKNOWN";
  }
}

void BME280Component::setup() {
  uint8_t chip_id = 0;

  // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
  // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component.
  if (this->is_failed()) {
    this->reset_to_construction_state();
  }

  if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
    this->error_code_ = COMMUNICATION_FAILED;
    this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
    return;
  }
  if (chip_id != 0x60) {
    this->error_code_ = WRONG_CHIP_ID;
    this->mark_failed(LOG_STR(BME280_ERROR_WRONG_CHIP_ID));
    return;
  }

  // Send a soft reset.
  if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
    this->mark_failed(LOG_STR("Reset failed"));
    return;
  }
  // Wait until the NVM data has finished loading.
  uint8_t status;
  uint8_t retry = 5;
  do {  // NOLINT
    delay(2);
    if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
      this->mark_failed(LOG_STR("Error reading status register"));
      return;
    }
  } while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
  if (status & BME280_STATUS_IM_UPDATE) {
    this->mark_failed(LOG_STR("Timeout loading NVM"));
    return;
  }

  // Read calibration
  this->calibration_.t1 = read_u16_le_(BME280_REGISTER_DIG_T1);
  this->calibration_.t2 = read_s16_le_(BME280_REGISTER_DIG_T2);
  this->calibration_.t3 = read_s16_le_(BME280_REGISTER_DIG_T3);

  this->calibration_.p1 = read_u16_le_(BME280_REGISTER_DIG_P1);
  this->calibration_.p2 = read_s16_le_(BME280_REGISTER_DIG_P2);
  this->calibration_.p3 = read_s16_le_(BME280_REGISTER_DIG_P3);
  this->calibration_.p4 = read_s16_le_(BME280_REGISTER_DIG_P4);
  this->calibration_.p5 = read_s16_le_(BME280_REGISTER_DIG_P5);
  this->calibration_.p6 = read_s16_le_(BME280_REGISTER_DIG_P6);
  this->calibration_.p7 = read_s16_le_(BME280_REGISTER_DIG_P7);
  this->calibration_.p8 = read_s16_le_(BME280_REGISTER_DIG_P8);
  this->calibration_.p9 = read_s16_le_(BME280_REGISTER_DIG_P9);

  this->calibration_.h1 = read_u8_(BME280_REGISTER_DIG_H1);
  this->calibration_.h2 = read_s16_le_(BME280_REGISTER_DIG_H2);
  this->calibration_.h3 = read_u8_(BME280_REGISTER_DIG_H3);
  // h4 and h5 are signed 12-bit values; shift left then arithmetic right shift to sign-extend
  int16_t h4_raw = read_u8_(BME280_REGISTER_DIG_H4) << 4 | (read_u8_(BME280_REGISTER_DIG_H4 + 1) & 0x0F);
  this->calibration_.h4 = static_cast<int16_t>(h4_raw << 4) >> 4;
  int16_t h5_raw = read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4);
  this->calibration_.h5 = static_cast<int16_t>(h5_raw << 4) >> 4;
  this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6);

  uint8_t humid_control_val = 0;
  if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
    this->mark_failed(LOG_STR("Read humidity control"));
    return;
  }
  humid_control_val &= ~0b00000111;
  humid_control_val |= this->humidity_oversampling_ & 0b111;
  if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
    this->mark_failed(LOG_STR("Write humidity control"));
    return;
  }

  uint8_t config_register = 0;
  if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
    this->mark_failed(LOG_STR("Read config"));
    return;
  }
  config_register &= ~0b11111100;
  config_register |= 0b101 << 5;  // 1000 ms standby time
  config_register |= (this->iir_filter_ & 0b111) << 2;
  if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
    this->mark_failed(LOG_STR("Write config"));
    return;
  }
}
void BME280Component::dump_config() {
  ESP_LOGCONFIG(TAG, "BME280:");
  switch (this->error_code_) {
    case COMMUNICATION_FAILED:
      ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
      break;
    case WRONG_CHIP_ID:
      ESP_LOGE(TAG, BME280_ERROR_WRONG_CHIP_ID);
      break;
    case NONE:
    default:
      break;
  }
  ESP_LOGCONFIG(TAG, "  IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
  LOG_UPDATE_INTERVAL(this);

  LOG_SENSOR("  ", "Temperature", this->temperature_sensor_);
  ESP_LOGCONFIG(TAG, "    Oversampling: %s", oversampling_to_str(this->temperature_oversampling_));
  LOG_SENSOR("  ", "Pressure", this->pressure_sensor_);
  ESP_LOGCONFIG(TAG, "    Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
  LOG_SENSOR("  ", "Humidity", this->humidity_sensor_);
  ESP_LOGCONFIG(TAG, "    Oversampling: %s", oversampling_to_str(this->humidity_oversampling_));
}

inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (1 << uint8_t(over_sampling)) >> 1; }

void BME280Component::update() {
  // Enable sensor
  ESP_LOGV(TAG, "Sending conversion request");
  uint8_t meas_value = 0;
  meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
  meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
  meas_value |= BME280_MODE_FORCED;
  if (!this->write_byte(BME280_REGISTER_CONTROL, meas_value)) {
    this->status_set_warning();
    return;
  }

  float meas_time = 1.5f;
  meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
  meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
  meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;

  this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
    uint8_t data[8];
    if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
      ESP_LOGW(TAG, "Error reading registers");
      this->status_set_warning();
      return;
    }
    int32_t t_fine = 0;
    float const temperature = this->read_temperature_(data, &t_fine);
    if (std::isnan(temperature)) {
      ESP_LOGW(TAG, "Invalid temperature");
      this->status_set_warning();
      return;
    }
    float const pressure = this->read_pressure_(data, t_fine);
    float const humidity = this->read_humidity_(data, t_fine);

    ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa Humidity=%.1f%%", temperature, pressure, humidity);
    if (this->temperature_sensor_ != nullptr)
      this->temperature_sensor_->publish_state(temperature);
    if (this->pressure_sensor_ != nullptr)
      this->pressure_sensor_->publish_state(pressure);
    if (this->humidity_sensor_ != nullptr)
      this->humidity_sensor_->publish_state(humidity);
    this->status_clear_warning();
  });
}
float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
  int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
  adc >>= 4;
  if (adc == 0x80000) {
    // temperature was disabled
    return NAN;
  }

  const int32_t t1 = this->calibration_.t1;
  const int32_t t2 = this->calibration_.t2;
  const int32_t t3 = this->calibration_.t3;

  int32_t const var1 = (((adc >> 3) - (t1 << 1)) * t2) >> 11;
  int32_t const var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
  *t_fine = var1 + var2;

  float const temperature = (*t_fine * 5 + 128);
  return temperature / 25600.0f;
}

float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
  int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
  adc >>= 4;
  if (adc == 0x80000) {
    // pressure was disabled
    return NAN;
  }
  const int64_t p1 = this->calibration_.p1;
  const int64_t p2 = this->calibration_.p2;
  const int64_t p3 = this->calibration_.p3;
  const int64_t p4 = this->calibration_.p4;
  const int64_t p5 = this->calibration_.p5;
  const int64_t p6 = this->calibration_.p6;
  const int64_t p7 = this->calibration_.p7;
  const int64_t p8 = this->calibration_.p8;
  const int64_t p9 = this->calibration_.p9;

  int64_t var1, var2, p;
  var1 = int64_t(t_fine) - 128000;
  var2 = var1 * var1 * p6;
  var2 = var2 + ((var1 * p5) << 17);
  var2 = var2 + (p4 << 35);
  var1 = ((var1 * var1 * p3) >> 8) + ((var1 * p2) << 12);
  var1 = ((int64_t(1) << 47) + var1) * p1 >> 33;

  if (var1 == 0)
    return NAN;

  p = 1048576 - adc;
  p = (((p << 31) - var2) * 3125) / var1;
  var1 = (p9 * (p >> 13) * (p >> 13)) >> 25;
  var2 = (p8 * p) >> 19;

  p = ((p + var1 + var2) >> 8) + (p7 << 4);
  return (p / 256.0f) / 100.0f;
}

float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
  uint16_t const raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
  if (raw_adc == 0x8000)
    return NAN;

  int32_t const adc = raw_adc;

  const int32_t h1 = this->calibration_.h1;
  const int32_t h2 = this->calibration_.h2;
  const int32_t h3 = this->calibration_.h3;
  const int32_t h4 = this->calibration_.h4;
  const int32_t h5 = this->calibration_.h5;
  const int32_t h6 = this->calibration_.h6;

  int32_t v_x1_u32r = t_fine - 76800;

  v_x1_u32r = ((((adc << 14) - (h4 << 20) - (h5 * v_x1_u32r)) + 16384) >> 15) *
              (((((((v_x1_u32r * h6) >> 10) * (((v_x1_u32r * h3) >> 11) + 32768)) >> 10) + 2097152) * h2 + 8192) >> 14);

  v_x1_u32r = v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * h1) >> 4);

  v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r;
  v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r;
  float const h = v_x1_u32r >> 12;

  return h / 1024.0f;
}
void BME280Component::set_temperature_oversampling(BME280Oversampling temperature_over_sampling) {
  this->temperature_oversampling_ = temperature_over_sampling;
}
void BME280Component::set_pressure_oversampling(BME280Oversampling pressure_over_sampling) {
  this->pressure_oversampling_ = pressure_over_sampling;
}
void BME280Component::set_humidity_oversampling(BME280Oversampling humidity_over_sampling) {
  this->humidity_oversampling_ = humidity_over_sampling;
}
void BME280Component::set_iir_filter(BME280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
uint8_t BME280Component::read_u8_(uint8_t a_register) {
  uint8_t data = 0;
  this->read_byte(a_register, &data);
  return data;
}
uint16_t BME280Component::read_u16_le_(uint8_t a_register) {
  uint16_t data = 0;
  this->read_byte_16(a_register, &data);
  return (data >> 8) | (data << 8);
}
int16_t BME280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }

}  // namespace esphome::bme280_base
