/**
 *  Arduino Digital Alarm Clock
 *  Copyright (C) 2020, Uri Shaked. MIT License.
 *
 *  Descrição rápida:
 *  - Display 4 dígitos/7 segmentos via SevSeg
 *  - 3 botões: hora, minuto, alarme (com auto-repeat)
 *  - Máquina de estados para UI (relógio, status alarme, hora alarme, tocando, soneca)
 */
#include <SevSeg.h>
#include "Button.h"
#include "AlarmTone.h"
#include "Clock.h"
#include "config.h"
// GPIOs
const int COLON_PIN   = 13; // Dois-pontos do display (nível invertido: LOW = aceso)
const int SPEAKER_PIN = A3; // Buzzer/alto-falante (usado como digital)
// Botões (entradas analógicas usadas como digitais)
Button hourButton(A0);
Button minuteButton(A1);
Button alarmButton(A2);
// Serviços
AlarmTone alarmTone;
Clock clock;
SevSeg sevseg;
// Estados de UI
enum DisplayState {
  DisplayClock,        // Exibe HHMM, permite ajustar hora/minuto
  DisplayAlarmStatus,  // Mostra " on " / " off " após toggle do alarme
  DisplayAlarmTime,    // Exibe/edita hora do alarme (HHMM)
  DisplayAlarmActive,  // Alarme tocando; curto = soneca, longo = parar
  DisplaySnooze,       // Tela "****" por alguns ms após ativar soneca
};
DisplayState displayState = DisplayClock;
long lastStateChange = 0;
// Helpers de estado/tempo
void changeDisplayState(DisplayState newValue) {
  displayState = newValue;
  lastStateChange = millis();
}
long millisSinceStateChange() {
  return millis() - lastStateChange;
}
// Controle do "colon" (dois-pontos) do display (LOW = aceso)
void setColon(bool value) {
  digitalWrite(COLON_PIN, value ? LOW : HIGH);
}
// Exibe hora atual no display e pisca os dois-pontos a cada segundo
void displayTime() {
  DateTime now = clock.now();
  bool blinkState = (now.second() % 2) == 0;
  sevseg.setNumber(now.hour() * 100 + now.minute()); // Formato HHMM
  setColon(blinkState);
}
// Estado principal: relógio
void clockState() {
  displayTime();
  // Se botão ALARME foi solto e o alarme está ativo, entrar no estado "tocando"
  if (alarmButton.read() == Button::RELEASED && clock.alarmActive()) {
    (void)alarmButton.has_changed();   // limpa a borda na lib Button
    changeDisplayState(DisplayAlarmActive);
    return;
  }
  // Ajustes hora/minuto com auto-repeat
  if (hourButton.pressed()) {
    clock.incrementHour();
  }
  if (minuteButton.pressed()) {
    clock.incrementMinute();
  }
  // Toggle do alarme e mostra status por alguns ms
  if (alarmButton.pressed()) {
    clock.toggleAlarm();
    changeDisplayState(DisplayAlarmStatus);
  }
}
// Mostra " on " / " off " por ALARM_STATUS_DISPLAY_TIME
void alarmStatusState() {
  setColon(false);
  sevseg.setChars(clock.alarmEnabled() ? " on" : " off");
  if (millisSinceStateChange() > ALARM_STATUS_DISPLAY_TIME) {
    changeDisplayState(clock.alarmEnabled() ? DisplayAlarmTime : DisplayClock);
    return;
  }
}
// Exibe/edita a hora do alarme (HHMM) por ALARM_HOUR_DISPLAY_TIME
void alarmTimeState() {
  DateTime alarm = clock.alarmTime();
  sevseg.setNumber(alarm.hour() * 100 + alarm.minute(), -1 /* sem ponto */);
  // Sai por timeout ou botão ALARME
  if (millisSinceStateChange() > ALARM_HOUR_DISPLAY_TIME || alarmButton.pressed()) {
    changeDisplayState(DisplayClock);
    return;
  }
  // Edição com auto-repeat; ao editar, renova o timeout
  if (hourButton.pressed()) {
    clock.incrementAlarmHour();
    lastStateChange = millis();
  }
  if (minuteButton.pressed()) {
    clock.incrementAlarmMinute();
    lastStateChange = millis();
  }
  if (alarmButton.pressed()) {
    changeDisplayState(DisplayClock);
  }
}
// Alarme tocando: gerencia som, soneca (curto) e parar (longo)
void alarmState() {
  displayTime(); // Continua mostrando a hora
  // Enquanto o botão está sendo "lido como solto", manter tocando
  if (alarmButton.read() == Button::RELEASED) {
    alarmTone.play();
  }
  // Se pressionado, silencia (enquanto pressiona)
  if (alarmButton.pressed()) {
    alarmTone.stop();
  }
  // Na borda de soltura, decide soneca (curto) ou parar (longo)
  if (alarmButton.released()) {
    alarmTone.stop();
    bool longPress = alarmButton.repeat_count() > 0; // >=1 s conforme set_repeat
    if (longPress) {
      clock.stopAlarm();                // para o alarme
      changeDisplayState(DisplayClock);
    } else {
      clock.snooze();                   // agenda soneca
      changeDisplayState(DisplaySnooze);
    }
  }
}
// Tela de soneca por alguns ms
void snoozeState() {
  sevseg.setChars("****");
  if (millisSinceStateChange() > SNOOZE_DISPLAY_TIME) {
    changeDisplayState(DisplayClock);
    return;
  }
}
void setup() {
  Serial.begin(115200);
  clock.begin(); // inicia RTC/relógio interno e controle de alarme
  // Configura botões com auto-repeat
  hourButton.begin();
  hourButton.set_repeat(500, 200);   // 0,5 s para iniciar; repete a cada 200 ms
  minuteButton.begin();
  minuteButton.set_repeat(500, 200);
  alarmButton.begin();
  alarmButton.set_repeat(1000, -1);  // define limite de "pressão longa" em ~1 s
  alarmTone.begin(SPEAKER_PIN); // inicializa saída para buzzer
  pinMode(COLON_PIN, OUTPUT);
  // Configuração do display 7 segmentos (4 dígitos)
  byte digits = 4;
  byte digitPins[]   = {2, 3, 4, 5};
  byte segmentPins[] = {6, 7, 8, 9, 10, 11, 12};
  bool resistorsOnSegments = false;  // resistores não estão nos segmentos
  bool updateWithDelays    = false;  // multiplex sem delays bloqueantes
  bool leadingZeros        = true;   // mostra zeros à esquerda (ex.: 09:03)
  bool disableDecPoint     = true;   // ponto decimal desativado
  sevseg.begin(DISPLAY_TYPE, digits, digitPins, segmentPins,
               resistorsOnSegments, updateWithDelays, leadingZeros, disableDecPoint);
  sevseg.setBrightness(90); // 0–100
}
void loop() {
  // Necessário para o multiplex do display (chamar sempre e com frequência)
  sevseg.refreshDisplay();
  // Máquina de estados da interface
  switch (displayState) {
    case DisplayClock:        clockState();      break;
    case DisplayAlarmStatus:  alarmStatusState();break;
    case DisplayAlarmTime:    alarmTimeState();  break;
    case DisplayAlarmActive:  alarmState();      break;
    case DisplaySnooze:       snoozeState();     break;
  }
}