#include "esphome/core/log.h"
#include "pm2005.h"

namespace esphome::pm2005 {

static const char *const TAG = "pm2005";

// Converts a sensor situation to a human readable string
static const LogString *pm2005_get_situation_string(int status) {
  switch (status) {
    case 1:
      return LOG_STR("Close");
    case 2:
      return LOG_STR("Malfunction");
    case 3:
      return LOG_STR("Under detecting");
    case 0x80:
      return LOG_STR("Detecting completed");
    default:
      return LOG_STR("Invalid");
  }
}

// Converts a sensor measuring mode to a human readable string
static const LogString *pm2005_get_measuring_mode_string(int status) {
  switch (status) {
    case 2:
      return LOG_STR("Single");
    case 3:
      return LOG_STR("Continuous");
    case 5:
      return LOG_STR("Dynamic");
    default:
      return LOG_STR("Timing");
  }
}

static inline uint16_t get_sensor_value(const uint8_t *data, uint8_t i) { return data[i] * 0x100 + data[i + 1]; }

void PM2005Component::setup() {
  if (this->sensor_type_ == PM2005) {
    this->situation_value_index_ = 3;
    this->pm_1_0_value_index_ = 4;
    this->pm_2_5_value_index_ = 6;
    this->pm_10_0_value_index_ = 8;
    this->measuring_value_index_ = 10;
  } else {
    this->situation_value_index_ = 2;
    this->pm_1_0_value_index_ = 3;
    this->pm_2_5_value_index_ = 5;
    this->pm_10_0_value_index_ = 7;
    this->measuring_value_index_ = 9;
  }

  if (this->read(this->data_buffer_, 12) != i2c::ERROR_OK) {
    ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
    this->mark_failed();
    return;
  }
}

void PM2005Component::update() {
  if (this->read(this->data_buffer_, 12) != i2c::ERROR_OK) {
    ESP_LOGW(TAG, "Read result failed");
    this->status_set_warning();
    return;
  }

  if (this->sensor_situation_ == this->data_buffer_[this->situation_value_index_]) {
    return;
  }

  this->sensor_situation_ = this->data_buffer_[this->situation_value_index_];
  ESP_LOGD(TAG, "Sensor situation: %s.", LOG_STR_ARG(pm2005_get_situation_string(this->sensor_situation_)));
  if (this->sensor_situation_ == 2) {
    this->status_set_warning();
    return;
  }
  if (this->sensor_situation_ != 0x80) {
    return;
  }

  const uint16_t pm1 = get_sensor_value(this->data_buffer_, this->pm_1_0_value_index_);
  const uint16_t pm25 = get_sensor_value(this->data_buffer_, this->pm_2_5_value_index_);
  const uint16_t pm10 = get_sensor_value(this->data_buffer_, this->pm_10_0_value_index_);
  const uint16_t sensor_measuring_mode = get_sensor_value(this->data_buffer_, this->measuring_value_index_);
  ESP_LOGD(TAG, "PM1.0: %d, PM2.5: %d, PM10: %d, Measuring mode: %s.", pm1, pm25, pm10,
           LOG_STR_ARG(pm2005_get_measuring_mode_string(sensor_measuring_mode)));

  if (this->pm_1_0_sensor_ != nullptr) {
    this->pm_1_0_sensor_->publish_state(pm1);
  }
  if (this->pm_2_5_sensor_ != nullptr) {
    this->pm_2_5_sensor_->publish_state(pm25);
  }
  if (this->pm_10_0_sensor_ != nullptr) {
    this->pm_10_0_sensor_->publish_state(pm10);
  }

  this->status_clear_warning();
}

void PM2005Component::dump_config() {
  ESP_LOGCONFIG(TAG,
                "PM2005:\n"
                "  Type: PM2%u05",
                this->sensor_type_ == PM2105);

  LOG_I2C_DEVICE(this);
  if (this->is_failed()) {
    ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
  }

  LOG_SENSOR("  ", "PM1.0", this->pm_1_0_sensor_);
  LOG_SENSOR("  ", "PM2.5", this->pm_2_5_sensor_);
  LOG_SENSOR("  ", "PM10 ", this->pm_10_0_sensor_);
}

}  // namespace esphome::pm2005
