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

// https://download.epsondevice.com/td/pdf/app/RX8130CE_en.pdf

namespace esphome {
namespace rx8130 {

static const uint8_t RX8130_REG_SEC = 0x10;
static const uint8_t RX8130_REG_MIN = 0x11;
static const uint8_t RX8130_REG_HOUR = 0x12;
static const uint8_t RX8130_REG_WDAY = 0x13;
static const uint8_t RX8130_REG_MDAY = 0x14;
static const uint8_t RX8130_REG_MONTH = 0x15;
static const uint8_t RX8130_REG_YEAR = 0x16;
static const uint8_t RX8130_REG_EXTEN = 0x1C;
static const uint8_t RX8130_REG_FLAG = 0x1D;
static const uint8_t RX8130_REG_CTRL0 = 0x1E;
static const uint8_t RX8130_REG_CTRL1 = 0x1F;
static const uint8_t RX8130_REG_DIG_OFFSET = 0x30;
static const uint8_t RX8130_BIT_CTRL_STOP = 0x40;
static const uint8_t RX8130_BAT_FLAGS = 0x30;
static const uint8_t RX8130_CLEAR_FLAGS = 0x00;

static const char *const TAG = "rx8130";

constexpr uint8_t bcd2dec(uint8_t val) { return (val >> 4) * 10 + (val & 0x0f); }
constexpr uint8_t dec2bcd(uint8_t val) { return ((val / 10) << 4) + (val % 10); }

void RX8130Component::setup() {
  // Set digital offset to disabled with no offset
  if (this->write_register(RX8130_REG_DIG_OFFSET, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) {
    this->mark_failed();
    return;
  }
  // Disable wakeup timers
  if (this->write_register(RX8130_REG_EXTEN, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) {
    this->mark_failed();
    return;
  }
  // Clear VLF flag in case there has been data loss
  if (this->write_register(RX8130_REG_FLAG, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) {
    this->mark_failed();
    return;
  }
  // Clear test flag and disable interrupts
  if (this->write_register(RX8130_REG_CTRL0, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) {
    this->mark_failed();
    return;
  }
  // Enable battery charging and switching
  if (this->write_register(RX8130_REG_CTRL1, &RX8130_BAT_FLAGS, 1) != i2c::ERROR_OK) {
    this->mark_failed();
    return;
  }
  // Clear STOP bit
  this->stop_(false);
}

void RX8130Component::update() { this->read_time(); }

void RX8130Component::dump_config() {
  ESP_LOGCONFIG(TAG, "RX8130:");
  LOG_I2C_DEVICE(this);
  RealTimeClock::dump_config();
}

void RX8130Component::read_time() {
  uint8_t date[7];
  if (this->read_register(RX8130_REG_SEC, date, 7) != i2c::ERROR_OK) {
    this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
    return;
  }
  ESPTime rtc_time{
      .second = bcd2dec(date[0] & 0x7f),
      .minute = bcd2dec(date[1] & 0x7f),
      .hour = bcd2dec(date[2] & 0x3f),
      .day_of_week = static_cast<uint8_t>((date[3] & 0x7f) ? __builtin_ctz(date[3] & 0x7f) + 1 : 1),
      .day_of_month = bcd2dec(date[4] & 0x3f),
      .month = bcd2dec(date[5] & 0x1f),
      .year = static_cast<uint16_t>(bcd2dec(date[6]) + 2000),
  };
  rtc_time.recalc_timestamp_utc(false);
  if (!rtc_time.is_valid(/*check_day_of_week=*/true, /*check_day_of_year=*/false)) {
    ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
    return;
  }
  ESP_LOGD(TAG, "Read UTC time: %04d-%02d-%02d %02d:%02d:%02d", rtc_time.year, rtc_time.month, rtc_time.day_of_month,
           rtc_time.hour, rtc_time.minute, rtc_time.second);
  time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
}

void RX8130Component::write_time() {
  auto now = time::RealTimeClock::utcnow();
  if (!now.is_valid()) {
    ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
    return;
  }
  uint8_t buff[7];
  buff[0] = dec2bcd(now.second);
  buff[1] = dec2bcd(now.minute);
  buff[2] = dec2bcd(now.hour);
  buff[3] = 1 << (now.day_of_week - 1);
  buff[4] = dec2bcd(now.day_of_month);
  buff[5] = dec2bcd(now.month);
  buff[6] = dec2bcd(now.year % 100);
  this->stop_(true);
  if (this->write_register(RX8130_REG_SEC, buff, 7) != i2c::ERROR_OK) {
    this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
  } else {
    ESP_LOGD(TAG, "Wrote UTC time: %04d-%02d-%02d %02d:%02d:%02d", now.year, now.month, now.day_of_month, now.hour,
             now.minute, now.second);
  }
  this->stop_(false);
}

void RX8130Component::stop_(bool stop) {
  const uint8_t data = stop ? RX8130_BIT_CTRL_STOP : RX8130_CLEAR_FLAGS;
  if (this->write_register(RX8130_REG_CTRL0, &data, 1) != i2c::ERROR_OK) {
    this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
  }
}

}  // namespace rx8130
}  // namespace esphome
