        
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal.I2C.h>
#include <Wire.h>
#include <EEPROM.h>

// RFID pins - UPDATED for your hardware
#define SS_PIN    5     // ESP32 pin GPIO5 connected to SDA/SS
#define RST_PIN   4     // ESP32 pin GPIO4 connected to RST (CHANGED from 22)
#define BUZZER_PIN 2    // You can adjust this as needed
#define REGISTER_BUTTON_PIN 15  // Button to enter registration mode

// I2C display address and dimensions
#define LCD_ADDR 0x27
#define LCD_COLS 16
#define LCD_ROWS 2

// EEPROM size
#define EEPROM_SIZE 1024
#define MAX_USERS 20
#define MAX_NAME_LENGTH 20
#define UID_LENGTH 14   // Format: "XX XX XX XX XX XX XX" (2 chars per byte + spaces)

// Initialize RFID and LCD
MFRC522 rfid(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);

// Database of authorized RFID cards (ID, Name)
struct User {
  char uid[UID_LENGTH + 1];  // +1 for null terminator
  char name[MAX_NAME_LENGTH + 1];  // +1 for null terminator
  bool isPresent;
  unsigned long checkInTime;
};

User users[MAX_USERS];
int numUsers = 0;
int attendanceCount = 0;
bool registrationMode = false;

void setup() {
  Serial.begin(115200);
  while (!Serial);  // Wait for serial to be ready
  Serial.println("Starting setup...");
  
  // Initialize EEPROM
  EEPROM.begin(EEPROM_SIZE);
  
  // Initialize SPI bus
  SPI.begin();
  
  // Initialize RFID reader
  rfid.PCD_Init();
  
  // Test RFID reader
  byte v = rfid.PCD_ReadRegister(MFRC522::VersionReg);
  if (v == 0x00 || v == 0xFF) {
    Serial.println("WARNING: RFID reader not detected! Check connections.");
    Serial.println("SS_PIN (SDA): GPIO5, RST_PIN: GPIO4, MOSI: GPIO23, MISO: GPIO19, SCK: GPIO18");
  } else {
    Serial.print("RFID reader detected. Version: 0x");
    Serial.println(v, HEX);
    Serial.println("RFID reader initialized successfully");
  }
  
  // Initialize I2C LCD
  Wire.begin();
  lcd.init();
  lcd.backlight();
  Serial.println("LCD initialized");
  
  // Initialize buzzer and register button
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(REGISTER_BUTTON_PIN, INPUT_PULLUP);
  Serial.println("Button and buzzer initialized");
  
  // Load users from EEPROM
  loadUsersFromEEPROM();
  
  // Show welcome message
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Smart Attendance");
  lcd.setCursor(0, 1);
  lcd.print("System Ready");
  
  Serial.println("\n=== Smart Attendance System ===");
  Serial.println("Commands:");
  Serial.println("- 'list': Show all registered users");
  Serial.println("- 'register': Enter registration mode");
  Serial.println("- 'delete X': Delete user at index X");
  Serial.println("============================\n");
  
  delay(2000);
  
  // Display attendance info
  updateAttendanceDisplay();
  Serial.println("Setup complete. System ready.");
}

void loop() {
  // Check for serial commands
  checkSerialCommands();
  
  // Check if registration button is pressed
  if (digitalRead(REGISTER_BUTTON_PIN) == LOW) {
    delay(50);  // Debounce
    if (digitalRead(REGISTER_BUTTON_PIN) == LOW) {
      registrationMode = !registrationMode;
      if (registrationMode) {
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Registration Mode");
        lcd.setCursor(0, 1);
        lcd.print("Scan new card...");
        Serial.println("Entered registration mode. Scan a new card.");
      } else {
        updateAttendanceDisplay();
        Serial.println("Exited registration mode.");
      }
      while (digitalRead(REGISTER_BUTTON_PIN) == LOW) { delay(10); }  // Wait for button release
    }
  }
  
  // Check if a new card is present
  if (!rfid.PICC_IsNewCardPresent()) {
    return;
  }
  
  // Read the card
  if (!rfid.PICC_ReadCardSerial()) {
    return;
  }
  
  // Get card UID
  String cardUID = getCardUID();
  Serial.print("Card detected: ");
  Serial.println(cardUID);
  
  if (registrationMode) {
    registerNewUser(cardUID);
  } else {
    processAttendance(cardUID);
  }
  
  // Halt PICC and stop encryption
  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();
}

void registerNewUser(String cardUID) {
  // First check if this card is already registered
  int userIndex = findUserByUID(cardUID);
  
  if (userIndex != -1) {
    // Card already registered
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Already Exists!");
    lcd.setCursor(0, 1);
    lcd.print(users[userIndex].name);
    
    Serial.println("Card already registered to: " + String(users[userIndex].name));
    errorBeep();
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Registration Mode");
    lcd.setCursor(0, 1);
    lcd.print("Scan new card...");
    return;
  }
  
  if (numUsers >= MAX_USERS) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Error: Database");
    lcd.setCursor(0, 1);
    lcd.print("Full!");
    
    Serial.println("Error: Maximum users reached!");
    errorBeep();
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Registration Mode");
    lcd.setCursor(0, 1);
    lcd.print("Scan new card...");
    return;
  }
  
  // Prompt for name input
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Enter name via");
  lcd.setCursor(0, 1);
  lcd.print("Serial Monitor");
  
  Serial.println("Enter student name (max 20 chars):");
  
  // Wait for name input from serial
  String name = "";
  while (name == "") {
    if (Serial.available()) {
      name = Serial.readStringUntil('\n');
      name.trim();
      
      if (name == "") {
        Serial.println("Name cannot be empty. Try again:");
      } else if (name.length() > MAX_NAME_LENGTH) {
        Serial.println("Name too long (max 20 chars). Try again:");
        name = "";
      }
    }
    delay(10);
  }
  
  // Add the new user
  cardUID.toCharArray(users[numUsers].uid, UID_LENGTH + 1);
  name.toCharArray(users[numUsers].name, MAX_NAME_LENGTH + 1);
  users[numUsers].isPresent = false;
  users[numUsers].checkInTime = 0;
  
  numUsers++;
  
  // Save to EEPROM
  saveUsersToEEPROM();
  
  // Display confirmation
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("User Registered!");
  lcd.setCursor(0, 1);
  lcd.print(name);
  
  Serial.println("Registered: " + name + " with UID: " + cardUID);
  successBeep();
  
  delay(2000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Registration Mode");
  lcd.setCursor(0, 1);
  lcd.print("Scan new card...");
}

void processAttendance(String cardUID) {
  // Check if card is authorized
  int userIndex = findUserByUID(cardUID);
  
  if (userIndex != -1) {
    // Card is authorized
    if (!users[userIndex].isPresent) {
      // User is checking in
      users[userIndex].isPresent = true;
      users[userIndex].checkInTime = millis();
      attendanceCount++;
      
      // Success beep
      successBeep();
      
      // Display user's name
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Welcome");
      lcd.setCursor(0, 1);
      lcd.print(users[userIndex].name);
      
      Serial.println("Attendance marked for: " + String(users[userIndex].name));
    } else {
      // User is checking out
      users[userIndex].isPresent = false;
      attendanceCount--;
      
      // Calculate duration
      unsigned long duration = (millis() - users[userIndex].checkInTime) / 1000; // in seconds
      
      // Goodbye beep
      successBeep();
      
      // Display checkout message
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Goodbye");
      lcd.setCursor(0, 1);
      lcd.print(users[userIndex].name);
      
      Serial.print("Checkout: " + String(users[userIndex].name));
      Serial.print(" Duration: ");
      Serial.print(duration / 60); // minutes
      Serial.print("m ");
      Serial.print(duration % 60); // seconds
      Serial.println("s");
    }
    
    delay(2000); // Show welcome/goodbye message for 2 seconds
    updateAttendanceDisplay(); // Return to attendance display
  } else {
    // Card is not authorized
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Unknown Card");
    lcd.setCursor(0, 1);
    lcd.print(cardUID);
    
    // Error beep
    errorBeep();
    
    Serial.println("Unauthorized card: " + cardUID);
    delay(2000);
    updateAttendanceDisplay();
  }
}

// Get card UID as a string
String getCardUID() {
  String cardID = "";
  for (byte i = 0; i < rfid.uid.size; i++) {
    cardID.concat(String(rfid.uid.uidByte[i] < 0x10 ? " 0" : " "));
    cardID.concat(String(rfid.uid.uidByte[i], HEX));
  }
  cardID.toUpperCase();
  return cardID.substring(1); // Remove leading space
}

// Find user by UID
int findUserByUID(String uid) {
  for (int i = 0; i < numUsers; i++) {
    if (String(users[i].uid) == uid) {
      return i;
    }
  }
  return -1; // User not found
}

// Update attendance display
void updateAttendanceDisplay() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Present: ");
  lcd.print(attendanceCount);
  lcd.setCursor(0, 1);
  lcd.print("Scan your card");
}

// Success beep pattern
void successBeep() {
  digitalWrite(BUZZER_PIN, HIGH);
  delay(200);
  digitalWrite(BUZZER_PIN, LOW);
}

// Error beep pattern
void errorBeep() {
  for (int i = 0; i < 3; i++) {
    digitalWrite(BUZZER_PIN, HIGH);
    delay(100);
    digitalWrite(BUZZER_PIN, LOW);
    delay(100);
  }
}

// Save users to EEPROM
void saveUsersToEEPROM() {
  // Save number of users first (2 bytes)
  EEPROM.write(0, numUsers & 0xFF);
  EEPROM.write(1, (numUsers >> 8) & 0xFF);
  
  // Calculate size of each user
  int userSize = sizeof(User) - sizeof(bool) - sizeof(unsigned long);
  // We don't store isPresent and checkInTime in EEPROM
  
  int address = 2;  // Start after number of users
  for (int i = 0; i < numUsers; i++) {
    // Store UID
    for (int j = 0; j < UID_LENGTH + 1; j++) {
      EEPROM.write(address++, users[i].uid[j]);
    }
    
    // Store name
    for (int j = 0; j < MAX_NAME_LENGTH + 1; j++) {
      EEPROM.write(address++, users[i].name[j]);
    }
  }
  
  EEPROM.commit();
  Serial.println("Users saved to EEPROM");
}

// Load users from EEPROM
void loadUsersFromEEPROM() {
  // Read number of users first (2 bytes)
  int lsb = EEPROM.read(0);
  int msb = EEPROM.read(1);
  numUsers = (msb << 8) | lsb;
  
  // Check if numUsers is valid
  if (numUsers < 0 || numUsers > MAX_USERS) {
    numUsers = 0;
    Serial.println("Invalid user count in EEPROM. Starting with empty database.");
    return;
  }
  
  // Calculate size of each user (excluding runtime variables)
  int userSize = (UID_LENGTH + 1) + (MAX_NAME_LENGTH + 1);
  
  int address = 2;  // Start after number of users
  for (int i = 0; i < numUsers; i++) {
    // Read UID
    for (int j = 0; j < UID_LENGTH + 1; j++) {
      users[i].uid[j] = EEPROM.read(address++);
    }
    
    // Read name
    for (int j = 0; j < MAX_NAME_LENGTH + 1; j++) {
      users[i].name[j] = EEPROM.read(address++);
    }
    
    // Initialize runtime variables
    users[i].isPresent = false;
    users[i].checkInTime = 0;
  }
  
  Serial.print("Loaded ");
  Serial.print(numUsers);
  Serial.println(" users from EEPROM");
  
  // Print all users
  for (int i = 0; i < numUsers; i++) {
    Serial.print(i);
    Serial.print(": ");
    Serial.print(users[i].name);
    Serial.print(" (");
    Serial.print(users[i].uid);
    Serial.println(")");
  }
}

// Check for serial commands
void checkSerialCommands() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    command.trim();
    
    if (command == "list") {
      // List all users
      Serial.println("\n=== Registered Users ===");
      if (numUsers == 0) {
        Serial.println("No users registered.");
      } else {
        for (int i = 0; i < numUsers; i++) {
          Serial.print(i);
          Serial.print(": ");
          Serial.print(users[i].name);
          Serial.print(" (");
          Serial.print(users[i].uid);
          Serial.println(")");
        }
      }
      Serial.println("=======================\n");
    } else if (command == "register") {
      // Enter registration mode
      registrationMode = true;
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Registration Mode");
      lcd.setCursor(0, 1);
      lcd.print("Scan new card...");
      Serial.println("Entered registration mode. Scan a new card.");
    } else if (command.startsWith("delete ")) {
      // Delete a user
      int index = command.substring(7).toInt();
      if (index >= 0 && index < numUsers) {
        Serial.print("Deleting user: ");
        Serial.println(users[index].name);
        
        // Shift all users after this one up by one
        for (int i = index; i < numUsers - 1; i++) {
          memcpy(&users[i], &users[i+1], sizeof(User));
        }
        numUsers--;
        saveUsersToEEPROM();
        Serial.println("User deleted successfully.");
      } else {
        Serial.println("Invalid user index.");
      }
    }
  }
}