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

/* Select gain for AT581X (3dB per step for level1, 6dB per step for level 2), high value = small gain. (p12) */
const uint8_t GAIN_ADDR_TABLE[] = {0x5c, 0x63};
const uint8_t GAIN5C_TABLE[] = {0x08, 0x18, 0x28, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8};
const uint8_t GAIN63_TABLE[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
const uint8_t GAIN61_VALUE = 0xCA;  // 0xC0 | 0x02 (freq present) | 0x08 (gain present)

/*!< Power consumption configuration table (p12). */
const uint8_t POWER_TABLE[] = {48, 56, 63, 70, 77, 91, 105, 115, 40, 44, 47, 51, 54, 61, 68, 78};
const uint8_t POWER67_TABLE[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7};
const uint8_t POWER68_TABLE[] = {0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8,
                                 24,  24,  24,  24,  24,  24,  24,  24};  // See Page 12, shift by 3 bits

/*!< Frequency Configuration table (p14/15 of datasheet). */
const uint8_t FREQ_ADDR = 0x61;
const uint16_t FREQ_TABLE[] = {5696, 5715, 5730, 5748, 5765, 5784, 5800, 5819, 5836, 5851, 5869, 5888};
const uint8_t FREQ5F_TABLE[] = {0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x40, 0x41, 0x42, 0x43};
const uint8_t FREQ60_TABLE[] = {0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9e};

/*!< Value for RF and analog modules switch (p10). */
const uint8_t RF_OFF_TABLE[] = {0x46, 0xaa, 0x50};
const uint8_t RF_ON_TABLE[] = {0x45, 0x55, 0xA0};
const uint8_t RF_REG_ADDR[] = {0x5d, 0x62, 0x51};

/*!< Registers of Lighting delay time. Unit: ms, min 2s (p8) */
const uint8_t HIGH_LEVEL_DELAY_CONTROL_ADDR = 0x41; /*!< Time_flag_out_ctrl 0x01 */
const uint8_t HIGH_LEVEL_DELAY_VALUE_ADDR = 0x42;   /*!< Time_flag_out_1 Bit<7:0> */

const uint8_t RESET_ADDR = 0x00;

/*!< Sensing distance address */
const uint8_t SIGNAL_DETECTION_THRESHOLD_ADDR_LO = 0x10;
const uint8_t SIGNAL_DETECTION_THRESHOLD_ADDR_HI = 0x11;

/*!< Bit field value for power registers */
const uint8_t POWER_THRESHOLD_ADDR_HI = 0x68;
const uint8_t POWER_THRESHOLD_ADDR_LO = 0x67;
const uint8_t PWR_WORK_TIME_EN = 8;     // Reg 0x67
const uint8_t PWR_BURST_TIME_EN = 32;   // Reg 0x68
const uint8_t PWR_THRESH_EN = 64;       // Reg 0x68
const uint8_t PWR_THRESH_VAL_EN = 128;  // Reg 0x67

/*!< Times */
const uint8_t TRIGGER_BASE_TIME_ADDR = 0x3D;  // 4 bytes, so up to 0x40
const uint8_t PROTECT_TIME_ADDR = 0x4E;       // 2 bytes, up to 0x4F
const uint8_t TRIGGER_KEEP_TIME_ADDR = 0x42;  // 4 bytes, so up to 0x45
const uint8_t TIME41_VALUE = 1;
const uint8_t SELF_CHECK_TIME_ADDR = 0x38;  // 2 bytes, up to 0x39

namespace esphome::at581x {

static const char *const TAG = "at581x";

bool AT581XComponent::i2c_write_reg(uint8_t addr, uint8_t data) {
  return this->write_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
}
bool AT581XComponent::i2c_write_reg(uint8_t addr, uint32_t data) {
  return this->i2c_write_reg(addr + 0, uint8_t(data & 0xFF)) &&
         this->i2c_write_reg(addr + 1, uint8_t((data >> 8) & 0xFF)) &&
         this->i2c_write_reg(addr + 2, uint8_t((data >> 16) & 0xFF)) &&
         this->i2c_write_reg(addr + 3, uint8_t((data >> 24) & 0xFF));
}
bool AT581XComponent::i2c_write_reg(uint8_t addr, uint16_t data) {
  return this->i2c_write_reg(addr, uint8_t(data & 0xFF)) && this->i2c_write_reg(addr + 1, uint8_t((data >> 8) & 0xFF));
}

bool AT581XComponent::i2c_read_reg(uint8_t addr, uint8_t &data) {
  return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
}

void AT581XComponent::setup() {}
void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); }
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
bool AT581XComponent::i2c_write_config() {
  ESP_LOGCONFIG(TAG,
                "Writing new config for AT581X\n"
                "  Frequency: %dMHz\n"
                "  Sensing distance: %d\n"
                "  Power: %dµA\n"
                "  Gain: %d\n"
                "  Trigger base time: %dms\n"
                "  Trigger keep time: %dms\n"
                "  Protect time: %dms\n"
                "  Self check time: %dms",
                this->freq_, this->delta_, this->power_, this->gain_, this->trigger_base_time_ms_,
                this->trigger_keep_time_ms_, this->protect_time_ms_, this->self_check_time_ms_);

  // Set frequency point
  if (!this->i2c_write_reg(FREQ_ADDR, GAIN61_VALUE)) {
    ESP_LOGE(TAG, "Failed to write AT581X Freq mode");
    return false;
  }
  // Find the current frequency from the table to know what value to write
  for (size_t i = 0; i < ARRAY_SIZE(FREQ_TABLE) + 1; i++) {
    if (i == ARRAY_SIZE(FREQ_TABLE)) {
      ESP_LOGE(TAG, "Set frequency not found");
      return false;
    }
    if (FREQ_TABLE[i] == this->freq_) {
      if (!this->i2c_write_reg(0x5F, FREQ5F_TABLE[i]) || !this->i2c_write_reg(0x60, FREQ60_TABLE[i])) {
        ESP_LOGE(TAG, "Failed to write AT581X Freq value");
        return false;
      }
      break;
    }
  }

  // Set distance
  if (!this->i2c_write_reg(SIGNAL_DETECTION_THRESHOLD_ADDR_LO, (uint8_t) (this->delta_ & 0xFF)) ||
      !this->i2c_write_reg(SIGNAL_DETECTION_THRESHOLD_ADDR_HI, (uint8_t) (this->delta_ >> 8))) {
    ESP_LOGE(TAG, "Failed to write AT581X sensing distance low");
    return false;
  }

  // Set power setting
  uint8_t pwr67 = PWR_THRESH_VAL_EN | PWR_WORK_TIME_EN, pwr68 = PWR_BURST_TIME_EN | PWR_THRESH_EN;
  for (size_t i = 0; i < ARRAY_SIZE(POWER_TABLE) + 1; i++) {
    if (i == ARRAY_SIZE(POWER_TABLE)) {
      ESP_LOGE(TAG, "Set power not found");
      return false;
    }
    if (POWER_TABLE[i] == this->power_) {
      pwr67 |= POWER67_TABLE[i];
      pwr68 |= POWER68_TABLE[i];  // See Page 12
      break;
    }
  }

  if (!this->i2c_write_reg(POWER_THRESHOLD_ADDR_LO, pwr67) || !this->i2c_write_reg(POWER_THRESHOLD_ADDR_HI, pwr68)) {
    ESP_LOGE(TAG, "Failed to write AT581X power registers");
    return false;
  }

  // Set gain
  if (this->gain_ < 0 || static_cast<size_t>(this->gain_) >= ARRAY_SIZE(GAIN5C_TABLE) ||
      static_cast<size_t>(this->gain_ >> 1) >= ARRAY_SIZE(GAIN63_TABLE)) {
    ESP_LOGE(TAG, "AT581X gain index out of range: %d", this->gain_);
    return false;
  }
  if (!this->i2c_write_reg(GAIN_ADDR_TABLE[0], GAIN5C_TABLE[this->gain_]) ||
      !this->i2c_write_reg(GAIN_ADDR_TABLE[1], GAIN63_TABLE[this->gain_ >> 1])) {
    ESP_LOGE(TAG, "Failed to write AT581X gain registers");
    return false;
  }

  // Set times
  if (!this->i2c_write_reg(TRIGGER_BASE_TIME_ADDR, (uint32_t) this->trigger_base_time_ms_)) {
    ESP_LOGE(TAG, "Failed to write AT581X trigger base time registers");
    return false;
  }
  if (!this->i2c_write_reg(TRIGGER_KEEP_TIME_ADDR, (uint32_t) this->trigger_keep_time_ms_)) {
    ESP_LOGE(TAG, "Failed to write AT581X trigger keep time registers");
    return false;
  }

  if (!this->i2c_write_reg(PROTECT_TIME_ADDR, (uint16_t) this->protect_time_ms_)) {
    ESP_LOGE(TAG, "Failed to write AT581X protect time registers");
    return false;
  }
  if (!this->i2c_write_reg(SELF_CHECK_TIME_ADDR, (uint16_t) this->self_check_time_ms_)) {
    ESP_LOGE(TAG, "Failed to write AT581X self check time registers");
    return false;
  }

  if (!this->i2c_write_reg(0x41, TIME41_VALUE)) {
    ESP_LOGE(TAG, "Failed to enable AT581X time registers");
    return false;
  }

  // Don't know why it's required in other code, it's not in datasheet
  if (!this->i2c_write_reg(0x55, (uint8_t) 0x04)) {
    ESP_LOGE(TAG, "Failed to enable AT581X");
    return false;
  }

  // Ok, config is written, let's reset the chip so it's using the new config
  return this->reset_hardware_frontend();
}

// float AT581XComponent::get_setup_priority() const { return 0; }
bool AT581XComponent::reset_hardware_frontend() {
  if (!this->i2c_write_reg(RESET_ADDR, (uint8_t) 0) || !this->i2c_write_reg(RESET_ADDR, (uint8_t) 1)) {
    ESP_LOGE(TAG, "Failed to reset AT581X hardware frontend");
    return false;
  }
  return true;
}

void AT581XComponent::set_rf_mode(bool enable) {
  const uint8_t *p = enable ? &RF_ON_TABLE[0] : &RF_OFF_TABLE[0];
  for (size_t i = 0; i < ARRAY_SIZE(RF_REG_ADDR); i++) {
    if (!this->i2c_write_reg(RF_REG_ADDR[i], p[i])) {
      ESP_LOGE(TAG, "Failed to write AT581X RF mode");
      return;
    }
  }
}

}  // namespace esphome::at581x
