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

namespace esphome::pzemdc {

static const char *const TAG = "pzemdc";

static const uint8_t PZEM_CMD_READ_IN_REGISTERS = 0x04;
static const uint8_t PZEM_CMD_RESET_ENERGY = 0x42;
static const uint8_t PZEM_REGISTER_COUNT = 10;  // 10x 16-bit registers

void PZEMDC::on_modbus_data(const std::vector<uint8_t> &data) {
  if (data.size() < 16) {
    ESP_LOGW(TAG, "Invalid size for PZEM DC!");
    return;
  }

  // See https://github.com/esphome/feature-requests/issues/49#issuecomment-538636809
  //           0     1     2     3     4     5     6     7           = ModBus register
  //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20  = Buffer index
  // 01 04 10 05 40 00 0A 00 0D 00 00 00 02 00 00 00 00 00 00 D6 29
  // Id Cc Sz Volt- Curre Power------ Energy----- HiAlm LoAlm Crc--

  auto pzem_get_16bit = [&](size_t i) -> uint16_t {
    return (uint16_t(data[i + 0]) << 8) | (uint16_t(data[i + 1]) << 0);
  };
  auto pzem_get_32bit = [&](size_t i) -> uint32_t {
    return (uint32_t(pzem_get_16bit(i + 2)) << 16) | (uint32_t(pzem_get_16bit(i + 0)) << 0);
  };

  uint16_t raw_voltage = pzem_get_16bit(0);
  float voltage = raw_voltage / 100.0f;  // max 655.35 V

  uint16_t raw_current = pzem_get_16bit(2);
  float current = raw_current / 100.0f;  // max 655.35 A

  uint32_t raw_power = pzem_get_32bit(4);
  float power = raw_power / 10.0f;  // max 429496729.5 W

  uint32_t raw_energy = pzem_get_32bit(8);
  float energy = raw_energy / 1000.0f;  // max 4294967.295 kWh

  ESP_LOGD(TAG, "PZEM DC: V=%.1f V, I=%.3f A, P=%.1f W", voltage, current, power);
  if (this->voltage_sensor_ != nullptr)
    this->voltage_sensor_->publish_state(voltage);
  if (this->current_sensor_ != nullptr)
    this->current_sensor_->publish_state(current);
  if (this->power_sensor_ != nullptr)
    this->power_sensor_->publish_state(power);
  if (this->energy_sensor_ != nullptr)
    this->energy_sensor_->publish_state(energy);
}

void PZEMDC::update() { this->send(PZEM_CMD_READ_IN_REGISTERS, 0, 8); }
void PZEMDC::dump_config() {
  ESP_LOGCONFIG(TAG,
                "PZEMDC:\n"
                "  Address: 0x%02X",
                this->address_);
  LOG_SENSOR("", "Voltage", this->voltage_sensor_);
  LOG_SENSOR("", "Current", this->current_sensor_);
  LOG_SENSOR("", "Power", this->power_sensor_);
  LOG_SENSOR("", "Energy", this->energy_sensor_);
}

void PZEMDC::reset_energy() {
  std::vector<uint8_t> cmd;
  cmd.push_back(this->address_);
  cmd.push_back(PZEM_CMD_RESET_ENERGY);
  this->send_raw(cmd);
}

}  // namespace esphome::pzemdc
