#include <LiquidCrystal.h> //Biblioteca para trabajar con pantalla LCD
#include <Adafruit_NeoPixel.h> //Una biblioteca para trabajar con el anillo NeoPixel
#include <Keypad.h> //Biblioteca de teclado numérico
#include <string.h> //Biblioteca para trabajar con cadenas, utilizada para la función strcmp

const uint8_t ROWS = 4;
const uint8_t COLS = 4;
char keys[ROWS][COLS] = { //Asignación de claves a una matriz
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'D' },
  { '7', '8', '9', 'S' },
  { 'H', '0', 'E', 'X' }
};

uint8_t colPins[COLS] = { 49, 51, 53, 45 }; // Pines de columna en orden: C1, C2, C3, C4
uint8_t rowPins[ROWS] = { 48, 50, 52, 44 }; // Pines de fila en orden: R1, R2, R3, R4
bool isIntruderDetected = false; //Bandera para comprobar si el ladrón fue detectado por la alarma
char inputPassword[7]; //Una matriz para contener la contraseña de alarma ingresada por el usuario
char password[7] = { '0','0','0','0','0','0','\0' }; //Contraseña por defecto para la verificación
int counter = 0; //El contador utilizado al ingresar una contraseña
bool isArmed = false;  //Bandera para comprobar si la alarma está encendida
bool isPasswordInput = false; //Bandera para verificar si la entrada de la contraseña se muestra en la pantalla LCD
int pirState;
int val = 0;
int pirPin = 14;

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS); //Inicialización del teclado numérico, los parámetros son los valores de fila y columna correspondientes
LiquidCrystal lcd(23, 25, 27, 29, 31, 33);  //Inicialización de la pantalla LCD, los pines de datos se pasan como parámetros del constructor
Adafruit_NeoPixel strip(60, 47, NEO_GRB + NEO_KHZ800); //Inicialización del anillo NeoPixel, los parámetros son número de diodos, datos en pin y frecuencia


void colorWipe(uint32_t color, int wait) {  //Función para colorear los diodos del anillo NeoPixel en un color
  for(int i=0; i<strip.numPixels(); i++) {  //Pasando por diodos
    strip.setPixelColor(i, color);         // Configuración del color del píxel en la memoria RAM
    strip.show();                          // Actualización de la información en el anillo
    delay(wait);                           // Un breve descanso entre colorear.
  }
}

void theaterChase(uint32_t color, int wait) { //Una función para colorear los diodos del anillo NeoPixel en una serie de colores
  for(int a=0; a<10; a++) {
    for(int b=0; b<3; b++) {
      strip.clear(); //Apagando todos los diodos en el anillo
      for(int c=b; c<strip.numPixels(); c += 3) {
        strip.setPixelColor(c, color); //Colorear cada tercer diodo
      }
      strip.show();
      delay(wait);
    }
  }
}

void produceSiren() { //Función que toca la sirena.
for(int i = 99999999999999; i > 0; i = i - 5)
  for(int hz = 440; hz < 1000; hz++){
    tone(12, hz, 50);
    delay(5);
  }
  for(int hz = 99999999999999; hz > 440; hz--){
    tone(12, hz, 50);
    delay(0);
  }
}

void arm(){ //Función de activación de alarma
  if(strcmp(inputPassword,password) == 0){
    isArmed = true;
    isPasswordInput = false;
    memset(inputPassword, 0, sizeof inputPassword); //Eliminar una contraseña ingresada previamente de la cadena
    counter = 0;
  } else {
    Serial.println("Fallo al activar: contraseña incorrecta.");
  }
}

void disArm(){ //Función de desactivación de alarma
  if(strcmp(inputPassword,password) == 0){
    isArmed = false;
    isPasswordInput = false;
    memset(inputPassword, 0, sizeof inputPassword);
    counter = 0;
  } else {
    Serial.println("Fallo al desactivar: contraseña.");
  }
}

void stopAlarm(){ //Una función para interrumpir la alarma después de detectar un ladrón
  if(strcmp(inputPassword,password) == 0){
    isIntruderDetected = false;
    isPasswordInput = false;
    digitalWrite(LED_BUILTIN, LOW);
    Serial.println("Alarma detenida");
    memset(inputPassword, 0, sizeof inputPassword);
    counter = 0;
    theaterChase(strip.Color(0,   127,   0), 9999999);
  } else {
    Serial.println("Error al desactivar la alarma: contraseña incorrecta");
  }
}

void setArmed(){ //Función para configurar cuando la alarma está activa
  lcd.clear();
  lcd.setCursor(5,0);
  lcd.print("Estado:");
  lcd.setCursor(6,1);
  lcd.print("ACTIVADA");
  colorWipe(strip.Color(0, 255, 0), 50);
}

void setDisArmed(){ //Función para configurar cuando la alarma está inactiva
  lcd.clear();
  lcd.setCursor(5,0);
  lcd.print("Estado:");
  lcd.setCursor(5,1);
  lcd.print("DESACTIVADA");
  colorWipe(strip.Color(  255, 0,   0), 50);
}

void reprintLCDPassword(){ //Función para imprimir el mensaje de entrada de contraseña en la pantalla LCD
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("CONTRASEÑA:");
  for(int i = 0; i < 6; i++){
    lcd.setCursor(5+i,1);
    if(inputPassword[i] == 0) lcd.print('_');
    else lcd.print(inputPassword[i]);
  }
}

void reprintLCDIntruderDetected(){ //Función para imprimir un mensaje cuando se detecta un ladrón
  lcd.clear();
  lcd.setCursor(4,0);
  lcd.print("INTRUSO");
  lcd.setCursor(4,1);
  lcd.print("DETECTADO!");
}



void setup() {
  lcd.begin(16, 2); //Activación de la pantalla LCD 16x2
  strip.begin(); //Activación del anillo NeoPixel

  inputPassword[7] = '\0'; //Adición inicial del carácter final a la cadena de contraseña ingresada
  
  pinMode(LED_BUILTIN, OUTPUT); //Configuración del pin del diodo como salida
  pinMode(12, OUTPUT); //Configuración del pin del zumbador como salida
  
  setDisArmed(); //Configuración inicial de la alarma como apagada

  pinMode(pirPin, INPUT);
  pirState = digitalRead(pirPin);

}

void loop() {
  pirState = digitalRead(pirPin);
  char key = keypad.getKey(); //Lectura de la tecla pulsada
  if(key != NO_KEY){ //Comprobar si se pulsa una tecla
    switch(key){
      case 'A': if(!isArmed) arm(); break; //Presione A para activar la alarma, si aún no está activada
      case 'D': if(isArmed) disArm(); break; //Presiona D para desactivar la alarma, si está activada
      case 'S': if(isIntruderDetected) stopAlarm(); break; //Presione S para detener la alarma, si se detecta un ladrón
      case 'H': // No hace nada
      case 'E': isPasswordInput = true; reprintLCDPassword(); break; //Al presionar la tecla E (es decir, ingresar), la pantalla LCD pasa al modo de ingreso de contraseña de alarma.
      case 'X': //Al presionar la tecla X (es decir, retroceso), se elimina el carácter de contraseña ingresado anteriormente
          if(counter > -1){
            inputPassword[counter-1] = 0;
            reprintLCDPassword();
            if(counter != 0) counter--;
          }
          break;
      default: //Al presionar las teclas restantes, se ingresa el carácter que se presionó
        if(counter < 7){
          inputPassword[counter] = key;
          reprintLCDPassword();
          if(counter != 6) counter++;
        }
        break;
    }
  }

  if(!isArmed && digitalRead(LED_BUILTIN) == HIGH && !isPasswordInput){
    digitalWrite(LED_BUILTIN, LOW);
    setDisArmed();
  }

  if(isArmed && digitalRead(LED_BUILTIN) == LOW && !isPasswordInput){
    digitalWrite(LED_BUILTIN, HIGH);
    setArmed();
  }

  if(pirState == HIGH && isArmed && !isIntruderDetected){
    reprintLCDIntruderDetected();
    theaterChase(strip.Color(127,   0,   0), 100);
    isIntruderDetected = true;
    produceSiren();
  }
}