#include <LiquidCrystal.h>
#include <Keypad.h>
#include <Servo.h>
#include <Arduino.h>
#include <EEPROM.h>

/* meccanismo di blocco */
#define PIN_SERVO 7
#define POS_BLOCCATO 20
#define POS_SBLOCCATO 90
Servo servo;

/* Display */
LiquidCrystal lcd(13, 12, 11, 10, 9, 8);

/* tastierino */
const byte RIGHE_TASTIERA = 4;
const byte COLONNE_TASTIERA = 4;
byte pinRighe[RIGHE_TASTIERA] = {5, 4, 3, 2};
byte pinColonne[COLONNE_TASTIERA] = {A3, A2, A1, A0};
char tasti[RIGHE_TASTIERA][COLONNE_TASTIERA] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
Keypad tastiera = Keypad(makeKeymap(tasti), pinRighe, pinColonne, RIGHE_TASTIERA, COLONNE_TASTIERA);

#define I_B 0
#define I_L_C 1
#define I_C 2
#define V_VUOTO 0xff

#define S_APERTO (char)0
#define S_CHIUSO (char)1

class safe {
private:
  bool bloccoAttivo;

public:
  safe() {
    this->bloccoAttivo = EEPROM.read(I_B) == S_CHIUSO;
  }

  void blocca() {
    this->impostaBlocco(true);
  }

  bool statoBlocco() {
    return this->bloccoAttivo;
  }

  bool PinPresente() {
    auto lunghezzaPin = EEPROM.read(I_L_C);
    return lunghezzaPin != V_VUOTO;
  }

  void impostaPin(String nuovoPin) {
    EEPROM.write(I_L_C, nuovoPin.length());
    for (byte i = 0; i < nuovoPin.length(); i++) {
      EEPROM.write(I_C + i, nuovoPin[i]);
    }
  }

  bool sblocca(String Pin) {
    auto lunghezzaPin = EEPROM.read(I_L_C);
    if (lunghezzaPin == V_VUOTO) {
      // Non c'era alcun Pin, quindi lo sblocco avviene sempre
      this->impostaBlocco(false);
      return true;
    }
    if (Pin.length() != lunghezzaPin) {
      return false;
    }
    for (byte i = 0; i < Pin.length(); i++) {
      auto cifra = EEPROM.read(I_C + i);
      if (cifra != Pin[i]) {
        return false;
      }
    }
    this->impostaBlocco(false);
    return true;
  }

  void impostaBlocco(bool attivo) {
    this->bloccoAttivo = attivo;
    EEPROM.write(I_B, attivo ? S_CHIUSO : S_APERTO);
  }
};

safe safe;

void blocca() {
  servo.write(POS_BLOCCATO);
  safe.blocca();
}

void sblocca() {
  servo.write(POS_SBLOCCATO);
}

void restart_lobby() {
  lcd.setCursor(4, 0);
  lcd.print("Benvenuto!");
  delay(1000);

  lcd.setCursor(0, 2);
  String messaggio = "Cassaforte EHF";
  for (byte i = 0; i < messaggio.length(); i++) {
    lcd.print(messaggio[i]);
    delay(100);
  }
  delay(500);
}

String inserisciPin() {
  lcd.setCursor(5, 1);
  lcd.print("[____]");
  lcd.setCursor(6, 1);
  String risultato = "";
  while (risultato.length() < 4) {
    char tasto = tastiera.getKey();
    if (tasto >= '0' && tasto <= '9') {
      lcd.print('*');
      risultato += tasto;
    }
  }
  return risultato;
}

void disp_wait(int delayMillis) {
  lcd.setCursor(2, 1);
  lcd.print("[..........]");
  lcd.setCursor(3, 1);
  for (byte i = 0; i < 10; i++) {
    delay(delayMillis);
    lcd.print("=");
  }
}

bool new_Pin() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(" New pin: ");
  String nuovoPin = inserisciPin();

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Conferma");
  String confermaPin = inserisciPin();

  if (nuovoPin.equals(confermaPin)) {
    safe.impostaPin(nuovoPin);
    return true;
  } else {
    lcd.clear();
    lcd.setCursor(1, 0);
    lcd.print("Errore!");
    lcd.setCursor(0, 1);
    lcd.print("Sblocco");
    delay(2000);
    return false;
  }
}

void unlock_text() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Sbloccato!");
  delay(1000);
}

void unlock_safe() {
  lcd.clear();

  lcd.setCursor(0, 0);
  lcd.print("Sbloccato!");

  bool nuovoPinNecessario = true;

  if (safe.PinPresente()) {
    lcd.setCursor(0, 1);
    lcd.print("New code");
    nuovoPinNecessario = false;
  }

  auto tasto = tastiera.getKey();
  while (tasto != 'A' && tasto != '#') {
    tasto = tastiera.getKey();
  }

  bool ready_lock = true;
  if (tasto == 'A' || nuovoPinNecessario) {
    ready_lock = new_Pin();
  }

  if (ready_lock) {
    lcd.clear();
    lcd.setCursor(5, 0);
    lcd.print("Bloccato!");
    safe.blocca();
    blocca();
    disp_wait(100);
  }
}

void logic_safelock() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Bloccato!");

  String PinUtente = inserisciPin();
  bool sbloccatoConSuccesso = safe.sblocca(PinUtente);
  disp_wait(200);

  if (sbloccatoConSuccesso) {
    unlock_text();
    sblocca();
  } else {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Errore");
    disp_wait(1000);
  }
}

void setup() {
  lcd.begin(16, 2);

  servo.attach(PIN_SERVO);

  /* verifica EEPROM */
  Serial.begin(115200);
  if (safe.statoBlocco()) {
    blocca();
  } else {
    sblocca();
  }

  restart_lobby();
}

void loop() {
  if (safe.statoBlocco()) {
    logic_safelock();
  } else {
    unlock_safe();
  }
}
$abcdeabcde151015202530fghijfghij