#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <Servo.h>
#include "pitches.h"

#define SPEAKER_PIN 11
#define SERVO_PIN 2
#define SERVO_LOCKED_POS 0 // position of the servo motor when in locked status
#define SERVO_UNLOCKED_POS 90 // position of the servo motor when in unlocked status
Servo lockServo;

LiquidCrystal_I2C lcd (0x27,20,4);
const int ROW_NUM = 4;
const int COLUMN_NUM = 3;
byte pin_rows[ROW_NUM] = {9,8,7,6};
byte pin_column[COLUMN_NUM] = {5,4,3};
char keys[ROW_NUM][COLUMN_NUM] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'*','0','#'}
};

int currTry;
int leftTry;
int maxTry = 3;
int ledRed = 12;
int ledGreen = 13;
char key;
boolean statusLocked = true;
boolean instance = false;
boolean isSuccessful = false;
String storePassword;

Keypad keypad = Keypad(makeKeymap(keys), pin_rows, pin_column, ROW_NUM, COLUMN_NUM);

//display initial screen 
void startScreen(){
  while(statusLocked){
    lockServo.write(SERVO_LOCKED_POS);
    lcd.setCursor(4, 0);
    lcd.print("LOCKED!");
    lcd.setCursor(1, 1);
    lcd.print("'#' to Unlock");
    ledStatus();
    key = keypad.getKey();

    if (key){
      if (checkKeyLocked(key) && instance == false){ //true when used on the first run
        createPass();
      }
      if (checkKeyLocked(key) && instance == true){ //true when used on the consecutive runs
        locked();
      }
    }
  }
}

// create new one time password when used for the first time

void createPass(){
  while(statusLocked){ //door is still on the locked status
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Enter new pass:");
    String newPass = passwordInpt();

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Confirm new pass:");
    String confirmPass = passwordInpt();

    //verify if the new pass and confirm pass matches
    if (newPass.equals(confirmPass)){
      instance = true;
      storePassword = newPass;
      lcd.clear();
      success();
      startScreen();
    }
    else {
      lcd.clear();
      lcd.setCursor(4, 0);
      lcd.print("Password");
      lcd.setCursor(4, 1);
      lcd.print("Mismatch!");
      delay(2000);
      lcd.clear();
      startScreen();
    }
  }
}

void locked (){
  while(statusLocked){
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Enter Password:");
    String tempPass = passwordInpt(); //store input to tempPass

    if (tempPass == storePassword){
      lcd.clear();
      success();
      unlocked();
    }
    else {
      lcd.clear();
      lcd.setCursor(4, 0);
      lcd.print("Password");
      lcd.setCursor(3, 1);
      lcd.print("Incorrect!");
      invalid();
      check_first();
      currTry +=1;
      leftTry = maxTry - currTry;
      delay(500);
      lcd.clear();
      lcd.setCursor(1, 0);
      lcd.print("Attempt/s left:");
      lcd.setCursor(7, 1);
      lcd.print(leftTry);
      check_second();
      delay(500);
      lcd.clear();
      startScreen();
    }
  }
}

void unlocked(){
  isSuccessful = true;
  statusLocked = false; //changed statusLocked flag to false
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("UNLOCKED!");
  lockServo.write(SERVO_UNLOCKED_POS); //re-position the motor in unlocking
  ledStatus();
  delay(3000); //automatically locks after 3 seconds
  statusLocked = true; //change the flag again to true
  lcd.clear();
  startScreen();
}

//change LED values depending statusLocked flag 
void ledStatus(){ 
  if (statusLocked == true){
    digitalWrite(ledRed, HIGH);
    digitalWrite(ledGreen, LOW);
  }
  else{
    digitalWrite(ledGreen, HIGH);
    digitalWrite(ledRed, LOW);
  }
}

//performs an approriate tone when the combination of password is correct
void success(){
  tone(SPEAKER_PIN, 650, 750);
  delay(100);
  tone(SPEAKER_PIN, 900, 1000);
  delay(100);
  tone(SPEAKER_PIN, 650, 750);
  delay(100);
  tone(SPEAKER_PIN, 900, 1000);
  noTone(SPEAKER_PIN);
}

//performs an approriate tone when the combination of password is incorrect
void invalid(){
  tone(SPEAKER_PIN, NOTE_G4);
  delay(100);
  tone(SPEAKER_PIN, NOTE_C4);
  delay(100);
  noTone(SPEAKER_PIN);
}

//first part of checking the attempts
void check_first(){
  if (isSuccessful){
    currTry = 0;
  }
  isSuccessful = false;
}

//second part of checking the attempts
void check_second(){
  if (leftTry == 0){
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Intruder Alert!!");
    warning();
  }
}

//when max attempts used
void warning(){
  while(true){
    digitalWrite(ledGreen, HIGH);
    digitalWrite(ledRed, HIGH);
    invalid();
    key = keypad.getKey();
    if (key == '*'){
      currTry = 0;
      break; //option to stop the alarm
    }
  }
}

//prompts the user to input 4-digit password
String passwordInpt(){
  lcd.setCursor(5, 1);
  lcd.print("[____]");
  lcd.setCursor(6, 1);
  String result = "";
  while (result.length() < 4){
    char key = keypad.getKey();
    if (key >= '0' && key <='9'){
      lcd.print('*');
      result += key;
    }
  }
  return result;
}

//check if key == '#'
boolean checkKeyLocked(char input){
  if(input != '#'){
    return false;
  }

  else{
    return true;
  }
}

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.home();
  lockServo.attach(SERVO_PIN);
  pinMode(SPEAKER_PIN, OUTPUT);
  pinMode(ledRed, OUTPUT);
  pinMode(ledGreen, OUTPUT);
  Serial.begin(9600);
  statusLocked = true; //statusLocked is set true on default 

}

void loop() {
  startScreen();
}