#include <Arduino.h>
#include <SPI.h>
#include "SdFat.h"

// ====================== CONFIG ======================
#define SAMPLE_DELAY_DEFAULT_MS  1000UL
#define SD_BUFFER_BYTES          256
#define SD_FLUSH_MS              30000UL
#define SD_FLUSH_LINES           8
#define BUTTON_HOLD_MS_ACK       1000UL
#define BUTTON_HOLD_MS_START     2000UL
#define STOP_HOLD_MS_FINAL       3200UL
#define BEEP_TOGGLE_MS           100
#define LED_BLINK_PERIOD_MS      1000UL
#define MINUTE_MS                60000UL
#define FILE_BASE_NAME           "Log_"

#define ADC_correctionFactor     1.06

#define ADC_REF_VOLTAGE  1.1f   // Using internal 1.1V reference

#define BUTTON_HOLD_MS_ACK       1000UL   // confirmation delay (stage 2 start)
#define BUTTON_HOLD_MS_START     2000UL   // action delay (stage 3 start)


#define PIN_BTN1   2
#define PIN_BTN2   3
#define PIN_LED_G  5
#define PIN_LED_R  6
#define PIN_BUZZER 9
#define PIN_SD_CS  10
#define STATUS_LED 13

#define BUZZ_ON(state) digitalWrite(PIN_BUZZER, !(state))

// ====================== GLOBALS ======================
SdFat sd;
SdFile dataFile;

uint8_t Sdcheck = 0;
uint8_t channelMask = 0;
uint32_t sampleDelayMs = SAMPLE_DELAY_DEFAULT_MS;
char currentFileName[13];
uint8_t sdBuffer[SD_BUFFER_BYTES];
size_t sdBufferLen = 0;
uint16_t sdBufferLines = 0;
uint32_t sdLastFlushMs = 0;

bool serialAtStartup = false;
bool anyOverVoltage = false;
bool loggingActive = false;
bool buzzEnabled = true;
bool ledEnabled  = true;
bool serialConnected = false;
bool ledOverrideActive = false;      // when true, BlinkLed() won't modify LEDs
bool savedLedEnabled = true;         // stored user LED state while both-press active
bool savedBuzzEnabled = true;        // stored user buzzer state while both-press active
bool skipFileCreation = false;





float chValues[8];
// ====================== HELPERS ======================
#define CHANNEL_ENABLED(i) ((channelMask & (1u << (7 - (i)))) != 0)
#define TS() Serial.print(millis()), Serial.print(',')

// -----------------------------------------------------------------------------
// Simple blocking beep
// -----------------------------------------------------------------------------
void beep(float times[], int count) {
  bool state = HIGH;
  int len = count / sizeof(times[0]);  
  for (int i = 0; i < len; i++) {
    digitalWrite(PIN_BUZZER, state);
    delay(times[i] * 1000);
    state = !state;
  }
  digitalWrite(PIN_BUZZER, LOW);
}

// -----------------------------------------------------------------------------
// Generate next file name (Log_00.csv, Log_01.csv, ...)
// -----------------------------------------------------------------------------
void makeNextFileName(char* outName) {
  const uint8_t baseLen = strlen(FILE_BASE_NAME);
  memcpy(outName, FILE_BASE_NAME, baseLen);
  outName[baseLen] = '0';
  outName[baseLen + 1] = '0';
  strcpy(outName + baseLen + 2, ".csv");

  while (sd.exists(outName)) {
    if (outName[baseLen + 1] != '9') outName[baseLen + 1]++;
    else if (outName[baseLen] != '9') {
      outName[baseLen + 1] = '0';
      outName[baseLen]++;
    } else break;
  }
}

// -----------------------------------------------------------------------------
// Read delay configuration
// -----------------------------------------------------------------------------
bool readDelayConfig() {
  File32 f;

  if (!f.open("dataloggerConfig_delay.csv", FILE_READ)) {
    Serial.println(F("Delay config not found → using default 1000 ms"));
    sampleDelayMs = SAMPLE_DELAY_DEFAULT_MS;
    return false;
  }

  char buf[32];
  int n = f.read(buf, sizeof(buf) - 1);
  f.close();
  buf[n] = '\0';

  sampleDelayMs = strtoul(buf, NULL, 10);
  if (sampleDelayMs == 0) sampleDelayMs = SAMPLE_DELAY_DEFAULT_MS;

  Serial.print(F("Delay set to "));
  Serial.print(sampleDelayMs);
  Serial.println(F(" ms"));
  return true;
}

// -----------------------------------------------------------------------------
// Read channel configuration file
// -----------------------------------------------------------------------------
bool readChannelConfig() {
  File32 f;
  if (!f.open("dataloggerConfig_channel.csv", FILE_READ)) {
    Serial.println(F("Channel config not found → all active."));
    channelMask = 0xFF;
    return false;
  }

  channelMask = 0;
  char line[32];
  while (f.fgets(line, sizeof(line)) > 0) {
    int ch = atoi(line) - 1;
    if (ch >= 0 && ch < 8) {
      if (strchr(line, 'Y') || strchr(line, 'y')) channelMask |= (1 << (7 - ch));
    }
  }
  f.close();

  Serial.print(F("Active channels: "));
  for (uint8_t i = 0; i < 8; i++)
    if (CHANNEL_ENABLED(i)) { Serial.print(i + 1); Serial.print(' '); }
  Serial.println();
  return true;
}



// -----------------------------------------------------------------------------
// Find the latest existing log file (highest xx in Log_xx.csv)
// -----------------------------------------------------------------------------
bool findLatestLogFile(char *outName) {
  char tempName[13];
  for (int i = 0; i < 100; i++) {
    sprintf(tempName, "Log_%02d.csv", i);
    if (!sd.exists(tempName)) {     // first missing file → previous one is latest
      if (i == 0) return false;     // no logs exist at all
      sprintf(outName, "Log_%02d.csv", i - 1);
      return true;
    }
  }
  return false;  // fallback (shouldn’t happen)
}

// -----------------------------------------------------------------------------
// Append POWER LOSS RECOVERY marker to the last log file
// -----------------------------------------------------------------------------
bool appendRecoveryMarker(const char *fileName) {
  SdFile f;
  if (!f.open(fileName, O_WRITE | O_APPEND)) return false;

  f.println();
  f.println(F("POWER LOSS RECOVERY"));
  f.println();
  f.sync();
  f.close();

  Serial.print(F("POWER LOSS RECOVERY in "));
  Serial.println(fileName);
  return true;
}



// -----------------------------------------------------------------------------
// Create next available log file
// -----------------------------------------------------------------------------
void createNewFile() {
  makeNextFileName(currentFileName);   // always get the next unused name

  // close any previous file (just in case)
  if (dataFile.isOpen()) {
    dataFile.close();
    delay(50);
  }

  Serial.print(F("Creating new file: "));
  Serial.println(currentFileName);

  if (!dataFile.open(currentFileName, O_WRONLY | O_CREAT | O_EXCL)) {
    Serial.println(F("⚠ ERROR: could not create new log file!"));
    Sdcheck = 0;
    return;
  }

  delay(50); // SD settle
  TS(); Serial.print(F("NEW_FILE_OK,"));
  Serial.println(currentFileName);
}


// -----------------------------------------------------------------------------
// Write CSV header
// -----------------------------------------------------------------------------
void writeHeader() {
  dataFile.println("DataLogger Created by Harshad Byadgi");
  dataFile.println("");
  dataFile.print(F("Time(ms)"));
  Serial.print(F("Time(ms)"));
  for (uint8_t i = 0; i < 8; i++) {
    if (CHANNEL_ENABLED(i)) {
      dataFile.print(F(",Ch"));
      dataFile.print(i + 1);
      dataFile.print(F("(V)"));
      Serial.print(F(",Ch"));
      Serial.print(i + 1);
      Serial.print(F("(V)"));
    }
  }
  dataFile.println();
  Serial.println();
  dataFile.sync();
}

// -----------------------------------------------------------------------------
// SD check and initialisation
// -----------------------------------------------------------------------------
bool CheckSD() {
  TS(); Serial.println("SD_CHECK");
  BUZZ_ON(false);
  if (!sd.begin(PIN_SD_CS, SD_SCK_MHZ(25))) {
    Sdcheck = 0;
    Serial.println("check3");
    TS(); Serial.println("SD_FAIL,INIT");
    Serial.println("check4");
    return false;
  }

  readDelayConfig();
  readChannelConfig();

  if (!skipFileCreation) {
    createNewFile();
    writeHeader();
  }


  TS(); Serial.print("SD_OK,");
  Serial.println(currentFileName);
  Sdcheck = 1;
  return true;
}

// -----------------------------------------------------------------------------
// Record data (direct print, fast)
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// Flush SD buffer if needed
// -----------------------------------------------------------------------------
bool sdFlushBuffer() {
  if (!dataFile.sync()) return false;
  sdLastFlushMs = millis();
  return true;
}

// -----------------------------------------------------------------------------
// Beep
// -----------------------------------------------------------------------------
void PlayBlockingBeep() {
  BUZZ_ON(true); delay(BEEP_TOGGLE_MS); BUZZ_ON(false);
}

// -----------------------------------------------------------------------------
// Button control logic
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// Setup
// -----------------------------------------------------------------------------
void setup() {
  pinMode(STATUS_LED, OUTPUT);
  pinMode(PIN_BTN1, INPUT_PULLUP);
  pinMode(PIN_BTN2, INPUT_PULLUP);
  pinMode(PIN_LED_G, OUTPUT);
  pinMode(PIN_LED_R, OUTPUT);
  pinMode(PIN_BUZZER, OUTPUT);

  analogReference(INTERNAL);   // use the internal 1.1V reference
  delay(50);                   // allow reference to stabilise


  digitalWrite(PIN_BUZZER, LOW);

  Serial.begin(115200);

  serialAtStartup=Serial;
  Serial.println(F("=== SdFat Datalogger with SD-Removal + Auto-Reconnect ==="));

  // Prevent CheckSD() from creating a new file if recovery is needed
  skipFileCreation = true;

  if (CheckSD()) {
    TS(); Serial.println("SD_READY,INIT_OK");

    // --- Check for loggingStatus.csv ---
    File32 statusFile;
    bool resumeLogging = false;
    char c = '0';

    if (!statusFile.open("loggingStatus.csv", FILE_READ)) {
      Serial.println(F("loggingStatus.csv not found — creating new"));
      File32 newFile;
      if (newFile.open("loggingStatus.csv", O_WRITE | O_CREAT | O_TRUNC)) {
        newFile.print('0');
        newFile.close();
      }
    } else {
      c = statusFile.read();
      statusFile.close();
      if (c == '1') resumeLogging = true;
    }

    // --- If previous logging was active, recover ---
    if (resumeLogging) {
      Serial.println(F("Detected previous active logging — recovering..."));
      char lastFile[13];
      if (findLatestLogFile(lastFile)) {
        if (appendRecoveryMarker(lastFile)) {
          delay(100); // allow SD to settle before reopening
          if (!dataFile.open(lastFile, O_WRITE | O_APPEND)) {
            Serial.println(F("⚠ Could not reopen last file for logging!"));
          } else {
            loggingActive = true;
            digitalWrite(PIN_LED_G, HIGH);
            digitalWrite(PIN_LED_R, LOW);

            // Indicate recovery (1s solid green, then 10 beeps)
            delay(1000);
            for (int i = 0; i < 10; i++) {
              BUZZ_ON(true); delay(200);
              BUZZ_ON(false); delay(200);
            }
            Serial.println(F("POWER LOSS RECOVERY COMPLETE — RESUMING LOGGING"));
          }
        }
      }
    }
    else {
      // Normal idle startup
      digitalWrite(PIN_LED_R, LOW);
      digitalWrite(PIN_LED_G, HIGH);
    }
  }
  else {
    // --- SD missing at startup ---
    digitalWrite(PIN_LED_R, HIGH);
    for (int i = 0; i < 5; i++) {
      BUZZ_ON(true); delay(200);
      BUZZ_ON(false); delay(200);
    }
    TS(); Serial.println("SD_FAIL,NO_CARD");
  }

  // Allow file creation again after startup
  skipFileCreation = false;

  analogReference(DEFAULT);
  analogRead(A0);
  delay(5);
  sdLastFlushMs = millis();
  TS(); Serial.println("SETUP_DONE");
}


// -----------------------------------------------------------------------------
// Main Loop
// -----------------------------------------------------------------------------
void loop() {
  if (!loggingActive) {
  SerialMonitorMode();   // live serial output when idle
  }


  static uint32_t lastSDCheck = 0;
  if (!loggingActive && (millis() - lastSDCheck > 2000)) {
    lastSDCheck = millis();
    if (!Sdcheck) {
      if (CheckSD()) {
        TS(); Serial.println("SD_RECONNECTED");
        PlayBlockingBeep();
        digitalWrite(PIN_LED_R, LOW);
        digitalWrite(PIN_LED_G, HIGH);  
      }
    }
  }

  CheckButtons();

  static uint32_t lastSample = 0;
  if (loggingActive && Sdcheck) {
    uint32_t now = millis();
    if (now - lastSample >= sampleDelayMs) {
      lastSample = now;
      RecordData();
      sdFlushBuffer();
    }
  }

  BlinkLed();
}
