/*
  Vamos aprender a usar a máquina de estados finitos com o Arduino.

  As máquinas de estado finito (FSMs) são importantes para entender o
  lógica de tomada de decisão, bem como controlar os sistemas digitais.

  https://create.arduino.cc/projecthub/tolentinocotesta/let-s-learn-how-to-use-finite-state-machine-with-arduino-c524ac?ref=part&ref_id=8233&offset=13
*/

#include <YA_FSM.h>

// Semáforo de pedestres -> luz verde LIGADO até que o botão seja pressionado
#define YELLOW_TIME 2000
#define RED_TIME    10000
#define CALL_DELAY  5000

const byte BTN_CALL = 2;
const byte GREEN_LED = 12;
const byte YELLOW_LED = 11;
const byte RED_LED = 10;

// Auxiliar para imprimir informações em vez de inteiro quando o estado mudar
const char * const stateName[] PROGMEM = { "RED", "GREEN", "YELLOW", "CALL"};

// Alias ​​do Estado
enum State {RED, GREEN, YELLOW, CALL};

// Entrada (trig transição do estado GREEN para CALL, as outras transições no tempo limite)
bool callButton = false;

// Variáveis ​​de saída
bool redLed = false;
bool greenLed = false;
bool yellowLed = false;

// Cria um novo FSM
YA_FSM stateMachine;

// Define a função de retorno de chamada "on enter" (o mesmo para todos os estados "light")
void onEnter() {
  Serial.print(stateMachine.ActiveStateName());
  Serial.println(F(" light ON"));
}

// Define a função de retorno de chamada "on exit" (o mesmo para todos os estados "light")
void onExit() {
  Serial.print(stateMachine.ActiveStateName());
  Serial.println(F(" light OFF\n"));
}

// Define a função "on enter" para o estado do botão CALL
void onEnterCall() {
  Serial.println(F("Chamada registrada, aguarde um pouco."));
}

// Configura a máquina de estado
void setupStateMachine() {
  // Siga a ordem de enumeração para a definição do estado (será usado como índice)
  // Adiciona os estados
  stateMachine.AddState(stateName[RED], RED_TIME, onEnter, nullptr, onExit);
  stateMachine.AddState(stateName[GREEN], 0, onEnter, nullptr, onExit);
  stateMachine.AddState(stateName[YELLOW], YELLOW_TIME, onEnter, nullptr, onExit);
  stateMachine.AddState(stateName[CALL], CALL_DELAY, onEnterCall, nullptr, nullptr);

  stateMachine.AddAction(RED, YA_FSM::N, redLed);        // N -> Enquanto o estado estiver ativo, o led vermelho está LIGADO
  stateMachine.AddAction(GREEN, YA_FSM::S, greenLed);    // S -> SETA o led verde aceso
  stateMachine.AddAction(YELLOW, YA_FSM::R, greenLed);   // R -> RESETA o led verde
  stateMachine.AddAction(YELLOW, YA_FSM::N, yellowLed);  // N -> Enquanto o estado estiver ativo, o led amarelo está LIGADO

  // Adicione as transições com funções de callback de entrada dos gatilhos relacionados
  stateMachine.AddTransition(RED, GREEN, []() {
    // Neste exemplo, é apenas uma função lambda simples que retorna o valor do tempo limite do estado
    return stateMachine.CurrentState()->timeout;
  });

  stateMachine.AddTransition(YELLOW, RED, []() {
    return stateMachine.CurrentState()->timeout;
  });

  stateMachine.AddTransition(CALL, YELLOW, []() {
    return stateMachine.CurrentState()->timeout;
  });

  stateMachine.AddTransition(GREEN, CALL, callButton);
}

void setup() {
  // Configuração de entrada/saída
  pinMode(BTN_CALL, INPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(YELLOW_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);

  Serial.begin(115200);
  while (!Serial) {} // Necessário apenas para porta USB nativa
  Serial.println(F("Iniciando a máquina de estados finitos...\n"));

  setupStateMachine();
}

void loop() {
  // Leitura do botão
  callButton = (digitalRead(BTN_CALL) == LOW);

  // Atualiza a máquina de estado
  if (stateMachine.Update()) {
    Serial.print(F("Estado ativo: "));
    Serial.println(stateMachine.ActiveStateName());
  }

  // Define as saídas
  digitalWrite(RED_LED, redLed);
  digitalWrite(GREEN_LED, greenLed);
  digitalWrite(YELLOW_LED, yellowLed);
}