#include <Keypad.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <FS.h>
#include <LittleFS.h>

// --- Configurable Constants ---
#define PASSCODE_FILE "/pass.txt"
#define DEFAULT_CODE "#D36"
#define HOLD_TIME_MS 2500 
#define RELAY_PIN 5            // Relay successfully moved to safe GPIO 5
#define RELAY_ON_TIME_MS 3000  // Relay trigger duration (3 seconds)

// --- Keypad Setup ---
const byte ROWS = 4; 
const byte COLS = 4; 
char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};
byte rowPins[ROWS] = {13, 12, 14, 27}; 
byte colPins[COLS] = {26, 25, 33, 32}; 
Keypad customKeypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// --- TFT Setup ---
TFT_eSPI tft = TFT_eSPI(); 

// --- State Machine ---
enum SystemState { 
  STATE_IDLE, 
  STATE_ENTER_PASSCODE, 
  STATE_AUTH_FOR_CHANGE, 
  STATE_CHANGE_PASSCODE, 
  STATE_VERIFY_PASSCODE 
};
SystemState currentState = STATE_IDLE;

String currentPasscode = DEFAULT_CODE;
String enteredInput = "";
String newPasscodeAttempt = "";

// Timing variables for holding '#'
unsigned long hashPressStartTime = 0;
bool isHashBeingHeld = false;

void setup() {
  Serial.begin(115200);
  
  // Set up the hardware relay pin control
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); // Start with relay deactivated (Locked)
  
  tft.init();
  tft.setRotation(0);
  
  if (!LittleFS.begin(true)) {
    Serial.println("LittleFS Mount Failed!");
  } else {
    loadPasscode();
  }

  showIdleScreen();
}

void loop() {
  char key = customKeypad.getKey();
  KeyState kstate = customKeypad.getState();

  // --- 1. SPECIAL '#' HANDLING IN IDLE STATE ---
  if (currentState == STATE_IDLE) {
    if (key == '#' && kstate == PRESSED) {
      isHashBeingHeld = true;
      hashPressStartTime = millis();
    }
    
    if (isHashBeingHeld && (kstate == HOLD || kstate == PRESSED)) {
      if (millis() - hashPressStartTime >= HOLD_TIME_MS) {
        isHashBeingHeld = false; 
        enteredInput = "";
        currentState = STATE_AUTH_FOR_CHANGE; 
        updateDisplay(); 
        return; 
      }
    }
    
    if (kstate == RELEASED && isHashBeingHeld) {
      isHashBeingHeld = false;
      currentState = STATE_ENTER_PASSCODE;
      enteredInput = "#"; 
      updateDisplay(); 
      return;
    }
  }

  // --- 2. PROCESS NORMAL KEYPRESSES ---
  if (key != NO_KEY) {
    
    if (currentState == STATE_IDLE && key != '#') {
      currentState = STATE_ENTER_PASSCODE;
      enteredInput = String(key); 
      updateDisplay();
      return; 
    }

    if (currentState == STATE_ENTER_PASSCODE || currentState == STATE_AUTH_FOR_CHANGE || 
        currentState == STATE_CHANGE_PASSCODE || currentState == STATE_VERIFY_PASSCODE) {
      
      if (enteredInput.length() < 4) {
        enteredInput += key;
        updateDisplay(); 
      }

      if (enteredInput.length() == 4) {
        delay(400); 
        
        if (currentState == STATE_ENTER_PASSCODE) {
          if (enteredInput == currentPasscode) {
            triggerRelay();
          } else {
            flashMessage("WRONG PASSCODE", TFT_RED);
          }
          resetToIdle();
        } 
        else if (currentState == STATE_AUTH_FOR_CHANGE) {
          if (enteredInput == currentPasscode) {
            enteredInput = "";
            currentState = STATE_CHANGE_PASSCODE;
            updateDisplay();
          } else {
            flashMessage("WRONG PASSCODE", TFT_RED);
            resetToIdle();
          }
        }
        else if (currentState == STATE_CHANGE_PASSCODE) { // Safe local matching string
          newPasscodeAttempt = enteredInput;
          enteredInput = "";
          currentState = STATE_VERIFY_PASSCODE;
          updateDisplay();
        } 
        else if (currentState == STATE_VERIFY_PASSCODE) {
          if (enteredInput == newPasscodeAttempt) {
            currentPasscode = newPasscodeAttempt;
            savePasscode(currentPasscode);
            
            tft.fillScreen(TFT_BLACK);
            tft.setTextColor(TFT_GREEN, TFT_BLACK);
            tft.setTextDatum(MC_DATUM);
            tft.drawString("Passcode Changed", 120, 100, 4);
            tft.drawString(currentPasscode, 120, 140, 4);
            delay(2500);
            resetToIdle();
          } else {
            flashMessage("MISMATCH! TRY AGAIN", TFT_RED);
            enteredInput = "";
            currentState = STATE_CHANGE_PASSCODE; 
            updateDisplay();
          }
        }
      }
    }
  }
}

// --- 3. Hardware Interfacing Actions ---
void triggerRelay() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.drawString("ACCESS GRANTED", 120, 120, 4);
  
  digitalWrite(RELAY_PIN, HIGH); // Turn relay ON (Unlock fence)
  delay(RELAY_ON_TIME_MS);        // Hold for 3 seconds
  digitalWrite(RELAY_PIN, LOW);  // Turn relay OFF (Lock fence)
}

// --- 4. Screen Drawing ---
void showIdleScreen() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.drawString("10:08", 120, 120, 7); 
}

void updateDisplay() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(TC_DATUM);

  if (currentState == STATE_ENTER_PASSCODE) {
    tft.drawString("Enter Passcode", 120, 50, 4);
  } else if (currentState == STATE_AUTH_FOR_CHANGE) {
    tft.drawString("Enter Passcode", 120, 35, 4);
    tft.drawString("To Change Passcode", 120, 65, 2);
  } else if (currentState == STATE_CHANGE_PASSCODE) {
    tft.drawString("Change Passcode", 120, 50, 4);
  } else if (currentState == STATE_VERIFY_PASSCODE) {
    tft.drawString("Re-enter Passcode", 120, 50, 4);
  }

  int startX = 60;
  int spacing = 40;
  tft.setTextDatum(MC_DATUM);

  for (int i = 0; i < 4; i++) {
    int currentX = startX + (i * spacing);
    if (i < enteredInput.length()) {
      tft.drawString("*", currentX, 130, 4);  
    } else {
      tft.drawString("_", currentX, 130, 4);  
    }
  }
}

void flashMessage(String msg, uint16_t color) {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(color, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.drawString(msg, 120, 120, 4);
  delay(2000);
}

void resetToIdle() {
  enteredInput = "";
  newPasscodeAttempt = "";
  isHashBeingHeld = false; 
  currentState = STATE_IDLE;
  showIdleScreen();
}

// --- 5. Storage Persistence Layer ---
void loadPasscode() {
  if (LittleFS.exists(PASSCODE_FILE)) {
    File file = LittleFS.open(PASSCODE_FILE, "r");
    if (file) {
      String saved = file.readStringUntil('\n');
      saved.trim();
      if (saved.length() == 4) {
        currentPasscode = saved;
        Serial.println("Loaded custom passcode: " + currentPasscode);
      }
      file.close();
    }
  }
}

void savePasscode(String newCode) {
  File file = LittleFS.open(PASSCODE_FILE, "w");
  if (file) {
    file.println(newCode);
    file.close();
    Serial.println("Saved custom passcode to LittleFS: " + newCode);
  }
}
