#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x20,16,2);

#include <Keypad.h>
const byte numRows = 4;
const byte numCols = 4;
char keymap[numRows][numCols] = {
    {'1', '2', '3', 'A'}, 
    {'4', '5', '6', 'B'}, 
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'}
};
// keypad pins
byte rowPins[numRows] = {13, 12, 11, 10};
byte colPins[numCols] = {9, 8, 7, 6};
Keypad keypad = Keypad(makeKeymap(keymap), rowPins, colPins, numRows, numCols);

#include <Servo.h>
Servo servo;

// led pins
const int greenLed = 1;
const int ledArray[6] = {2, 3, 4, 5, 6, 7};
const int redLed = 8;

// pins
const int registerInput = 1;
const int outputRegister = 2;
const int shiftRegister = 3;
const int piezo = 4;
const int servoPin = 5;

// variables
bool locked = true;
bool showPassword = false;
bool alarmed = false;
int attempts = 5;
String password = "";
String masterkey = "";
char key;

void setup() {
    lcd.init();
  	lcd.clear();
    lcd.backlight();
    pinMode(registerInput, OUTPUT);
  	pinMode(outputRegister, OUTPUT);
  	pinMode(shiftRegister, OUTPUT);
    pinMode(piezo, OUTPUT);
  	servo.write(0);
    servo.attach(servoPin);
}

void loop() {
    if (alarmed) {
        lcd.clear();
        lcd.print("Enter masterkey:");
      	alarmedLoop();
    } else if (locked) {
        ledMode(greenLed, LOW);
        ledMode(redLed, HIGH);
        lcd.clear();
        lcd.print("Enter password:");
        lockedLoop();
    } else {
        ledMode(redLed, LOW);
        ledMode(greenLed, HIGH);
        lcd.clear();
        lcd.print("Press A to lock");
        unlockedLoop();
    }
}

void lockedLoop() {
    while (locked) {
        key = keypad.getKey();
        if (key != NO_KEY) {
            if (key >= '0' && key <= '9') {
                if (password.length() <= 16) {
                    buzz();
                    password += key;
                } else {
                    error();
                }
            }
            if (key == '*') {
                buzz();
                password = "";
            }
            if (key == '#') {
                buzz();
                password = password.substring(0, password.length() - 1);
            }
            if (key == 'A') {
                checkPassword();
            }
            if (key == 'D') {
                buzz();
                showPassword = !showPassword;
            }
            clearLine(1, 0);
            if (showPassword) {
                lcd.print(password);
            } else {
                for (int i = 0; i < password.length(); i++) {
                    lcd.print('*');
                }
            }
        }
    }
}

void unlockedLoop() {
  Serial.println("unlockedLoop");
    while (!locked) {
        key = keypad.getKey();
        if (key != NO_KEY && key == 'A') {
            lock();
            locked = true;
        }
    }
}

void alarmedLoop() {
    while (alarmed) {
      	alarm(500, 200);
        key = keypad.getKey();
        if (key != NO_KEY) {
            if (key >= '0' && key <= '9') {
                if (masterkey.length() < 16) {
                    buzz();
                    masterkey += key;
                } else {
                    error();
                }
            }
            if (key == '*') {
                buzz();
                masterkey = "";
            }
            if (key == '#') {
                buzz();
                masterkey = masterkey.substring(0, masterkey.length() - 1);
            }
            if (key == 'A') {
                checkMasterkey();
            }
            if (key == 'D') {
                buzz();
                showPassword = !showPassword;
            }
            clearLine(1, 0);
            if (showPassword) {
                lcd.print(masterkey);
            } else {
                for (int i = 0; i < masterkey.length(); i++) {
                    lcd.print('*');
                }
            }
        }
    }
}

void checkPassword() {
    if (password == "1258") {
        locked = false;
        password = "";
       	attempts = 5;
        unlock();
    } else {
        attempts--;
        if (attempts != 0) {
            error();
            lcd.clear();
            lcd.print("Wrong password!");
            delay(1000);
            lcd.clear();
            lcd.print("Remaining");
            lcd.setCursor(0, 1);
            lcd.print("attempts: ");
            lcd.print(attempts);
            delay(1000);
          	lcd.clear();
          	lcd.print("Enter password:");
        } else {
            alarmed = true;
          	locked = false;
          	password = "";
        }
    }
}

void checkMasterkey() {
    if (masterkey == "7852") {
        alarmed = false;
      	locked = true;
        masterkey = "";
      	attempts = 5;
      	noTone(piezo);
    } else {
        lcd.clear();
        lcd.print("Wrong masterkey!");
        delay(1000);
        lcd.clear();
        lcd.print("Enter masterkey:");
    }
}

void unlock() {
    tone(piezo, 1000, 1000);
    digitalWrite(redLed, LOW);
    digitalWrite(greenLed, HIGH);
    lcd.clear();
    lcd.setCursor(4, 0);
    lcd.print("Unlocked");
    servo.write(90);
    delay(1000);
}

void lock() {
    tone(piezo, 1000, 1000);
    digitalWrite(greenLed, LOW);
    digitalWrite(redLed, HIGH);
    lcd.clear();
    lcd.setCursor(5, 0);
    lcd.print("Locked");
    servo.write(0);
    delay(1000);
}

void alarm(int hertz, int timing) { //CHATGPT for the millis and led algorithm
    static unsigned long previousMillis = 0;
    static bool state = false;
    static int ledIndex = 0;
    static bool directionForward = true;
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillis >= timing) {
        previousMillis = currentMillis;
        if (state) {
            tone(piezo, hertz + 100);
        } else {
            tone(piezo, hertz - 100);
        }
        state = !state;
        for (int i = 0; i < 6; i++) {
            ledMode(ledArray[i], LOW);
        }
        ledMode(ledArray[ledIndex], HIGH);
        if (directionForward) {
            ledIndex++;
            if (ledIndex >= 6) {
                directionForward = false;
            }
        } else {
            ledIndex--;
            if (ledIndex < 0) {
                directionForward = true;
            }
        }
    }
}

void ledMode(int ledNumber, bool state) { //CHATGPT for shift register
    digitalWrite(outputRegister, LOW);
    byte data = 0;
    if (ledNumber >= 1 && ledNumber <= 8) {
        data = state ? (1 << (ledNumber - 1)) : 0;
    }
    shiftOut(registerInput, shiftRegister, MSBFIRST, data);
    digitalWrite(outputRegister, HIGH);
}

void clearLine(int l, int c) {
    lcd.setCursor(0, l);
    lcd.print("                ");
    lcd.setCursor(c, l);
}

void buzz() {
    tone(piezo, 1000, 100);
}

void error() {
    tone(piezo, 500, 500);
}