/*
 * Secure IoT Access Control System
 * Demonstrates: RFID-style authentication, role-based access control,
 *               lockout policies, and comprehensive audit logging
 */
#include <Arduino.h>

// Pin definitions
const int LED_ACCESS_GRANTED = 2;   // Green LED
const int LED_ACCESS_DENIED = 4;    // Red LED
const int LED_SYSTEM_STATUS = 5;    // Yellow LED
const int LED_ADMIN_MODE = 18;      // Blue LED
const int BUZZER_PIN = 19;
const int BUTTON_SELECT = 15;
const int BUTTON_ACCESS = 16;

// Access levels and credential structure
enum AccessLevel { ACCESS_NONE=0, ACCESS_GUEST=1, ACCESS_USER=2, ACCESS_ADMIN=3 };

struct UserCredential {
    const char* cardId;
    const char* userName;
    AccessLevel accessLevel;
    bool isActive;
};


const int NUM_USERS = 6;
UserCredential userDatabase[NUM_USERS] = {
    {"RFID_ADMIN_001", "Alice Admin", ACCESS_ADMIN, true},
    {"RFID_ADMIN_002", "Bob Admin", ACCESS_ADMIN, true},
    {"RFID_USER_001", "Charlie User", ACCESS_USER, true},
    {"RFID_USER_002", "Diana User", ACCESS_USER, true},
    {"RFID_GUEST_001", "Eve Guest", ACCESS_GUEST, true},
    {"RFID_DISABLED", "Frank Former", ACCESS_USER, false}
};

const int NUM_TEST_CARDS = 8;
const char* testCards[NUM_TEST_CARDS] = {
    "RFID_ADMIN_001", "RFID_USER_001", "RFID_GUEST_001", "RFID_DISABLED",
    "RFID_UNKNOWN_1", "RFID_UNKNOWN_2", "RFID_ADMIN_002", "RFID_USER_002"
};

const int MAX_FAILED_ATTEMPTS = 3;
const unsigned long LOCKOUT_DURATION_MS = 60000;
const unsigned long LOCKOUT_ESCALATION_MS = 30000;
const int MAX_LOCKOUT_DURATION_MS = 300000;

struct AccessZone { const char* zoneName; AccessLevel requiredLevel; };
const int NUM_ZONES = 4;
AccessZone accessZones[NUM_ZONES] = {
    {"Public Lobby", ACCESS_GUEST}, {"Office Area", ACCESS_USER},
    {"Server Room", ACCESS_ADMIN},  {"Control Center", ACCESS_ADMIN}
};


struct SecurityState {
    int failedAttempts;
    unsigned long lockoutEndTime;
    bool isLocked;
    int currentCardIndex;
    String lastAuthenticatedUser;
    AccessLevel currentAccessLevel;
    unsigned long sessionStartTime;
} secState;

struct AuditEntry {
    unsigned long timestamp;  const char* cardId;
    const char* userName;     const char* eventType;
    const char* zoneName;     bool success;
    AccessLevel attemptedLevel;
};

const int MAX_AUDIT_ENTRIES = 50;
AuditEntry auditLog[MAX_AUDIT_ENTRIES];
int auditIndex = 0;

unsigned long lastButtonSelectTime = 0;
unsigned long lastButtonAccessTime = 0;
const unsigned long DEBOUNCE_DELAY = 250;

// Forward declarations
void initializeSystem();  void handleButtons();
UserCredential* authenticateCard(const char* cardId);
bool authorizeAccess(AccessLevel userLevel, AccessLevel requiredLevel);
void requestAccess(int zoneIndex);
void logAuditEvent(const char* cardId, const char* userName,
    const char* eventType, const char* zoneName,
    bool success, AccessLevel level);
void printAuditLog();      void handleLockout();
void indicateAccessGranted(AccessLevel level);
void indicateAccessDenied(); void indicateSystemLocked();
void playTone(int frequency, int duration);
bool constantTimeCompare(const char* a, const char* b);
void processSerialCommands(); void printMenu();
const char* accessLevelToString(AccessLevel level);


void setup() {
    Serial.begin(115200);
    delay(1000);
    initializeSystem();
    Serial.println("\n=========================================");
    Serial.println("   IoT ACCESS CONTROL SYSTEM v1.0");
    Serial.println("=========================================");
    printMenu();
}

void loop() {
    handleButtons();
    handleLockout();
    processSerialCommands();
    delay(10);
}

void initializeSystem() {
    pinMode(LED_ACCESS_GRANTED, OUTPUT);
    pinMode(LED_ACCESS_DENIED, OUTPUT);
    pinMode(LED_SYSTEM_STATUS, OUTPUT);
    pinMode(LED_ADMIN_MODE, OUTPUT);
    pinMode(BUZZER_PIN, OUTPUT);
    pinMode(BUTTON_SELECT, INPUT_PULLUP);
    pinMode(BUTTON_ACCESS, INPUT_PULLUP);
    // All LEDs off, reset security state
    for (int pin : {LED_ACCESS_GRANTED, LED_ACCESS_DENIED,
                    LED_SYSTEM_STATUS, LED_ADMIN_MODE})
        digitalWrite(pin, LOW);
    secState = {0, 0, false, 0, "", ACCESS_NONE, 0};
    playTone(1000, 100); delay(100);
    playTone(1500, 100); delay(100);
    playTone(2000, 150);
}


UserCredential* authenticateCard(const char* cardId) {
    for (int i = 0; i < NUM_USERS; i++)
        if (constantTimeCompare(cardId, userDatabase[i].cardId))
            return &userDatabase[i];
    return NULL;
}

bool constantTimeCompare(const char* a, const char* b) {
    size_t lenA = strlen(a), lenB = strlen(b);
    size_t maxLen = (lenA > lenB) ? lenA : lenB;
    volatile int result = 0;
    for (size_t i = 0; i < maxLen; i++) {
        char charA = (i < lenA) ? a[i] : 0;
        char charB = (i < lenB) ? b[i] : 0;
        result |= charA ^ charB;
    }
    result |= (lenA != lenB);
    return (result == 0);
}

bool authorizeAccess(AccessLevel userLevel, AccessLevel required) {
    return userLevel >= required;
}


void requestAccess(int zoneIndex) {
    if (zoneIndex < 0 || zoneIndex >= NUM_ZONES) return;
    const char* cardId = testCards[secState.currentCardIndex];
    AccessZone* zone = &accessZones[zoneIndex];

    Serial.println("\n=== ACCESS REQUEST ===");
    Serial.printf("Card: %s -> Zone: %s (Requires: %s)\n",
        cardId, zone->zoneName,
        accessLevelToString(zone->requiredLevel));

    // Step 1: Check lockout
    if (secState.isLocked) {
        unsigned long remaining =
            (secState.lockoutEndTime - millis()) / 1000;
        Serial.printf("BLOCKED: Locked for %lu more seconds\n",
            remaining);
        logAuditEvent(cardId, "LOCKED_OUT", "ACCESS_BLOCKED",
            zone->zoneName, false, ACCESS_NONE);
        indicateSystemLocked();
        return;
    }

    // Step 2: Authentication -- who are you?
    UserCredential* user = authenticateCard(cardId);
    if (user == NULL) {
        secState.failedAttempts++;
        Serial.printf("AUTH FAILED: Unknown card (%d/%d)\n",
            secState.failedAttempts, MAX_FAILED_ATTEMPTS);
        if (secState.failedAttempts >= MAX_FAILED_ATTEMPTS) {
            unsigned long lockTime = LOCKOUT_DURATION_MS +
                ((secState.failedAttempts - MAX_FAILED_ATTEMPTS)
                 * LOCKOUT_ESCALATION_MS);
            if (lockTime > MAX_LOCKOUT_DURATION_MS)
                lockTime = MAX_LOCKOUT_DURATION_MS;
            secState.isLocked = true;
            secState.lockoutEndTime = millis() + lockTime;
            Serial.println("!!! BRUTE FORCE LOCKOUT !!!");
        }
        logAuditEvent(cardId, "UNKNOWN", "AUTH_FAILED",
            zone->zoneName, false, ACCESS_NONE);
        indicateAccessDenied();
        return;
    }
    if (!user->isActive) {
        logAuditEvent(cardId, user->userName, "ACCOUNT_DISABLED",
            zone->zoneName, false, user->accessLevel);
        indicateAccessDenied();
        return;
    }

    // Step 3: Authorization -- what can you do?
    if (authorizeAccess(user->accessLevel, zone->requiredLevel)) {
        Serial.printf("ACCESS GRANTED: %s -> %s\n",
            user->userName, zone->zoneName);
        secState.failedAttempts = 0;
        secState.lastAuthenticatedUser = user->userName;
        secState.currentAccessLevel = user->accessLevel;
        logAuditEvent(cardId, user->userName, "ACCESS_GRANTED",
            zone->zoneName, true, user->accessLevel);
        indicateAccessGranted(user->accessLevel);
    } else {
        Serial.printf("ACCESS DENIED: %s has %s, needs %s\n",
            user->userName,
            accessLevelToString(user->accessLevel),
            accessLevelToString(zone->requiredLevel));
        logAuditEvent(cardId, user->userName, "ACCESS_DENIED_AUTHZ",
            zone->zoneName, false, user->accessLevel);
        indicateAccessDenied();
    }
}


void handleLockout() {
    if (secState.isLocked && millis() >= secState.lockoutEndTime) {
        secState.isLocked = false;
        secState.failedAttempts = 0;
        Serial.println("Lockout expired -- system unlocked.");
        playTone(1000, 200); delay(100); playTone(1500, 200);
    }
}

void logAuditEvent(const char* cardId, const char* userName,
    const char* eventType, const char* zoneName,
    bool success, AccessLevel level) {
    AuditEntry* e = &auditLog[auditIndex];
    e->timestamp = millis(); e->cardId = cardId;
    e->userName = userName;  e->eventType = eventType;
    e->zoneName = zoneName;  e->success = success;
    e->attemptedLevel = level;
    auditIndex = (auditIndex + 1) % MAX_AUDIT_ENTRIES;
}

void indicateAccessGranted(AccessLevel level) {
    digitalWrite(LED_ACCESS_GRANTED, HIGH);
    digitalWrite(LED_ACCESS_DENIED, LOW);
    if (level == ACCESS_ADMIN) {
        digitalWrite(LED_ADMIN_MODE, HIGH);
        playTone(2000, 100); delay(50);
        playTone(2500, 100); delay(50);
        playTone(3000, 200);
    } else {
        playTone(1500, 150); delay(50);
        playTone(2000, 200);
    }
    delay(2000);
    digitalWrite(LED_ACCESS_GRANTED, LOW);
    digitalWrite(LED_ADMIN_MODE, LOW);
}

void indicateAccessDenied() {
    for (int i = 0; i < 3; i++) {
        digitalWrite(LED_ACCESS_DENIED, HIGH);
        playTone(400, 100); delay(100);
        digitalWrite(LED_ACCESS_DENIED, LOW); delay(100);
    }
}

void indicateSystemLocked() {
    for (int i = 0; i < 5; i++) {
        digitalWrite(LED_SYSTEM_STATUS, HIGH);
        digitalWrite(LED_ACCESS_DENIED, HIGH);
        playTone(300, 100); delay(100);
        digitalWrite(LED_SYSTEM_STATUS, LOW);
        digitalWrite(LED_ACCESS_DENIED, LOW); delay(100);
    }
}

void playTone(int frequency, int duration) {
    tone(BUZZER_PIN, frequency, duration);
}


void handleButtons() {
    unsigned long t = millis();
    if (digitalRead(BUTTON_SELECT) == LOW &&
        (t - lastButtonSelectTime) > DEBOUNCE_DELAY) {
        lastButtonSelectTime = t;
        secState.currentCardIndex =
            (secState.currentCardIndex + 1) % NUM_TEST_CARDS;
        Serial.printf("Card selected: %s\n",
            testCards[secState.currentCardIndex]);
    }
    if (digitalRead(BUTTON_ACCESS) == LOW &&
        (t - lastButtonAccessTime) > DEBOUNCE_DELAY) {
        lastButtonAccessTime = t;
        requestAccess(2);  // Default: Server Room
    }
}

void processSerialCommands() {
    if (!Serial.available()) return;
    String cmd = Serial.readStringUntil('\n');
    cmd.trim(); cmd.toUpperCase();

    if (cmd == "HELP") printMenu();
    else if (cmd == "LOG")  printAuditLog();
    else if (cmd == "STATUS") {
        Serial.printf("Locked: %s | Fails: %d | Card: %s\n",
            secState.isLocked ? "YES":"NO",
            secState.failedAttempts,
            testCards[secState.currentCardIndex]);
    }
    else if (cmd.startsWith("ZONE "))
        requestAccess(cmd.substring(5).toInt());
    else if (cmd.startsWith("CARD ")) {
        int n = cmd.substring(5).toInt();
        if (n >= 0 && n < NUM_TEST_CARDS)
            secState.currentCardIndex = n;
    }
    else if (cmd == "RESET") {
        secState.failedAttempts = 0;
        secState.isLocked = false;
    }
}

void printMenu() {
    Serial.println("Commands: HELP STATUS USERS ZONES");
    Serial.println("  ZONE n | CARD n | LOG | RESET");
}

const char* accessLevelToString(AccessLevel level) {
    switch (level) {
        case ACCESS_NONE: return "NONE";
        case ACCESS_GUEST: return "GUEST";
        case ACCESS_USER: return "USER";
        case ACCESS_ADMIN: return "ADMIN";
        default: return "UNKNOWN";
    }
}

void printAuditLog() {
    Serial.println("\n=== SECURITY AUDIT LOG ===");
    for (int i = 0; i < MAX_AUDIT_ENTRIES; i++) {
        AuditEntry* e = &auditLog[i];
        if (e->timestamp > 0)
            Serial.printf("%8lu | %-16s | %-12s | %-13s | %s\n",
                e->timestamp, e->eventType, e->userName,
                e->zoneName, e->success ? "OK":"FAIL");
    }
}
