#include "pico/stdlib.h"
#include <stdio.h>
#include <string.h>
#include "hardware/timer.h"


#define ID_LENGTH 6
#define PASSWORD_LENGTH 4
#define NUM_USERS 5
#define MAX_ATTEMPTS 3

// Definir los GPIOs para filas(Salidas) y columnas(Entradas)
#define ROW_1 2
#define ROW_2 3
#define ROW_3 4
#define ROW_4 5
#define COL_1 6
#define COL_2 7
#define COL_3 8
#define COL_4 9

const uint32_t maskOuts = 0b00000000000000000000000000111100;
const uint32_t maskIns = 0b00000000000000000000001111000000;

// GPIO adicional para indicar la detección de teclas
#define LED_ACCESO 10 //Led verde
#define LED_NE_IC 11 //Led rojo
#define LED_ID 12 // Led amarillo

// Matriz de teclas de la membrana matriz keyborad
const char keypad[4][4] = {
    {'1', '2', '3', 'A'},
    {'4', '5', '6', 'B'},
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'}};

// Estructura para la base de datos de usuarios
typedef struct
{
    char ID[ID_LENGTH + 1];             // ID de 6 caracteres + nulo
    char PASSWORD[PASSWORD_LENGTH + 1]; // Contraseña de 4 caracteres + nulo
    int blocked;                        // Estado de bloqueo del usuario (0 = no bloqueado, 1 = bloqueado)
    int attempts;                       // Contador de intentos fallidos
} User;

// Base de datos de usuarios
User users[NUM_USERS] = {
    {"123456", "1234", 0, 0},
    {"234567", "2345", 0, 0},
    {"345678", "3456", 0, 0},
    {"456789", "4567", 0, 0},
    {"567890", "5678", 0, 0}};

int8_t getUserInput(char *ID, char *PASSWORD);
void setUpKeypad();
char getKey();
int verify_data(const char *ID, const char *PASSWORD);
bool change_password(char *ID, char *PASSWORD);

int main()
{
    stdio_init_all(); // Inicializar la consola para depuración
    setUpKeypad();    // Configurar GPIOs del teclado y LEDs

    while (true)
    {
        char ID[ID_LENGTH + 1] = {'\0'};             // Buffer para ID
        char PASSWORD[PASSWORD_LENGTH + 1] = {'\0'}; // Buffer para la contraseña

        // Obtener ID y contraseña; si el tiempo se excede, retornar al inicio del bucle
        int8_t user_input = getUserInput(ID, PASSWORD);
        if (user_input == -1)
        {
            printf("Acceso denegado para ID por falta de tiempo: %s\n", ID);
            // Encender LED rojo indicando que se superó el tiempo límite
            gpio_put(LED_NE_IC, 1);
            sleep_ms(2000); // Mantener LED encendido 2 segundos
            gpio_put(LED_NE_IC, 0);
            continue; // Volver al inicio del bucle para esperar una nueva entrada
        }
        else if(user_input == 2)
        { // Cambio de contraseña
            (change_password(ID, PASSWORD))? printf("Contraseña cambiada exitosamente.\n"): printf("Contraseña no cambiada.\n");
            continue;
        }
        // Verificar los datos obtenidos del usuario
        int result = verify_data(ID, PASSWORD);

        switch (result)
        {
        case 3: // ID no encontrado
          printf("ID no encontrado: %s\n", ID);
            gpio_put(LED_NE_IC, 1); // Encender LED rojo
            sleep_ms(2000);         // Mantener LED encendido por 2 segundos
            gpio_put(LED_NE_IC, 0); // Apagar LED
            break;
        case 2: // Acceso concedido
            printf("Acceso concedido. ID: %s\n", ID);
            gpio_put(LED_ACCESO, 1); // Encender LED verde
            sleep_ms(5000);          // Mantener LED encendido por 5 segundos
            gpio_put(LED_ACCESO, 0); // Apagar LED
            break;
        case 1: // Usuario bloqueado
            printf("Usuario bloqueado: %s\n", ID);
            gpio_put(LED_NE_IC, 1); // Encender LED rojo
            sleep_ms(2000);         // Mantener LED encendido por 2 segundos
            gpio_put(LED_NE_IC, 0); // Apagar LED
            break;
        default: // Datos incorrectos
            printf("Acceso denegado para ID: %s\n", ID);
            gpio_put(LED_NE_IC, 1); // Encender LED rojo
            sleep_ms(2000);         // Mantener LED encendido por 2 segundos
            gpio_put(LED_NE_IC, 0); // Apagar LED
            break;
        }
    }
    return 0;
}

// Inicializar como deben de ser los gpios
void setUpKeypad()
{
    gpio_init_mask(maskOuts);          // Inicializar GPIOs de salidas
    gpio_set_dir_out_masked(maskOuts); // Configurar como salida

    gpio_init_mask(maskIns);         // Inicializar GPIOs de entradas
    gpio_set_dir_in_masked(maskIns); // Configurar como entrada

    // Configurar columnas como entradas con pull-down
    gpio_pull_down(COL_1);
    gpio_pull_down(COL_2);
    gpio_pull_down(COL_3);
    gpio_pull_down(COL_4);

    // Inicializar LED_ACCESO como salida
    gpio_init(LED_ACCESO);
    gpio_set_dir(LED_ACCESO, GPIO_OUT);

    // Inicializar LED_NE_IC como salida
    gpio_init(LED_NE_IC);
    gpio_set_dir(LED_NE_IC, GPIO_OUT);

    // Inicializar LED_ID como salida
    gpio_init(LED_ID);
    gpio_set_dir(LED_ID, GPIO_OUT);

}

// Función para obtener la entrada del usuario con límite de tiempo de 10 segundos
int8_t getUserInput(char *ID, char *PASSWORD)
{
    absolute_time_t start_time;
    bool first_key_detected = false;

    // Leer el ID del usuario con límite de tiempo
    for (int i = 0; i < ID_LENGTH; i++)
    {
        char key = '\0';
        // Esperar hasta que se presione una tecla
        while (key == '\0')
        {   
            gpio_put(LED_ID, !first_key_detected);
            key = getKey(); // Leer la tecla presionada
            sleep_ms(250);

            // Si se detecta la primera tecla, iniciar el temporizador
            if (!first_key_detected && key != '\0')
            {
                first_key_detected = true;
                start_time = get_absolute_time(); // Obtener tiempo de inicio para los 10 segundos
            }

            // Verificar si se ha superado el límite de tiempo de 10 segundos
            if (first_key_detected && absolute_time_diff_us(start_time, get_absolute_time()) > 30 * 1000000)
            {
                // Se excedió el tiempo
                printf("Tiempo límite excedido. Fallo en la entrada de datos.\n");
                gpio_put(LED_NE_IC, 1); // Encender LED rojo
                sleep_ms(2000);         // Mantener LED encendido 2 segundos
                gpio_put(LED_NE_IC, 0); // Apagar LED

                // Resetear ID y PASSWORD como prevención y volver al estado inicial para prender el led amarillo
                memset(ID, '\0', ID_LENGTH + 1);
                memset(PASSWORD, '\0', PASSWORD_LENGTH + 1);
                return -1;
            }
        }
        printf("%c\n", key);
        ID[i] = key; // Asignar la tecla presionada al ID
    }
    ID[ID_LENGTH] = '\0'; // Agregar carácter nulo al final
    bool isPasswordChange = true;
    for(uint8_t i = 0; i < ID_LENGTH; i++)
    {
      if (ID[i] != '#') isPasswordChange = false; 
    }
    if(isPasswordChange == true){
      printf("Cambio de contraseña activado.\n");
      return 2;
    }
    printf("%s\n", ID);
    bool LED_ID_state = true;
    absolute_time_t startYellowLEDTime = get_absolute_time();
    // Leer la contraseña del usuario con límite de tiempo
    for (int i = 0; i < PASSWORD_LENGTH; i++)
    {
        char key = '\0';
        // Esperar hasta que se presione una tecla
        while (key == '\0')
        {
            if(absolute_time_diff_us(startYellowLEDTime, get_absolute_time()) > 1 * 1000000)
            { // Cambiar estado de LED amarillo cada 2 seg.
                gpio_put(LED_ID, LED_ID_state);
                LED_ID_state = !LED_ID_state;
                startYellowLEDTime = get_absolute_time();
            }
            key = getKey(); // Leer la tecla presionada
            sleep_ms(250);

            // Verificar si se ha superado el límite de tiempo de 10 segundos
            if (first_key_detected && absolute_time_diff_us(start_time, get_absolute_time()) > 30 * 1000000)
            {
                // Se excedió el tiempo
                printf("Tiempo límite excedido. Fallo en la entrada de datos.\n");
                gpio_put(LED_ID, 0);    // Apagar LED amarillo
                gpio_put(LED_NE_IC, 1); // Encender LED rojo
                sleep_ms(2000);         // Mantener LED encendido 2 segundos
                gpio_put(LED_NE_IC, 0); // Apagar LED

                // Resetear ID y PASSWORD como prevención y volver al estado inicial
                memset(ID, '\0', ID_LENGTH + 1);
                memset(PASSWORD, '\0', PASSWORD_LENGTH + 1);
                return -1;
            }
        }

        PASSWORD[i] = key; // Asignar la tecla presionada a la contraseña
        
        printf("%c\n", key);
    }
    PASSWORD[PASSWORD_LENGTH] = '\0'; // Agregar carácter nulo al final
    gpio_put(LED_ID, 0); // Apagar LED amarillo antes de verificar datos
    printf("%s\n", PASSWORD);
    return 1;
}

// Función para leer la tecla presionada
char getKey()
{
    for (int row = 0; row < 4; row++)
    {
        // Poner todas las filas en bajo
        gpio_clr_mask(maskOuts);

        // Activar (poner en alto) la fila actual
        switch (row)
        {
        case 0:
            gpio_put(ROW_1, 1);
            break;
        case 1:
            gpio_put(ROW_2, 1);
            break;
        case 2:
            gpio_put(ROW_3, 1);
            break;
        case 3:
            gpio_put(ROW_4, 1);
            break;
        }

        // Leer columnas y verificar qué tecla se presionó
        if (gpio_get(COL_1))
            return keypad[row][0];
        if (gpio_get(COL_2))
            return keypad[row][1];
        if (gpio_get(COL_3))
            return keypad[row][2];
        if (gpio_get(COL_4))
            return keypad[row][3];
    }

    return '\0'; // No se presionó ninguna tecla
}

// Verificar los datos del usuario en la base de datos
int verify_data(const char *ID, const char *PASSWORD)
{
    for (int i = 0; i < NUM_USERS; i++)
    {
        if (strcmp(users[i].ID, ID) == 0)
        {
            // Verificar si el usuario ya está bloqueado
            if (users[i].blocked)
            {
                return 1; // Usuario bloqueado
            }
            // Verificar la contraseña
            if (strcmp(users[i].PASSWORD, PASSWORD) == 0)
            {
                // Contraseña correcta, resetear intentos fallidos
                users[i].attempts = 0;
                return 2; // Datos correctos
            }
            else
            {
                // Contraseña incorrecta, incrementar el contador de intentos fallidos
                users[i].attempts++;

                // Bloquear al usuario si ha alcanzado el número máximo de intentos
                if (users[i].attempts >= MAX_ATTEMPTS)
                {
                    users[i].blocked = 1;
                    printf("El usuario con ID: %s ha sido bloqueado por demasiados intentos fallidos.\n", ID);
                    return 1; // Usuario bloqueado
                }

                // Devolver 0 para indicar que la contraseña es incorrecta
                return 0;
            }
        }
    }
    return 3; // ID no encontrado
}

bool change_password(char *ID, char *PASSWORD)
{
    absolute_time_t start_time;
    bool first_key_detected = false;

    // Leer el ID del usuario con límite de tiempo
    for (int i = 0; i < ID_LENGTH; i++)
    {
        char key = '\0';
        // Esperar hasta que se presione una tecla
        while (key == '\0')
        {   
            gpio_put(LED_ID, !first_key_detected);
            key = getKey(); // Leer la tecla presionada
            sleep_ms(250);

            // Si se detecta la primera tecla, iniciar el temporizador
            if (!first_key_detected && key != '\0')
            {
                first_key_detected = true;
                start_time = get_absolute_time(); // Obtener tiempo de inicio para los 10 segundos
            }

            // Verificar si se ha superado el límite de tiempo de 10 segundos
            if (first_key_detected && absolute_time_diff_us(start_time, get_absolute_time()) > 30 * 1000000)
            {
                // Se excedió el tiempo
                printf("Tiempo límite excedido. Fallo en la entrada de datos.\n");
                gpio_put(LED_NE_IC, 1); // Encender LED rojo
                sleep_ms(2000);         // Mantener LED encendido 2 segundos
                gpio_put(LED_NE_IC, 0); // Apagar LED

                // Resetear ID y PASSWORD como prevención y volver al estado inicial para prender el led amarillo
                memset(ID, '\0', ID_LENGTH + 1);
                memset(PASSWORD, '\0', PASSWORD_LENGTH + 1);
                return false;
            }
        }
        printf("%c\n", key);
        ID[i] = key; // Asignar la tecla presionada al ID
    }
    ID[ID_LENGTH] = '\0'; // Agregar carácter nulo al final
    printf("%s\n", ID);
    bool isUserFound = false;
    int userIndx = 0;
    for (int i = 0; i < NUM_USERS; i++)
    {
        if (strcmp(users[i].ID, ID) == 0)
        {
            // Verificar si el usuario ya está bloqueado
            if (users[i].blocked)
            {
                printf("Este usuario se encuentra bloqueado. ");
                gpio_put(LED_ID, 0);    // Apagar LED amarillo
                gpio_put(LED_NE_IC, 1); // Encender LED rojo
                sleep_ms(2000);         // Mantener LED encendido 2 segundos
                gpio_put(LED_NE_IC, 0); // Apagar LED
                return false; // Usuario bloqueado
            }
            else
            {
                printf("Usuario encontrado. Ingresar contraseña actual.\n");
            }
            isUserFound = true;
            userIndx = i;
        }
        else if(i == (NUM_USERS - 1) && !isUserFound){
            gpio_put(LED_ID, 0);    // Apagar LED amarillo
            gpio_put(LED_NE_IC, 1); // Encender LED rojo
            sleep_ms(2000);         // Mantener LED encendido 2 segundos
            gpio_put(LED_NE_IC, 0); // Apagar LED          
            printf("Usuario no encontrado. ");
            return false;
        }
    }
    bool LED_ID_state = true;
    absolute_time_t startYellowLEDTime = get_absolute_time();
    // Leer la contraseña del usuario con límite de tiempo
    for (int i = 0; i < PASSWORD_LENGTH; i++)
    {
        char key = '\0';
        // Esperar hasta que se presione una tecla
        while (key == '\0')
        {
            if(absolute_time_diff_us(startYellowLEDTime, get_absolute_time()) > 1 * 1000000)
            { // Cambiar estado de LED amarillo cada seg.
                gpio_put(LED_ID, LED_ID_state);
                LED_ID_state = !LED_ID_state;
                startYellowLEDTime = get_absolute_time();
            }
            key = getKey(); // Leer la tecla presionada
            sleep_ms(250);

            // Verificar si se ha superado el límite de tiempo de 10 segundos
            if (first_key_detected && absolute_time_diff_us(start_time, get_absolute_time()) > 30 * 1000000)
            {
                // Se excedió el tiempo
                printf("Tiempo límite excedido. Fallo en la entrada de datos.\n");
                gpio_put(LED_ID, 0);    // Apagar LED amarillo
                gpio_put(LED_NE_IC, 1); // Encender LED rojo
                sleep_ms(2000);         // Mantener LED encendido 2 segundos
                gpio_put(LED_NE_IC, 0); // Apagar LED

                // Resetear ID y PASSWORD como prevención y volver al estado inicial
                memset(ID, '\0', ID_LENGTH + 1);
                memset(PASSWORD, '\0', PASSWORD_LENGTH + 1);
                return false;
            }
        }
        PASSWORD[i] = key; // Asignar la tecla presionada a la contraseña
        printf("%c\n", key);
    }
    PASSWORD[PASSWORD_LENGTH] = '\0'; // Agregar carácter nulo al final
    gpio_put(LED_ID, 0); // Apagar LED amarillo antes de verificar datos
    printf("%s\n", PASSWORD);
    if (strcmp(users[userIndx].PASSWORD, PASSWORD) == 0)
    {
        printf("Contraseña actual correcta. Ingresar nueva contraseña.\n");
        memset(PASSWORD, '\0', PASSWORD_LENGTH + 1);
        absolute_time_t startYellowLEDTime = get_absolute_time();
        // Leer la contraseña del usuario con límite de tiempo
        for (int i = 0; i < PASSWORD_LENGTH; i++)
        {
            char key = '\0';
            // Esperar hasta que se presione una tecla
            while (key == '\0')
            {
                if(absolute_time_diff_us(startYellowLEDTime, get_absolute_time()) > 2 * 1000000)
                { // Cambiar estado de LED amarillo cada 2 seg.
                    gpio_put(LED_ID, LED_ID_state);
                    LED_ID_state = !LED_ID_state;
                    startYellowLEDTime = get_absolute_time();
                }
                key = getKey(); // Leer la tecla presionada
                sleep_ms(250);

                // Verificar si se ha superado el límite de tiempo de 30 segundos
                if (first_key_detected && absolute_time_diff_us(start_time, get_absolute_time()) > 50 * 1000000)
                {
                    // Se excedió el tiempo
                    printf("Tiempo límite excedido. Fallo en la entrada de datos.\n");
                    gpio_put(LED_ID, 0);    // Apagar LED amarillo
                    gpio_put(LED_NE_IC, 1); // Encender LED rojo
                    sleep_ms(2000);         // Mantener LED encendido 2 segundos
                    gpio_put(LED_NE_IC, 0); // Apagar LED

                    // Resetear ID y PASSWORD como prevención y volver al estado inicial
                    memset(ID, '\0', ID_LENGTH + 1);
                    memset(PASSWORD, '\0', PASSWORD_LENGTH + 1);
                    return false;
                }
            }
            PASSWORD[i] = key; // Asignar la tecla presionada a la contraseña
            printf("%c\n", key);
        }
        PASSWORD[PASSWORD_LENGTH] = '\0'; // Agregar carácter nulo al final
        gpio_put(LED_ID, 0); // Apagar LED amarillo antes de verificar datos
        printf("%s\n", PASSWORD);
        strcpy(users[userIndx].PASSWORD, PASSWORD); // Cambio de contraseña efectuado
    }
    else
    { // Contraseña incorrecta
        // Devolver false para indicar que la contraseña es incorrecta
        printf("Contraseña actual incorrecta.\n");
        gpio_put(LED_ID, 0);    // Apagar LED amarillo
        gpio_put(LED_NE_IC, 1); // Encender LED rojo
        sleep_ms(2000);         // Mantener LED encendido 2 segundos
        gpio_put(LED_NE_IC, 0); // Apagar LED
        return false;
    }
    gpio_put(LED_ID, 0);    // Apagar LED amarillo
    gpio_put(LED_ACCESO, 1); // Encender LED rojo
    sleep_ms(10000);         // Mantener LED encendido 2 segundos
    gpio_put(LED_ACCESO, 0); // Apagar LED
    return true;
}
BOOTSELLED1239USBRaspberryPiPico©2020RP2-8020/21P64M15.00TTT