/**
 * Riconoscitore di Sequenza di Parole
 *
 * Questo programma implementa un sistema di riconoscimento di sequenza di parole utilizzando Arduino.
 * L'utente inserisce una sequenza di parole tramite la linea seriale.
 * Le parole vengono memorizzate in una lista concatenata e quando la sequenza corrisponde a una
 * sequenza predefinita, un LED verde lampeggia e rimane acceso, indicando che l'allarme è attivato.
 * Se la sequenza viene reinserita, un LED rosso lampeggia e rimane acceso, indicando
 * che l'allarme è disattivato, e così via.
 *
 * https://wokwi.com/projects/425709756229035009
 *
 * @author Filippo Bilardo
 * @version 1.0 17/03/25 - Versione iniziale
 */
#include <Arduino.h>
const int PIN_LED_VERDE = 8;               // Pin per il LED verde
const int PIN_LED_ROSSO = 13;              // Pin per il LED rosso
const int LUNGHEZZA_SEQUENZA = 3;          // Numero di parole nella sequenza da riconoscere
const int MAX_LUNGHEZZA_PAROLA = 20;       // Lunghezza massima di una parola
bool allarmeAttivo = false;                // Stato dell'allarme (attivo/disattivo)
// Parole della sequenza corretta
const char* sequenzaCorretta[] = {"apri", "sesamo", "ora"};
// Struttura per il nodo della lista concatenata
struct Nodo {
  char parola[MAX_LUNGHEZZA_PAROLA];
  Nodo* prossimo;
  
  // Costruttore per facilitare la creazione di nodi
  Nodo(const char* _parola) {
    strncpy(parola, _parola, MAX_LUNGHEZZA_PAROLA);
    parola[MAX_LUNGHEZZA_PAROLA - 1] = '\0';  // Assicura la terminazione
    prossimo = nullptr;
  }
};
// Classe per gestire la lista concatenata
class ListaParole {
private:
  Nodo* testa;
  Nodo* coda;
  int lunghezza;
public:
  // Costruttore
  ListaParole() : testa(nullptr), coda(nullptr), lunghezza(0) {}
  
  // Distruttore
  ~ListaParole() {
    svuota();
  }
  
  // Aggiunge una parola alla fine della lista
  void aggiungi(const char* parola) {
    Nodo* nuovoNodo = new Nodo(parola);
    
    if (testa == nullptr) {
      // Lista vuota
      testa = nuovoNodo;
      coda = nuovoNodo;
    } else {
      // Aggiunge in coda
      coda->prossimo = nuovoNodo;
      coda = nuovoNodo;
    }
    
    lunghezza++;
    
    // Se la lista supera la lunghezza massima, rimuove il primo elemento
    if (lunghezza > LUNGHEZZA_SEQUENZA) {
      rimuoviPrimo();
    }
  }
  
  // Rimuove il primo elemento della lista
  void rimuoviPrimo() {
    if (testa == nullptr) return;
    
    Nodo* temp = testa;
    testa = testa->prossimo;
    delete temp;
    
    lunghezza--;
    
    // Se la lista è vuota, aggiorna anche la coda
    if (testa == nullptr) {
      coda = nullptr;
    }
  }
  
  // Svuota completamente la lista
  void svuota() {
    while (testa != nullptr) {
      rimuoviPrimo();
    }
  }
  
  // Confronta la lista con la sequenza corretta
  bool confrontaConSequenza(const char* sequenza[], int lunghezzaSequenza) {
    if (lunghezza != lunghezzaSequenza) return false;
    
    Nodo* corrente = testa;
    for (int i = 0; i < lunghezzaSequenza; i++) {
      if (corrente == nullptr || strcmp(corrente->parola, sequenza[i]) != 0) {
        return false;
      }
      corrente = corrente->prossimo;
    }
    return true;
  }
  
  // Stampa il contenuto della lista
  void stampa() {
    Serial.print("Sequenza attuale: ");
    
    Nodo* corrente = testa;
    while (corrente != nullptr) {
      Serial.print(corrente->parola);
      Serial.print(" ");
      corrente = corrente->prossimo;
    }
    Serial.println();
  }
  
  // Restituisce la lunghezza attuale della lista
  int getLunghezza() const {
    return lunghezza;
  }
};
// Oggetto lista globale
ListaParole listaParole;
// Buffer per la parola corrente
char parolaCorrente[MAX_LUNGHEZZA_PAROLA];
int indiceParola = 0;
// Prototipi delle funzioni
void LedVerdeConfigura();
void LedVerdeAccendi();
void LedVerdeSpegni();
void LedVerdeLampeggia(int volte, int ritardoMs);
void LedRossoConfigura();
void LedRossoAccendi();
void LedRossoSpegni();
void LedRossoLampeggia(int volte, int ritardoMs);
void SerialeMostraRicezione(const char* parola);
void AllarmeMostraStato();
void AllarmeImpostaStato(bool attivo);
void elaboraParola(const char* parola);
void aggiungiCarattere(char c);
void verificaSequenza();
void setup() {
  Serial.begin(9600);           // Inizializza la comunicazione seriale
  LedVerdeConfigura();          // Configura il pin del LED verde
  LedRossoConfigura();          // Configura il pin del LED rosso
  
  // Messaggio iniziale
  Serial.println("Sistema di riconoscimento sequenza di parole avviato");
  Serial.println("Inserisci parole terminate da un punto (.)");
  Serial.println("Sequenza corretta: \"apri sesamo ora\"");
}
void loop() {
  if (Serial.available() > 0) {
    char carattereInput = Serial.read();
    
    // Gestione dei caratteri speciali
    if (carattereInput == '.' || carattereInput == '\n' || carattereInput == ' ') {
      // Termina la parola corrente e la elabora
      if (indiceParola > 0) {
        parolaCorrente[indiceParola] = '\0';  // Termina la stringa
        elaboraParola(parolaCorrente);
        indiceParola = 0;  // Resetta l'indice per la prossima parola
      }
    } else {
      // Aggiungi il carattere alla parola corrente
      aggiungiCarattere(carattereInput);
    }
  }
}
/**
 * Aggiunge un carattere alla parola corrente
 */
void aggiungiCarattere(char c) {
  if (indiceParola < MAX_LUNGHEZZA_PAROLA - 1) {
    parolaCorrente[indiceParola++] = c;
  }
}
/**
 * Elabora una parola completa
 */
void elaboraParola(const char* parola) {
  SerialeMostraRicezione(parola);
  
  // Aggiunge la parola alla lista
  listaParole.aggiungi(parola);
  
  // Stampa la sequenza corrente
  listaParole.stampa();
  
  // Verifica se la sequenza corrisponde a quella corretta
  verificaSequenza();
}
/**
 * Verifica se la sequenza corrente corrisponde a quella corretta
 */
void verificaSequenza() {
  if (listaParole.confrontaConSequenza(sequenzaCorretta, LUNGHEZZA_SEQUENZA)) {
    Serial.println("Sequenza riconosciuta!");
    
    // Cambia lo stato dell'allarme (attiva/disattiva)
    AllarmeImpostaStato(!allarmeAttivo);
  }
}
/**
 * Configura il pin del LED verde
 */
void LedVerdeConfigura() {
  pinMode(PIN_LED_VERDE, OUTPUT);
  LedVerdeSpegni();
}
/**
 * Accende il LED verde
 */
void LedVerdeAccendi() {
  digitalWrite(PIN_LED_VERDE, HIGH);
}
/**
 * Spegne il LED verde
 */
void LedVerdeSpegni() {
  digitalWrite(PIN_LED_VERDE, LOW);
}
/**
 * Fa lampeggiare il LED verde per un determinato numero di volte
 * @param volte Il numero di volte che il LED deve lampeggiare
 * @param ritardoMs Il tempo di attesa tra un lampeggio e l'altro in millisecondi
 */
void LedVerdeLampeggia(int volte, int ritardoMs) {
  for (int i = 0; i < volte; i++) {
    LedVerdeAccendi();
    delay(ritardoMs);
    LedVerdeSpegni();
    delay(ritardoMs);
  }
}
/**
 * Configura il pin del LED rosso
 */
void LedRossoConfigura() {
  pinMode(PIN_LED_ROSSO, OUTPUT);
  LedRossoSpegni();
}
/**
 * Accende il LED rosso
 */
void LedRossoAccendi() {
  digitalWrite(PIN_LED_ROSSO, HIGH);
}
/**
 * Spegne il LED rosso
 */
void LedRossoSpegni() {
  digitalWrite(PIN_LED_ROSSO, LOW);
}
/**
 * Fa lampeggiare il LED rosso per un determinato numero di volte
 * @param volte Il numero di volte che il LED deve lampeggiare
 * @param ritardoMs Il tempo di attesa tra un lampeggio e l'altro in millisecondi
 */
void LedRossoLampeggia(int volte, int ritardoMs) {
  for (int i = 0; i < volte; i++) {
    LedRossoAccendi();
    delay(ritardoMs);
    LedRossoSpegni();
    delay(ritardoMs);
  }
}
/**
 * Indica la ricezione di una parola
 * @param parola La parola ricevuta
 */
void SerialeMostraRicezione(const char* parola) {
  LedVerdeAccendi();
  LedRossoAccendi();
  Serial.print("Ricevuto: ");
  Serial.println(parola);
  delay(50);
  
  // Mostra lo stato dell'allarme
  AllarmeMostraStato();
}
/**
 * Mostra lo stato dell'allarme (attivo/disattivo)
 */
void AllarmeMostraStato() {
  if (allarmeAttivo) {
    LedVerdeAccendi();
    LedRossoSpegni();
  } else {
    LedVerdeSpegni();
    LedRossoAccendi();
  }
}
/**
 * Imposta lo stato dell'allarme (attivo/disattivo)
 * @param attivo true per attivare l'allarme, false per disattivarlo
 */
void AllarmeImpostaStato(bool attivo) {
  allarmeAttivo = attivo;
  
  if (attivo) {
    // Attiva l'allarme
    Serial.println("Allarme attivato");
    LedVerdeLampeggia(3, 200);
    LedVerdeAccendi();
    LedRossoSpegni();
  } else {
    // Disattiva l'allarme
    Serial.println("Allarme disattivato");
    LedRossoLampeggia(3, 200);
    LedRossoAccendi();
    LedVerdeSpegni();
  }
}