// 20260214 ChatGPT
// $Id$
// $HeadURL$

#include <Arduino.h>
#include <Wire.h>
#include "tbeam_supreme_adapter.h"

#ifndef RTC_I2C_ADDR
#define RTC_I2C_ADDR 0x51
#endif

static XPowersLibInterface* g_pmu = nullptr;
static uint32_t g_logSeq = 0;
static uint32_t g_lastPrintMs = 0;
static String g_cmdBuf;
static bool g_lastWasCR = false;

struct RtcDateTime {
  uint16_t year;
  uint8_t month;
  uint8_t day;
  uint8_t hour;
  uint8_t minute;
  uint8_t second;
  uint8_t weekday;
};

static uint8_t toBcd(uint8_t v) {
  return ((v / 10U) << 4U) | (v % 10U);
}

static uint8_t fromBcd(uint8_t b) {
  return ((b >> 4U) * 10U) + (b & 0x0FU);
}

static void logf(const char* fmt, ...) {
  char msg[196];
  va_list args;
  va_start(args, fmt);
  vsnprintf(msg, sizeof(msg), fmt, args);
  va_end(args);
  Serial.printf("[%10lu][%06lu] %s\r\n", (unsigned long)millis(), (unsigned long)g_logSeq++, msg);
}

static bool initPmuForRtc() {
  return tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial);
}

static bool rtcRead(RtcDateTime& out, bool& lowVoltageFlag) {
  Wire1.beginTransmission(RTC_I2C_ADDR);
  Wire1.write(0x02); // seconds register
  if (Wire1.endTransmission(false) != 0) {
    return false;
  }

  const uint8_t need = 7;
  uint8_t got = Wire1.requestFrom((int)RTC_I2C_ADDR, (int)need);
  if (got != need) {
    return false;
  }

  uint8_t sec = Wire1.read();
  uint8_t min = Wire1.read();
  uint8_t hour = Wire1.read();
  uint8_t day = Wire1.read();
  uint8_t weekday = Wire1.read();
  uint8_t month = Wire1.read();
  uint8_t year = Wire1.read();

  lowVoltageFlag = (sec & 0x80U) != 0;
  out.second = fromBcd(sec & 0x7FU);
  out.minute = fromBcd(min & 0x7FU);
  out.hour = fromBcd(hour & 0x3FU);
  out.day = fromBcd(day & 0x3FU);
  out.weekday = fromBcd(weekday & 0x07U);
  out.month = fromBcd(month & 0x1FU);
  uint8_t yy = fromBcd(year);
  bool century = (month & 0x80U) != 0;
  out.year = century ? (1900U + yy) : (2000U + yy);

  return true;
}

static bool rtcWrite(const RtcDateTime& in) {
  bool century = in.year < 2000;
  uint8_t yy = (uint8_t)(in.year % 100);
  uint8_t monthReg = toBcd(in.month) & 0x1FU;
  if (century) {
    monthReg |= 0x80U;
  }

  Wire1.beginTransmission(RTC_I2C_ADDR);
  Wire1.write(0x02); // seconds register
  Wire1.write(toBcd(in.second) & 0x7FU);
  Wire1.write(toBcd(in.minute) & 0x7FU);
  Wire1.write(toBcd(in.hour) & 0x3FU);
  Wire1.write(toBcd(in.day) & 0x3FU);
  Wire1.write(toBcd(in.weekday) & 0x07U);
  Wire1.write(monthReg);
  Wire1.write(toBcd(yy));
  return Wire1.endTransmission() == 0;
}

static bool parseSetCommand(const String& line, RtcDateTime& dt) {
  int y, mo, d, h, mi, s;
  if (sscanf(line.c_str(), "set %d-%d-%d %d:%d:%d", &y, &mo, &d, &h, &mi, &s) != 6) {
    return false;
  }

  if (y < 1900 || y > 2099) return false;
  if (mo < 1 || mo > 12) return false;
  if (d < 1 || d > 31) return false;
  if (h < 0 || h > 23) return false;
  if (mi < 0 || mi > 59) return false;
  if (s < 0 || s > 59) return false;

  dt.year = (uint16_t)y;
  dt.month = (uint8_t)mo;
  dt.day = (uint8_t)d;
  dt.hour = (uint8_t)h;
  dt.minute = (uint8_t)mi;
  dt.second = (uint8_t)s;
  dt.weekday = 0; // Not critical for this persistence check.
  return true;
}

static void printRtcNow(const char* label) {
  RtcDateTime now{};
  bool lowV = false;
  if (!rtcRead(now, lowV)) {
    logf("%s: RTC read failed", label);
    return;
  }
  logf("%s: %04u-%02u-%02u %02u:%02u:%02u  weekday=%u%s",
       label,
       (unsigned)now.year, (unsigned)now.month, (unsigned)now.day,
       (unsigned)now.hour, (unsigned)now.minute, (unsigned)now.second,
       (unsigned)now.weekday,
       lowV ? "  [LOW_VOLTAGE_FLAG]" : "");
}

static void handleCommand(const String& raw) {
  String line = raw;
  line.trim();
  if (line.length() == 0) {
    return;
  }

  if (line == "help") {
    logf("Commands:");
    logf("  show");
    logf("  set YYYY-MM-DD HH:MM:SS");
    logf("  help");
    return;
  }
  if (line == "show") {
    printRtcNow("RTC");
    return;
  }

  RtcDateTime dt{};
  if (parseSetCommand(line, dt)) {
    if (rtcWrite(dt)) {
      logf("RTC set succeeded");
      printRtcNow("RTC");
    } else {
      logf("RTC set failed");
    }
    return;
  }

  logf("Unknown command: %s", line.c_str());
}

void setup() {
  Serial.begin(115200);
  Serial.println("Sleeping for 5 seconds to allow Serial Monitor connection...");
  delay(5000);

  Serial.println("\r\n==================================================");
  Serial.println("Exercise 06: RTC check (PCF8563)");
  Serial.println("==================================================");
  Serial.printf("RTC I2C: SDA1=%d SCL1=%d ADDR=0x%02X\r\n",
                tbeam_supreme::i2cSda(), tbeam_supreme::i2cScl(), RTC_I2C_ADDR);

  initPmuForRtc();

  logf("Type 'help' for commands.");
  logf("Power-off persistence test:");
  logf("  1) set time");
  logf("  2) power off for a few minutes");
  logf("  3) power on and run 'show' before any GPS sync");
  printRtcNow("RTC startup");
}

void loop() {
  while (Serial.available() > 0) {
    char c = (char)Serial.read();
    if (c == '\r' || c == '\n') {
      // Handle CR, LF, and CRLF/LFCR cleanly as one line ending.
      if ((c == '\n' && g_lastWasCR) || (c == '\r' && !g_lastWasCR && g_cmdBuf.length() == 0)) {
        g_lastWasCR = (c == '\r');
        continue;
      }
      handleCommand(g_cmdBuf);
      g_cmdBuf = "";
      g_lastWasCR = (c == '\r');
    } else {
      g_lastWasCR = false;
      g_cmdBuf += c;
      if (g_cmdBuf.length() > 120) {
        g_cmdBuf = "";
        logf("Input line too long, buffer cleared");
      }
    }
  }

  uint32_t nowMs = millis();
  if ((uint32_t)(nowMs - g_lastPrintMs) >= 10000) {
    g_lastPrintMs = nowMs;
    printRtcNow("RTC periodic");
  }
}
