// Copyright (c) M5Stack. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#ifndef M5UNIFIED_IMPLEMENTATION
#error "M5PM1_Class.inl is part of M5Unified.cpp and is not meant to be included on its own"
#endif

#include "M5PM1_Class.hpp"

#if __has_include(<esp_log.h>)
#include <esp_log.h>
#endif

#include <algorithm>

namespace m5
{
// M5PM1 registers
  static constexpr const uint8_t M5PM1_REG_DEVICE_ID   = 0x00;
  static constexpr const uint8_t M5PM1_REG_PWR_SRC     = 0x04;
  static constexpr const uint8_t M5PM1_REG_WAKE_SRC    = 0x05;
  static constexpr const uint8_t M5PM1_REG_PWR_CFG     = 0x06;
  static constexpr const uint8_t M5PM1_REG_I2C_CFG     = 0x09;
  static constexpr const uint8_t M5PM1_REG_WDT_CNT     = 0x0A;
  static constexpr const uint8_t M5PM1_REG_SYS_CMD     = 0x0C;
  static constexpr const uint8_t M5PM1_REG_GPIO_MODE   = 0x10;
  static constexpr const uint8_t M5PM1_REG_GPIO_OUT    = 0x11;
  static constexpr const uint8_t M5PM1_REG_GPIO_IN     = 0x12;
  static constexpr const uint8_t M5PM1_REG_GPIO_DRV    = 0x13;
  static constexpr const uint8_t M5PM1_REG_GPIO_PUPD0  = 0x14;
  static constexpr const uint8_t M5PM1_REG_GPIO_PUPD1  = 0x15;
  static constexpr const uint8_t M5PM1_REG_GPIO_FUNC0  = 0x16;
  static constexpr const uint8_t M5PM1_REG_GPIO_FUNC1  = 0x17;
  static constexpr const uint8_t M5PM1_REG_VBAT_L      = 0x22;
  static constexpr const uint8_t M5PM1_REG_VIN_L       = 0x24;
  static constexpr const uint8_t M5PM1_REG_5VOUT_L     = 0x26;
  static constexpr const uint8_t M5PM1_REG_PWM0_L      = 0x30;
  static constexpr const uint8_t M5PM1_REG_PWM_FREQ_L  = 0x34;
  static constexpr const uint8_t M5PM1_REG_IRQ_STATUS1 = 0x40;
  static constexpr const uint8_t M5PM1_REG_IRQ_STATUS2 = 0x41;
  static constexpr const uint8_t M5PM1_REG_IRQ_STATUS3 = 0x42;
  static constexpr const uint8_t M5PM1_REG_IRQ_MASK1   = 0x43;
  static constexpr const uint8_t M5PM1_REG_IRQ_MASK2   = 0x44;
  static constexpr const uint8_t M5PM1_REG_IRQ_MASK3   = 0x45;

  static constexpr const uint8_t M5PM1_PWR_CFG_CHG_EN   = 1 << 0;
  static constexpr const uint8_t M5PM1_PWR_CFG_DCDC_EN  = 1 << 1;
  static constexpr const uint8_t M5PM1_PWR_CFG_LDO_EN   = 1 << 2;
  static constexpr const uint8_t M5PM1_PWR_CFG_BOOST_EN = 1 << 3;
  static constexpr const uint8_t M5PM1_PWR_CFG_LED_EN   = 1 << 4;
  static constexpr const uint8_t M5PM1_SYS_CMD_SHUTDOWN = 0xA1;
  static constexpr const uint8_t M5PM1_PWM_ENABLE       = 1 << 4;
  static constexpr const uint8_t M5PM1_PWM_POLARITY     = 1 << 5;

  static constexpr bool is_valid_gpio(M5PM1_Class::gpio_t pin)
  {
    return static_cast<std::uint8_t>(pin) < 5;
  }

  static constexpr std::uint8_t gpio_num(M5PM1_Class::gpio_t pin)
  {
    return static_cast<std::uint8_t>(pin);
  }

  static constexpr bool is_valid_pwm_channel(M5PM1_Class::pwm_channel_t channel)
  {
    return static_cast<std::uint8_t>(channel) <= M5PM1_Class::pwm_ch1;
  }

  bool M5PM1_Class::begin(void)
  {
    if (!_init) {
      std::uint32_t val = 0;
      auto res = readRegister(M5PM1_REG_DEVICE_ID, (uint8_t*)&val, 4);
      if (res)
      {
        writeRegister8(M5PM1_REG_I2C_CFG, 0x00); // disable I2C idle sleep
        writeRegister8(M5PM1_REG_WDT_CNT, 0x00); // disable watchdog
      }
      _init = res;
    }
    return _init;
  }

  bool M5PM1_Class::setExtOutput(bool enable)
  {
    return enable ? bitOn(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_BOOST_EN)
                  : bitOff(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_BOOST_EN);
  }

  bool M5PM1_Class::getExtOutput(void)
  {
    if (!_init) { return false; }
    return readRegister8(M5PM1_REG_PWR_CFG) & M5PM1_PWR_CFG_BOOST_EN;
  }

  bool M5PM1_Class::setLDOOutput(bool enable)
  {
    return enable ? bitOn(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_LDO_EN)
                  : bitOff(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_LDO_EN);
  }
  bool M5PM1_Class::setDCDCOutput(bool enable)
  {
    return enable ? bitOn(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_DCDC_EN)
                  : bitOff(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_DCDC_EN);
  }
  bool M5PM1_Class::setLedEnLevel(bool level)
  {
    return level ? bitOn(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_LED_EN)
                 : bitOff(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_LED_EN);
  }

  M5PM1_Class::pwr_src_t M5PM1_Class::getPowerSource(void)
  {
    if (!_init) { return none; }
    return static_cast<pwr_src_t>(readRegister8(M5PM1_REG_PWR_SRC) & 0x07);
  }

  bool M5PM1_Class::getVbatNodePowered(bool* powered)
  {
    if (!_init || powered == nullptr) { return false; }
    *powered = readRegister8(M5PM1_REG_PWR_SRC) & 0x04;
    return true;
  }

  bool M5PM1_Class::setGPIOFunction(gpio_t pin, gpio_function_t function)
  {
    if (!is_valid_gpio(pin)
     || (function != gpio && function != irq && function != special)) { return false; }
    auto num = gpio_num(pin);
    auto reg = num < 4 ? M5PM1_REG_GPIO_FUNC0 : M5PM1_REG_GPIO_FUNC1;
    auto shift = static_cast<std::uint8_t>((num < 4 ? num : num - 4) * 2);
    std::uint8_t mask = 0x03 << shift;
    std::uint8_t reg_val = readRegister8(reg);
    reg_val = (reg_val & ~mask) | (static_cast<std::uint8_t>(function) << shift);
    return writeRegister8(reg, reg_val);
  }

  bool M5PM1_Class::setGPIOMode(gpio_t pin, gpio_mode_t mode)
  {
    if (!is_valid_gpio(pin)) { return false; }
    auto mask = 1 << gpio_num(pin);
    return mode == output ? bitOn(M5PM1_REG_GPIO_MODE, mask)
                          : bitOff(M5PM1_REG_GPIO_MODE, mask);
  }

  bool M5PM1_Class::setGPIOPull(gpio_t pin, gpio_pull_t pull)
  {
    if (!is_valid_gpio(pin)) { return false; }
    auto num = gpio_num(pin);
    auto reg = num < 4 ? M5PM1_REG_GPIO_PUPD0 : M5PM1_REG_GPIO_PUPD1;
    auto shift = static_cast<std::uint8_t>((num < 4 ? num : num - 4) * 2);
    std::uint8_t mask = 0x03 << shift;
    std::uint8_t reg_val = readRegister8(reg);
    reg_val = (reg_val & ~mask) | (static_cast<std::uint8_t>(pull) << shift);
    return writeRegister8(reg, reg_val);
  }

  bool M5PM1_Class::setGPIODrive(gpio_t pin, gpio_drive_t drive)
  {
    if (!is_valid_gpio(pin)) { return false; }
    auto mask = 1 << gpio_num(pin);
    return drive == open_drain ? bitOn(M5PM1_REG_GPIO_DRV, mask)
                               : bitOff(M5PM1_REG_GPIO_DRV, mask);
  }

  bool M5PM1_Class::setGPIOOutput(gpio_t pin, bool high)
  {
    if (!is_valid_gpio(pin)) { return false; }
    auto mask = 1 << gpio_num(pin);
    return high ? bitOn(M5PM1_REG_GPIO_OUT, mask) : bitOff(M5PM1_REG_GPIO_OUT, mask);
  }

  bool M5PM1_Class::getGPIOInput(gpio_t pin)
  {
    if (!_init || !is_valid_gpio(pin)) { return false; }
    return readRegister8(M5PM1_REG_GPIO_IN) & (1 << gpio_num(pin));
  }

  bool M5PM1_Class::getGPIOInputBits(std::uint8_t* bits)
  {
    if (!_init || bits == nullptr) { return false; }
    return readRegister(M5PM1_REG_GPIO_IN, bits, 1);
  }

  bool M5PM1_Class::getGPIOOutputLatch(gpio_t pin)
  {
    if (!_init || !is_valid_gpio(pin)) { return false; }
    return readRegister8(M5PM1_REG_GPIO_OUT) & (1 << gpio_num(pin));
  }

  bool M5PM1_Class::setPwmFrequency(std::uint16_t frequency)
  {
    std::uint8_t data[2] =
    { static_cast<std::uint8_t>(frequency & 0xFF)
    , static_cast<std::uint8_t>(frequency >> 8)
    };
    return writeRegister(M5PM1_REG_PWM_FREQ_L, data, sizeof(data));
  }

  bool M5PM1_Class::setPwmDutyPercent(pwm_channel_t channel, std::uint32_t duty,
                                      pwm_polarity_t polarity, bool enable)
  {
    if (duty > 100) { return false; }
    auto duty12 = duty * 0x0FFF / 100;
    return setPwmDuty12bit(channel, duty12, polarity, enable);
  }

  bool M5PM1_Class::setPwmDuty12bit(pwm_channel_t channel, std::uint32_t duty12,
                                    pwm_polarity_t polarity, bool enable)
  {
    if (!is_valid_pwm_channel(channel) || duty12 > 0x0FFF) { return false; }
    std::uint8_t high = static_cast<std::uint8_t>(duty12 >> 8);
    if (enable) { high |= M5PM1_PWM_ENABLE; }
    if (polarity == pwm_polarity_t::inverted) { high |= M5PM1_PWM_POLARITY; }
    std::uint8_t data[2] = { static_cast<std::uint8_t>(duty12 & 0xFF), high };
    auto reg = static_cast<std::uint8_t>(M5PM1_REG_PWM0_L + static_cast<std::uint8_t>(channel) * 2);
    return writeRegister(reg, data, sizeof(data));
  }

  bool M5PM1_Class::clearWakeSource(std::uint8_t mask)
  {
    return writeRegister8(M5PM1_REG_WAKE_SRC, static_cast<std::uint8_t>(~mask & 0x7F));
  }

  bool M5PM1_Class::clearGPIOIRQStatus(void)
  {
    return writeRegister8(M5PM1_REG_IRQ_STATUS1, 0x00);
  }

  bool M5PM1_Class::clearSystemIRQStatus(void)
  {
    return writeRegister8(M5PM1_REG_IRQ_STATUS2, 0x00);
  }

  bool M5PM1_Class::clearButtonIRQStatus(void)
  {
    return writeRegister8(M5PM1_REG_IRQ_STATUS3, 0x00);
  }

  bool M5PM1_Class::clearIRQStatus(void)
  {
    return clearGPIOIRQStatus()
        && clearSystemIRQStatus()
        && clearButtonIRQStatus();
  }

  bool M5PM1_Class::setGPIOIRQMaskBits(std::uint8_t mask)
  {
    return writeRegister8(M5PM1_REG_IRQ_MASK1, mask & 0x1F);
  }

  bool M5PM1_Class::setSystemIRQMaskBits(std::uint8_t mask)
  {
    return writeRegister8(M5PM1_REG_IRQ_MASK2, mask & 0x3F);
  }

  bool M5PM1_Class::setButtonIRQMaskBits(std::uint8_t mask)
  {
    return writeRegister8(M5PM1_REG_IRQ_MASK3, mask & 0x07);
  }

  bool M5PM1_Class::setBatteryCharge(bool enable)
  {
    return enable ? bitOn(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_CHG_EN)
                  : bitOff(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_CHG_EN);
  }

  bool M5PM1_Class::getBatteryCharge(bool* enabled)
  {
    if (!_init || enabled == nullptr) { return false; }
    std::uint8_t cfg = 0;
    if (!readRegister(M5PM1_REG_PWR_CFG, &cfg, 1)) { return false; }
    *enabled = cfg & M5PM1_PWR_CFG_CHG_EN;
    return true;
  }

  bool M5PM1_Class::setChargeCurrent(std::uint16_t max_mA)
  {
    return false;
  }

  bool M5PM1_Class::setChargeVoltage(std::uint16_t max_mV)
  {
    return false;
  }

  std::uint16_t M5PM1_Class::getChargeCurrent(void)
  {
    return 0; // Return 0 if read failed
  }

  std::uint16_t M5PM1_Class::getChargeVoltage(void)
  {
    return 0; // Return 0 if read failed
  }

  bool M5PM1_Class::isCharging(void)
  {
    return false;
  }

  uint8_t M5PM1_Class::getPekPress(void)
  {
    if (!_init) return 0;
    uint8_t irq3 = 0;
    if (!readRegister(M5PM1_REG_IRQ_STATUS3, &irq3, 1)) return 0;
    uint8_t pending = irq3 & ((1 << 0) | (1 << 2));
    if (!pending) return 0;
    if (!writeRegister8(M5PM1_REG_IRQ_STATUS3, static_cast<uint8_t>(~pending & 0x07))) return 0;
    _pek_double_pending = (pending & (1 << 2)) != 0;
    return 2;
  }

  bool M5PM1_Class::wasPekDoubleClicked(void)
  {
    bool result = _pek_double_pending;
    _pek_double_pending = false;
    return result;
  }

  std::uint16_t M5PM1_Class::getVBUSVoltage(void)
  {
    if (!_init) { return 0; }
    std::uint8_t buf[2] = {};
    return readRegister(M5PM1_REG_VIN_L, buf, sizeof(buf)) ? (buf[1] << 8) | buf[0] : 0;
  }

  std::uint16_t M5PM1_Class::getBatteryVoltage(void)
  {
    std::uint16_t mv = 0;
    return getBatteryVoltage(&mv) ? mv : 0;
  }

  bool M5PM1_Class::getBatteryVoltage(std::uint16_t* millivolt)
  {
    if (!_init || millivolt == nullptr) { return false; }
    std::uint8_t buf[2] = {};
    if (!readRegister(M5PM1_REG_VBAT_L, buf, sizeof(buf))) { return false; }
    *millivolt = (buf[1] << 8) | buf[0];
    return true;
  }

  std::uint16_t M5PM1_Class::get5VoutVoltage(void)
  {
    if (!_init) { return 0; }
    std::uint8_t buf[2] = {};
    return readRegister(M5PM1_REG_5VOUT_L, buf, sizeof(buf)) ? (buf[1] << 8) | buf[0] : 0;
  }

  bool M5PM1_Class::powerOff(void)
  {
    return _init && writeRegister8(M5PM1_REG_SYS_CMD, M5PM1_SYS_CMD_SHUTDOWN);
  }

} // namespace m5
