// 20260213 ChatGPT
// $Id$
// $HeadURL$

#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#include "tbeam_supreme_adapter.h"

static SPIClass sdSpiH(HSPI);
static SPIClass sdSpiF(FSPI);
static SPIClass* g_sdSpi = nullptr;
static const char* g_sdBusName = "none";
static uint32_t g_sdFreq = 0;
static XPowersLibInterface* g_pmu = nullptr;

static const char* kRootTestFile = "/Exercise_04_test.txt";
static const char* kNestedDir = "/test/testsub1/testsubsub1";
static const char* kNestedTestFile = "/test/testsub1/testsubsub1/Exercise_04_test.txt";
static const char* kPayload = "This is a test";

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

static const char* cardTypeToString(uint8_t type) {
  switch (type) {
    case CARD_MMC: return "MMC";
    case CARD_SD: return "SDSC";
    case CARD_SDHC: return "SDHC/SDXC";
    default: return "UNKNOWN";
  }
}

static bool tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz) {
  SD.end();
  bus.end();
  delay(10);

  // Keep inactive devices deselected on shared bus lines.
  pinMode(tbeam_supreme::sdCs(), OUTPUT);
  digitalWrite(tbeam_supreme::sdCs(), HIGH);
  pinMode(tbeam_supreme::imuCs(), OUTPUT);
  digitalWrite(tbeam_supreme::imuCs(), HIGH);

  bus.begin(tbeam_supreme::sdSck(), tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi(), tbeam_supreme::sdCs());
  delay(2);

  Serial.printf("SD: trying bus=%s freq=%lu Hz\r\n", busName, (unsigned long)hz);
  if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) {
    Serial.println("SD: mount failed (possible non-FAT format, power, or bus issue)");
    return false;
  }

  uint8_t cardType = SD.cardType();
  if (cardType == CARD_NONE) {
    SD.end();
    return false;
  }

  g_sdSpi = &bus;
  g_sdBusName = busName;
  g_sdFreq = hz;
  return true;
}

static bool mountCard() {
  const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};

  for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
    if (tryMountWithBus(sdSpiH, "HSPI", freqs[i])) {
      Serial.println("SD: card detected and mounted");
      return true;
    }
  }
  for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
    if (tryMountWithBus(sdSpiF, "FSPI", freqs[i])) {
      Serial.println("SD: card detected and mounted");
      return true;
    }
  }

  Serial.println("SD: begin() failed on all bus/frequency attempts");
  Serial.println("    likely card absent, bad format, pin mismatch, or hardware issue");
  return false;
}

static void printCardInfo() {
  uint8_t cardType = SD.cardType();
  uint64_t cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL);
  uint64_t totalMB = SD.totalBytes() / (1024ULL * 1024ULL);
  uint64_t usedMB = SD.usedBytes() / (1024ULL * 1024ULL);

  Serial.printf("SD type: %s\r\n", cardTypeToString(cardType));
  Serial.printf("SD size: %llu MB\r\n", cardSizeMB);
  Serial.printf("FS total: %llu MB\r\n", totalMB);
  Serial.printf("FS used : %llu MB\r\n", usedMB);
  Serial.printf("SPI bus: %s @ %lu Hz\r\n", g_sdBusName, (unsigned long)g_sdFreq);
}

static bool ensureDirRecursive(const char* path) {
  String full(path);
  if (!full.startsWith("/")) {
    full = "/" + full;
  }

  int start = 1;
  while (start > 0 && start < (int)full.length()) {
    int slash = full.indexOf('/', start);
    String partial = (slash < 0) ? full : full.substring(0, slash);

    if (!SD.exists(partial.c_str())) {
      Serial.printf("Creating directory: %s\r\n", partial.c_str());
      if (!SD.mkdir(partial.c_str())) {
        Serial.printf("ERROR: mkdir failed for %s\r\n", partial.c_str());
        return false;
      }
    }

    if (slash < 0) {
      break;
    }
    start = slash + 1;
  }

  return true;
}

static bool rewriteFile(const char* path, const char* payload) {
  if (SD.exists(path)) {
    Serial.printf("WARNING: %s exists ... erasing\r\n", path);
    if (!SD.remove(path)) {
      Serial.printf("ERROR: failed to erase %s\r\n", path);
      return false;
    }
  }

  File f = SD.open(path, FILE_WRITE);
  if (!f) {
    Serial.printf("ERROR: failed to create %s\r\n", path);
    return false;
  }

  size_t wrote = f.println(payload);
  f.close();

  if (wrote == 0) {
    Serial.printf("ERROR: write failed for %s\r\n", path);
    return false;
  }

  Serial.printf("Wrote file: %s\r\n", path);
  return true;
}

static void permissionsDemo(const char* path) {
  Serial.println("Permissions demo:");
  Serial.println("  SD/FAT does not support Unix permissions (chmod/chown).");
  Serial.println("  Access control is by open mode (FILE_READ/FILE_WRITE).");

  File r = SD.open(path, FILE_READ);
  if (!r) {
    Serial.printf("  Could not open %s as FILE_READ\r\n", path);
    return;
  }

  Serial.printf("  FILE_READ open succeeded, size=%u bytes\r\n", (unsigned)r.size());
  size_t writeInReadMode = r.print("attempt write while opened read-only");
  if (writeInReadMode == 0) {
    Serial.println("  As expected, write via FILE_READ handle was blocked.");
  } else {
    Serial.printf("  NOTE: write via FILE_READ returned %u (unexpected)\r\n", (unsigned)writeInReadMode);
  }
  r.close();
}

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

  Serial.println("\r\n==================================================");
  Serial.println("Exercise 04: SD card test loop");
  Serial.println("==================================================");
  Serial.printf("Pins: CS=%d SCK=%d MISO=%d MOSI=%d\r\n",
                tbeam_supreme::sdCs(), tbeam_supreme::sdSck(),
                tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi());
  Serial.printf("PMU I2C: SDA1=%d SCL1=%d\r\n",
                tbeam_supreme::i2cSda(), tbeam_supreme::i2cScl());
  Serial.println("Note: SD must be FAT16/FAT32 for Arduino SD library.\r\n");

  initPmuForSdPower();
}

void loop() {
  Serial.println("Sleeping 10 seconds");
  delay(10000);

  if (!mountCard()) {
    Serial.println("SD step skipped this cycle.\r\n");
    return;
  }

  printCardInfo();

  if (!rewriteFile(kRootTestFile, kPayload)) {
    SD.end();
    Serial.println("Cycle ended due to root file error.\r\n");
    return;
  }

  if (!ensureDirRecursive(kNestedDir)) {
    SD.end();
    Serial.println("Cycle ended due to directory creation error.\r\n");
    return;
  }

  if (!rewriteFile(kNestedTestFile, kPayload)) {
    SD.end();
    Serial.println("Cycle ended due to nested file error.\r\n");
    return;
  }

  permissionsDemo(kRootTestFile);
  SD.end();
  Serial.println("Cycle complete.\r\n");
}
