/// DISPLAY 4x7-segmento 5461AS-1 (45) VERIFICAR SE COMMON ANODE ou CATHODE
/// ON@ -> Cathode = HIGH; Anode = LOW (SINK com resistor ao PCF)

/// READ ME:
/// Pressiona MENU para definir um horario para o ALARME
/// Ao inserir digito válido, pressiona PLAY para avançar ao próximo campo
/// Concluido os 4 digitos, pressiona PLAY p/ salvar HORA DO ALARME
/// Pressiona POWER p/ Liga/desliga alarme.

#include <IRremote.hpp>       // Do not change header order.
#include <RTClib.h>
#include <Adafruit_PCF8574.h>

#define IR_PIN 8
#define BUZZER_PIN 13

RTC_DS1307 rtc; // &I2C =? 0x68 /// No slide diz 1302/1307. Se 1302 precisa mudar Lib
Adafruit_PCF8574 pcf; // &I2C =? 0x20 // 8574 -> 8 I/O; 8575 -> 16 I/O

int display[4] = {4, 5, 6, 7};
byte binary[10] = {0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110,
                   0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111};

int prevSec = 0, irCode = 0;;
int timeNow[5];
int alarmSettings[4];
bool blink = true, alarmArmed = false, alarmBeeping = false, timeSet = false;

void setup() {
  Serial.begin(9600);
  pinMode(BUZZER_PIN, OUTPUT);
  
  if (!rtc.begin()) {
    Serial.println("Nao foi possivel conectar ao RTC DS1307");
    while (1);
  }

  if (!pcf.begin(0x20, &Wire)) {
    Serial.println("Nao foi possivel conectar ao PCF8574");
    while (1);
  }

  for (int i = 0; i < 8; i++) {
    pcf.pinMode(i, OUTPUT);
    pcf.digitalWrite(i, HIGH);
  }
  for (int i = 0; i < 4; i++) {
    pinMode(display[i], OUTPUT);
    digitalWrite(display[i], LOW);
  }
  IrReceiver.begin(IR_PIN);
}

void loop() {
  getTime();
  printTime();
  if (alarmArmed) checkAlarm();
  irCode = irReceive();
  if (irCode == 0xE2) setAlarmTime();
  if (irCode == 0xA2) alarmArmed = !alarmArmed;
}

void getTime() {
  DateTime n = rtc.now();
  timeNow[0] = n.hour() / 10;
  timeNow[1] = n.hour() % 10;
  timeNow[2] = n.minute() / 10;
  timeNow[3] = n.minute() % 10;
  timeNow[4] = n.second();
}

void printTime() {
  if (timeNow[4] != prevSec) {
    prevSec = timeNow[4];
    blink = !blink;
  }
  printDigit(timeNow[0], 7, false);
  printDigit(timeNow[1], 6, blink);
  printDigit(timeNow[2], 5, false);
  printDigit(timeNow[3], 4, alarmArmed);
}

void printDigit(int val, int disp, bool dot) {
  digitalWrite(disp, LOW);
  for (int i = 0; i < 7; i++) pcf.digitalWrite(i, (binary[val] & (1 << i)) ? LOW : HIGH);
  pcf.digitalWrite(7, dot ? LOW : HIGH);
  digitalWrite(disp, HIGH);
  delay(5); // Para efeito Persistência de Visão (POV)
  digitalWrite(disp, LOW);
}

int irReceive() {
  int received = 0;
  if (IrReceiver.decode()) {
    if (IrReceiver.decodedIRData.protocol == NEC) {
      received = IrReceiver.decodedIRData.command;
      // Serial.print("Command: 0x");
      // Serial.println(received, HEX);
    }
    IrReceiver.resume();
  }
  return received;
}

int getButtonFromHex(int hex) {
  int hexCode[10] = {0x68, 0x30, 0x18, 0x7A, 0x10,
                     0x38, 0x5A, 0x42, 0x4A, 0x52}; // 0 à 9
  if (hex == 0xA2) return 10; // POWER
  if (hex == 0xE2) return 11; // MENU
  if (hex == 0xA8) return 12; // PLAY
  for (int i = 0; i < 10; i++) {
    if (hexCode[i] == hex) return i;
  }
  return -1;
}

void setAlarmTime() {
  bool rollback = false;
  int alarm[4] = {0};
  if (timeSet) for (int i = 0; i < 4; i++) alarm[i] = alarmSettings[i];
  int code = 0;
  int count = 0;

  while (!rollback){
    for (int i = 0; i < 4; i++) timeNow[i] = alarm[i];
    printTime();
    if (count > 3) break;
    code = getButtonFromHex(irReceive());

    if (code == 11) rollback = true;
    else if (code == 12) count++;

    else if (code >= 0 && code < 10) {
      if (count == 0) {
        if (code == 0 || code == 1) alarm[0] = code;
        else if (code == 2) {
          alarm[0] = 2;
          alarm[1] = (alarm[1] < 4) ? alarm[1] : 3;
        } else continue;
      } else if (count == 1) {
        if (alarm[0] < 2) alarm[1] = code;
        else alarm[1] = (code < 4) ? code : 3;
      } else if (count == 2) alarm[2] = (code < 6) ? code : 5;
      else alarm[3] = code;
    }
    else continue;
  }
  if (!rollback) {
    for (int i = 0; i < 4; i++) alarmSettings[i] = alarm[i];
    timeSet = true;
  }
}

void checkAlarm() {
  bool bip = true;
  for (int i = 0; i < 4; i++) {
    bip = (alarmSettings[i] != timeNow[i]) ? false : bip;
  }
  while (bip) {
    getTime();
    printTime();
    if (irReceive() == 0xA2) {
      alarmArmed = false;
      bip = false;
    }
    digitalWrite(BUZZER_PIN, HIGH);
    delay(100);
    digitalWrite(BUZZER_PIN, LOW);
    delay(200);
  }
}
GND5VSDASCLSQWRTCDS1307+
pcf8574Breakout