// 20260220 Codex
// Exercise 14: Power / Charging Visual Indicator

#include <Arduino.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <XPowersLib.h>
#include <time.h>

#include "tbeam_supreme_adapter.h"

#ifndef NODE_LABEL
#define NODE_LABEL "POWER"
#endif

#ifndef OLED_SDA
#define OLED_SDA 17
#endif
#ifndef OLED_SCL
#define OLED_SCL 18
#endif
#ifndef OLED_ADDR
#define OLED_ADDR 0x3C
#endif

static XPowersLibInterface *g_pmu = nullptr;
static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE);

static const uint32_t kBlinkIntervalMs = 500;
static const uint32_t kStatusIntervalMs = 1000;
static const uint32_t kSerialIntervalMs = 2000;
static const uint32_t kStartupDisplayMs = 120000;

static bool g_ledOn = false;
static uint32_t g_lastBlinkMs = 0;
static uint32_t g_lastStatusMs = 0;
static uint32_t g_lastSerialMs = 0;
static uint32_t g_bootMs = 0;

static void oledShow(const char *l1,
                     const char *l2 = nullptr,
                     const char *l3 = nullptr,
                     const char *l4 = nullptr,
                     const char *l5 = nullptr)
{
  g_oled.clearBuffer();
  g_oled.setFont(u8g2_font_5x8_tf);
  if (l1) g_oled.drawUTF8(0, 12, l1);
  if (l2) g_oled.drawUTF8(0, 24, l2);
  if (l3) g_oled.drawUTF8(0, 36, l3);
  if (l4) g_oled.drawUTF8(0, 48, l4);
  if (l5) g_oled.drawUTF8(0, 60, l5);
  g_oled.sendBuffer();
}

static void setChargeLed(bool on)
{
  if (!g_pmu) return;
  g_pmu->setChargingLedMode(on ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
}

static void setupChargingDefaults()
{
  if (!g_pmu) return;
  g_pmu->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
  g_pmu->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
}

static const char *powerState(bool batteryPresent, bool usbPresent, bool fullyCharged, bool chargingNow)
{
  if (!batteryPresent) return "NO BATTERY";
  if (!usbPresent) return "USB NOT PRESENT";
  if (fullyCharged) return "FULL";
  if (chargingNow) return "CHARGING";
  return "IDLE";
}

static void formatDisplayTime(char *out, size_t outSize)
{
  const time_t now = time(nullptr);
  if (now > 1700000000) {
    struct tm tmNow;
    localtime_r(&now, &tmNow);
    snprintf(out, outSize, "Time: %02d:%02d:%02d", tmNow.tm_hour, tmNow.tm_min, tmNow.tm_sec);
    return;
  }

  uint32_t sec = millis() / 1000;
  uint32_t hh = sec / 3600;
  uint32_t mm = (sec % 3600) / 60;
  uint32_t ss = sec % 60;
  snprintf(out, outSize, "Uptime: %02lu:%02lu:%02lu", (unsigned long)hh, (unsigned long)mm, (unsigned long)ss);
}

void setup()
{
  g_bootMs = millis();

  Serial.begin(115200);
  delay(1200);
  Serial.println("Exercise 14_Power boot");

  Wire.begin(OLED_SDA, OLED_SCL);
  g_oled.setI2CAddress(OLED_ADDR << 1);
  g_oled.begin();
  oledShow("Exercise 14 Power", "Node: " NODE_LABEL, "Booting...");

  if (!tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial)) {
    Serial.println("ERROR: PMU init failed");
    oledShow("Exercise 14 Power", "Node: " NODE_LABEL, "PMU init FAILED");
    return;
  }

  setupChargingDefaults();
  setChargeLed(false);

  Serial.println("PMU init OK");
  oledShow("Exercise 14 Power", "Node: " NODE_LABEL, "PMU ready");
}

void loop()
{
  if (!g_pmu) {
    delay(1000);
    return;
  }

  const bool batteryPresent = g_pmu->isBatteryConnect();
  const bool usbPresent = g_pmu->isVbusIn();
  const bool chargingNow = g_pmu->isCharging();
  const int battPercent = g_pmu->getBatteryPercent();
  const float battV = g_pmu->getBattVoltage() / 1000.0f;

  const bool fullyCharged = batteryPresent && battPercent >= 100;
  const bool shouldCharge = batteryPresent && usbPresent && !fullyCharged;

  if (shouldCharge) {
    if (millis() - g_lastBlinkMs >= kBlinkIntervalMs) {
      g_lastBlinkMs = millis();
      g_ledOn = !g_ledOn;
      setChargeLed(g_ledOn);
    }
  } else {
    g_ledOn = false;
    setChargeLed(false);
  }

  if (millis() - g_lastStatusMs >= kStatusIntervalMs) {
    g_lastStatusMs = millis();

    char l1[32];
    char l2[32];
    char l3[32];
    char l4[32];
    char l5[32];

    const char *state = powerState(batteryPresent, usbPresent, fullyCharged, chargingNow);
    const bool startupWindow = (millis() - g_bootMs) < kStartupDisplayMs;

    if (startupWindow) {
      snprintf(l1, sizeof(l1), "Exercise 14 Power");
    } else {
      snprintf(l1, sizeof(l1), "Power Monitor");
    }

    snprintf(l2, sizeof(l2), "Node: %s", NODE_LABEL);
    formatDisplayTime(l3, sizeof(l3));
    snprintf(l4, sizeof(l4), "State: %s", state);
    if (battPercent >= 0) {
      snprintf(l5, sizeof(l5), "VBAT:%.3fV  %d%%", battV, battPercent);
    } else {
      snprintf(l5, sizeof(l5), "VBAT:%.3fV  pct:?", battV);
    }

    oledShow(l1, l2, l3, l4, l5);
  }

  if (millis() - g_lastSerialMs >= kSerialIntervalMs) {
    g_lastSerialMs = millis();
    Serial.printf("node=%s usb=%u batt=%u charging=%u full=%u led=%u vbatt=%.3fV pct=%d\r\n",
                  NODE_LABEL,
                  usbPresent ? 1 : 0,
                  batteryPresent ? 1 : 0,
                  chargingNow ? 1 : 0,
                  fullyCharged ? 1 : 0,
                  g_ledOn ? 1 : 0,
                  battV,
                  battPercent);
  }

  delay(20);
}
