#include <LiquidCrystal.h>
#include <Keypad.h>
#include <Servo.h>
#include "Adafruit_ILI9341.h"
#include "pitches.h"
#include "DHT.h"
#include "RTClib.h"

/* LCD Setup lcd(rs, e, d4, d5, d6, d7)*/
LiquidCrystal lcd (2, 3, 4, 5, 6, 7);

/* TFT LCD Display */
#define TFT_DC 33
#define TFT_CS 53
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

/*Photoresistor*/
int vaultLed = A5;
const float GAMMA = 0.7; 
const float RL10 = 50;

/* RTC Module */
RTC_DS1307 rtc;

/* Temperature and Humidity Sensor */
#define DHT_PIN 35
#define DHT_TYPE DHT22
const int delay_value = 2000;
DHT dht(DHT_PIN, DHT_TYPE);

/* Servo Motor*/
#define SERVO_PIN A3            // Vault Door
#define LOCKED_SERVO 0          // Vault Door is still locked
#define UNLOCKED_SERVO 90       // Vault Door is unlocked
Servo vault;

/*Active Buzzer*/
#define BUZZER_PIN A4           // Buzzer for Alarm System

/*Keypad Functionality*/
const byte KEYPAD_ROWS = 4;
const byte KEYPAD_COLS = 3;
byte rowPins[KEYPAD_ROWS] = {43, 45, 47, 49};
byte colPins[KEYPAD_COLS] = {A15, A14, A13};
char keys[KEYPAD_ROWS][KEYPAD_COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'}, 
  {'7', '8', '9'}, 
  {'*', '0', '#'}  
};

/* Reference: Keypad Repository */
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, KEYPAD_ROWS, KEYPAD_COLS);

int currAtt;                    // attempt counter
int leftAtt;                    // number of attempts left
int maxAtt = 3;                 // number of maximum attempts
int ledRed = 37;                // blue led color ; blue & yellow = green
int ledGreen = 39;              // purple led color
int ledBlue = 41;               // yellow led color ; yellow & purple = red
unsigned long startTime = 0;
unsigned long endTime = 0;
char key;
bool status = true;
boolean vaultLocked = true;
bool run = false;               // flag to check if system was initialized or is on a consecutive run
bool accessGranted = false;     // flag to check if attempt was successful
const String defaultPasscode = "3216";
String storePasscode = defaultPasscode;

void initialScreen(){
  digitalWrite(ledRed, LOW);
  while(vaultLocked){
    digitalWrite(ledRed, LOW);
    vault.write(LOCKED_SERVO);
    lcd.setCursor(2,0);
    lcd.print("VAULT: LOCKED");
    lcd.setCursor(3,1);
    lcd.print("# to Unlock");
    key = keypad.getKey();

    if (key == '*'){
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Enter admin code: ");
      String defaultPassInput = passcodeInput();

      if (defaultPassInput == defaultPasscode){
        setPasscode();
      } else {
        lcd.clear();
        lcd.setCursor(2,0);
        lcd.print("Unauthorized");
        lcd.setCursor(4,1);
        lcd.print("Access.");
        inaccessible();
        delay(2000);
        lcd.clear();
        initialScreen();
      }
    }

    if (key == '#'){
      lockedStatus();
    }
  }
}

void setPasscode(){                           // allows user to set a password for the first time
  while(vaultLocked){
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Set new passcode:");
    String newPasscode = passcodeInput();

    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Confirm passcode:");
    String confirmPasscode = passcodeInput();
    
    if(newPasscode.equals(confirmPasscode)){
      run = true;
      storePasscode = newPasscode;
      lcd.clear();
      accessible();
      lockedStatus();
    }
    else {
      lcd.clear();
      lcd.setCursor(5,0);
      lcd.print("SET-UP");
      lcd.setCursor(2,1);
      lcd.print("UNSUCCESSFUL");
      inaccessible();
      delay(1500);
      lcd.clear();
      initialScreen();
    }
  }
}

void lockedStatus(){
  while(vaultLocked){
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Enter passcode:");
    String tempPasscode = passcodeInput();

    if (run){
      if (tempPasscode == storePasscode){
          lcd.clear();
          accessible();
          unlockedStatus();
      } else{
        lcd.clear();
        lcd.setCursor(3,0);
        lcd.print("INCORRECT");
        lcd.setCursor(3,1);
        lcd.print("PASSWORD!");
        for (int i = 1; i < 5; i++){
          ledCondition();
          tone(BUZZER_PIN, NOTE_G4);
          delay(100);
          ledCondition();
          noTone(BUZZER_PIN);
          delay(100);
        }
        checkFirstAtt();
        currAtt +=1;
        leftAtt = maxAtt - currAtt;
        delay(1500);
        lcd.clear();
        lcd.setCursor(1,0);
        lcd.print("Attempt(s) left:");
        lcd.setCursor(7,1);
        lcd.print(leftAtt);
        checkSecondAtt();
        delay(1000);
        lcd.clear();
        lockedStatus();
      }
    } else {
        if (tempPasscode == defaultPasscode){
          lcd.clear();
          accessible();
          unlockedStatus();
      } else{
        lcd.clear();
        lcd.setCursor(3,0);
        lcd.print("INCORRECT");
        lcd.setCursor(3,1);
        lcd.print("PASSWORD!");
        for (int i = 1; i < 5; i++){
          ledCondition();
          tone(BUZZER_PIN, NOTE_G4);
          delay(100);
          ledCondition();
          noTone(BUZZER_PIN);
          delay(100);
        }
        checkFirstAtt();
        currAtt +=1;
        leftAtt = maxAtt - currAtt;
        delay(1500);
        lcd.clear();
        lcd.setCursor(1,0);
        lcd.print("Attempt(s) left:");
        lcd.setCursor(7,1);
        lcd.print(leftAtt);
        checkSecondAtt();
        delay(1000);
        lcd.clear();
        lockedStatus();
      }
    }
  }
}

void unlockedStatus(){
  endTime = millis();
  unsigned long timeElapsed = endTime - startTime;
  accessGranted = true;

  if (timeElapsed <= 30000){
    vaultLocked = false;
    lcd.setCursor(0,0);
    lcd.print("Access Granted!");
    vault.write(UNLOCKED_SERVO);
    ledCondition();
    TFTDisplay();
    lcd.clear();
    vaultLocked = true;
    lockedStatus();
  } else {
    lcd.setCursor(0,0);
    lcd.print("Access Denied!");
    lcd.setCursor(0,1);
    lcd.print("Please wait.");
    tft.setCursor(25, 120);
    tft.setTextColor(ILI9341_WHITE);
    tft.setTextSize(3);
    tft.print("THE VAULT!");

    tft.setCursor(20, 160);
    tft.setTextColor(ILI9341_BLUE);
    tft.setTextSize(2);
    tft.print("Security Deployed.");

    tft.setCursor(20, 200);
    tft.setTextColor(ILI9341_RED);
    tft.setTextSize(2);
    tft.print("Time: ");
    tft.print(timeElapsed/1000);
    tft.print(" seconds");

    blast();
    lcd.clear();
    initialScreen();
  }
}

void ledCondition(){
  if (vaultLocked == true){
    digitalWrite(ledGreen, HIGH); 
    digitalWrite(ledBlue, HIGH); 
  } else {
    digitalWrite(ledRed, HIGH);
    digitalWrite(ledBlue, HIGH);
  }
}

void inaccessible(){
  tone(BUZZER_PIN, NOTE_G4);
  delay(100);
  tone(BUZZER_PIN, NOTE_C4);
  delay(100);
  noTone(BUZZER_PIN);
}

void accessible(){
  tone(BUZZER_PIN, 650, 750);
  delay(100);
  tone(BUZZER_PIN, 900, 1000);
  delay(100);
  tone(BUZZER_PIN, 650, 750);
  delay(100);
  tone(BUZZER_PIN, 900, 1000);
  noTone(BUZZER_PIN);
}

void checkFirstAtt(){
  if(accessGranted){
    currAtt = 0;
  }
  accessGranted = false;
}

void checkSecondAtt(){
  if(leftAtt == 0){
    lcd.clear();
    lcd.setCursor(1,0);
    lcd.print("Intruder Alert");
    lcd.setCursor(0,1);
    lcd.print("Security Notified");
    tft.fillScreen(ILI9341_BLACK);

    tft.setCursor(25, 120);
    tft.setTextColor(ILI9341_WHITE);
    tft.setTextSize(3);
    tft.print("THE VAULT!");

    tft.setCursor(20, 160);
    tft.setTextColor(ILI9341_RED);
    tft.setTextSize(3);
    tft.print("ON LOCKDOWN");

    blast();
  }
}

void blast(){
  while(true){
    inaccessible();
    key = keypad.getKey();
    if (key == '*'){
      currAtt = 0;
      tft.fillScreen(ILI9341_BLACK);
      break;
    }
  }
}

String passcodeInput(){
  lcd.setCursor(5,1);
  lcd.print("[----]");
  lcd.setCursor(6,1);
  String userInput = "";
  while (userInput.length()<4){
    char key = keypad.getKey();
    if (key >= '0' && key <= '9'){
      lcd.print('*');
      userInput +=key;
    }
  }
  return userInput;
}

boolean pressPound(char attempt){
  if(attempt != '#'){
    return false;
  } else {
    return true;
  }
}

void TFTDisplay() {
  // Clear the screen
  tft.fillScreen(ILI9341_BLACK);

  // Print title
  tft.setCursor(30, 70);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(3);
  tft.print("THE VAULT!");

  // Read temperature and humidity
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();

  // DATE TIME
  DateTime now = rtc.now();
  tft.setCursor(20, 110);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.print(now.year(), DEC);
  tft.print('/');
  tft.print(now.month(), DEC);
  tft.print('/');
  tft.print(now.day(), DEC);
  tft.print(" ");
  tft.print(now.hour(), DEC);
  tft.print(":");
  if((now.minute()) < 10){
    tft.print('0');
  }
  tft.print(now.minute(), DEC);
  tft.print(':');
  if((now.second()) < 10){
    tft.print('0');
  }
  tft.print(now.second(), DEC);

  // Print temperature
  tft.setCursor(20, 150);
  tft.setTextColor(ILI9341_RED);
  tft.setTextSize(2);
  tft.print("Temp.:");
  tft.print(temperature);
  tft.print("C");

  // Print humidity
  tft.setCursor(20, 190);
  tft.setTextColor(ILI9341_GREEN);
  tft.setTextSize(2);
  tft.print("Humidity:");
  tft.print(humidity);
  tft.print("%");

  int analogValue = analogRead(A0);
  float voltage = analogValue / 1024. * 5;
  float resistance = 2000 * voltage / (1 - voltage / 5);
  float lux = pow(RL10 * 1e3 * pow(10, GAMMA) / resistance, (1 / GAMMA));

  tft.setCursor (20, 230);
  tft.setTextColor(ILI9341_BLUE);
  tft.setTextSize(2);
  if (lux > 50){
    tft.print("Vault: Bright.");
    digitalWrite(vaultLed, LOW);
  } else {
    tft.print("Vault: Dark.");
    digitalWrite(vaultLed, HIGH);
  }

  tft.setCursor (20, 250);
  tft.print("Lux:");
  tft.print(lux);

  delay(10000);
  tft.fillScreen(ILI9341_BLACK);
}

void setup(){
  Serial.begin(9600);
  dht.begin();
  tft.begin();
  lcd.begin(16,2);
  vault.attach(SERVO_PIN);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(ledRed, OUTPUT);
  pinMode(ledGreen, OUTPUT);
  pinMode(ledBlue, OUTPUT);
  pinMode(vaultLed, OUTPUT);
  vaultLocked = true;
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    abort();
  }

  if (! rtc.isrunning()) {
    Serial.println("RTC is NOT running, set the time!");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
}

void loop(){
  initialScreen();
}
GND5VSDASCLSQWRTCDS1307+