/*
 * УСТАНОВКА ВРЕМЕНИ
 * При длительном нажатии первой кнопки часы переходит в режим установки часов.
 * Второй кнопкой короткими нажатиями увеличивается значение часов на 1.
 * Повторное длительное нажатие первой кнопки переходит в режим установки минут.
 * Повторное длительное нажатие первой кнопки выйдет из режима настройки времени.
 * 
 * УСТАНОВКА БУДИЛЬНИКА
 * Аналогично установки времени, но для переключения режимов используется третья кнопка.
 * 
 * ВЗВЕДЕНИЕ БУДИЛЬНИКА
 * Длительное нажатие второй кнопки переключает взведение будильника.
 * 
 * СРАБАТЫВАНИЕ БУДИЛЬНИКА
 * После срабатывания будильника отключение производится любой кнопкой.
 * 
*/

#define ON  1
#define OFF  0

#define BUTTON_1_PIN  A1
#define BUTTON_2_PIN  A2
#define BUTTON_3_PIN  A3

#define BEEPER_PIN    3

#define LED_1_PIN     13
#define LED_2_PIN     12
#define LED_3_PIN     11
#define LED_4_PIN     10

#define LED_1         1
#define LED_2         2
#define LED_3         4
#define LED_4         8
#define LED_ALL       15

#define LATCH_DIO     4 // Линия синхронизации данных
#define CLK_DIO       7 // Линия тактирования 
#define DATA_DIO      8 // Линия передачи данных

#define BUTTON_PRESSED_IND        (0 << 6)
#define BUTTON_SHORT_RELEASE_IND  (1 << 6)
#define BUTTON_LONG_PRESSED_IND   (2 << 6)
#define BUTTON_LONG_RELEASE_IND   (3 << 6)

#define BUTTON_1_PRESSED        (1 |  BUTTON_PRESSED_IND)
#define BUTTON_1_SHORT_RELEASE  (1 |  BUTTON_SHORT_RELEASE_IND)
#define BUTTON_1_LONG_PRESSED   (1 |  BUTTON_LONG_PRESSED_IND)
#define BUTTON_1_LONG_RELEASE   (1 |  BUTTON_LONG_RELEASE_IND)

#define BUTTON_2_PRESSED        (2 |  BUTTON_PRESSED_IND)
#define BUTTON_2_SHORT_RELEASE  (2 |  BUTTON_SHORT_RELEASE_IND)
#define BUTTON_2_LONG_PRESSED   (2 |  BUTTON_LONG_PRESSED_IND)
#define BUTTON_2_LONG_RELEASE   (2 |  BUTTON_LONG_RELEASE_IND)

#define BUTTON_3_PRESSED        (3 |  BUTTON_PRESSED_IND)
#define BUTTON_3_SHORT_RELEASE  (3 |  BUTTON_SHORT_RELEASE_IND)
#define BUTTON_3_LONG_PRESSED   (3 |  BUTTON_LONG_PRESSED_IND)
#define BUTTON_3_LONG_RELEASE   (3 |  BUTTON_LONG_RELEASE_IND)

#define BUTTON_SAMPLE_INTERVAL 2000

const byte LED[] = {LED_1_PIN, LED_2_PIN, LED_3_PIN, LED_4_PIN};

const byte SEGMENT_DIGITS[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};
const byte SEGMENT_ALPHA[] = {136, 131, 167, 161, 134, 142, 144, 139 ,207, 241, 182, 199, 182, 171, 163, 140, 152, 175, 146, 135, 227, 182, 182, 182, 145, 182};
const byte SEGMENT_NUM[] = {0xF1,0xF2,0xF4,0xF8};

class Shield {
  public:
  void initShield();

  byte getButton();

  void beep(unsigned int on_period = 50,
            unsigned int off_period = 0,
            byte cycles = 0,
            unsigned int loop_cycles = 0,
            unsigned int loop_delay_period = 0
           );

  void blinkLeds(byte led, int millsec, bool turnoff_delay);
  void blinkLeds(byte led1, byte led2, int millsec, bool turnoff_delay);
  void blinkLeds(byte led1, byte led2, byte led3, int millsec, bool turnoff_delay);
  void blinkLeds(int millsec, bool turnoff_delay);

  void writeValue(byte seg, byte val);
  void writeNumber(byte seg, byte val);
  void writeDisplay(int val);

  private:
  byte readButton(byte btn, byte &btnVal);

  volatile unsigned long m_timerButtonNext;
  volatile unsigned long m_timerButtonTo = 1000;
  volatile unsigned long m_timerButton = 0;

  volatile unsigned long m_timerBeeperNext;
  volatile unsigned long m_timerBeeperTo = 1;
  volatile unsigned long m_timerBeeperOnPeriod = 0;
  volatile unsigned long m_timerBeeperOffPeriod = 0;
  volatile unsigned long m_timerBeeperLoopDelayPeriod = 0;

};


Shield MFS;

void setup () {
  MFS.initShield();

}

enum class Mode {
  CLOCK,
  SET_HOUR,
  SET_MINUTE,
  ALARM,
  ALARM_HOUR,
  ALARM_MINUTE,
};

unsigned int hours = 0;
unsigned int minutes = 0;
unsigned int seconds = 0;

int alarmHour = 0;
int alarmMinute = 0;
bool alarmEnabled = false;

bool beep = false;

unsigned long counter_next;
unsigned long counter_to = 1000;
unsigned long fps_next;
unsigned long fps_to = 10;
unsigned long beep_next;
unsigned long beep_to = 200;

Mode clockMode = Mode::CLOCK;

void loop() {

  unsigned long tm = millis();
  if (counter_next < tm) {
    counter_next = tm + counter_to;

    if (clockMode != Mode::SET_HOUR && clockMode != Mode::SET_MINUTE) {
      
      if (seconds >= 60) {
        seconds = 0;
        minutes++;
        if (alarmEnabled && hours == alarmHour && minutes == alarmMinute) {
          clockMode = Mode::ALARM;
        }
      }
      else seconds++;
      
      if (minutes >= 60) {
        minutes = 0;
        hours++;
      }
      if (hours >= 24) {
        hours = 0;
      }
    }
  }


  if (beep_next < tm) {
    beep_next = tm + beep_to;
    
    if (clockMode == Mode::ALARM && beep) {
      beep = false;
      digitalWrite(BEEPER_PIN, LOW);
      tone(BEEPER_PIN, 100);
    }
    else {
      beep = true;
      noTone(BEEPER_PIN);
      digitalWrite(BEEPER_PIN, HIGH);
    }
  }


  if (clockMode == Mode::ALARM_HOUR || clockMode == Mode::ALARM_MINUTE) {
    MFS.writeNumber(0, (alarmHour / 10) % 10);
    MFS.writeNumber(1, alarmHour % 10);
    MFS.writeNumber(2, (alarmMinute / 10) % 10);
    MFS.writeNumber(3, alarmMinute % 10);
  }
  else if (clockMode == Mode::ALARM) {
    MFS.writeValue(0, 0xBf);
    MFS.writeValue(1, 0xBf);
    MFS.writeValue(2, 0xBf);
    MFS.writeValue(3, 0xBf);
  }
  else {
    MFS.writeNumber(0, (hours / 10) % 10);
    MFS.writeNumber(1, hours % 10);
    MFS.writeNumber(2, (minutes / 10) % 10);
    MFS.writeNumber(3, minutes % 10);
  }

  if (fps_next < tm) {
    fps_next = tm + fps_to;
    byte btn = MFS.getButton();
    if (btn) {
      if (clockMode == Mode::ALARM) {
        clockMode = Mode::CLOCK;
      }
      else {
        byte buttonNumber = btn & B00111111;
        byte buttonAction = btn & B11000000;

        switch (buttonNumber) {
          case 1:
            if (buttonAction == BUTTON_LONG_PRESSED_IND) {
              Serial.println("BUTTON_1");
              if (clockMode == Mode::CLOCK) {
                clockMode = Mode::SET_HOUR;
                digitalWrite(LED_1_PIN, LOW);
              }
              else if (clockMode == Mode::SET_HOUR) {
                clockMode = Mode::SET_MINUTE;
                MFS.blinkLeds(LED_1_PIN, 50, true);
                digitalWrite(LED_1_PIN, LOW);
              }
              else if (clockMode == Mode::SET_MINUTE) {
                clockMode = Mode::CLOCK;
                digitalWrite(LED_1_PIN, HIGH);
              };
            }
            break;
          case 2:
            if (buttonAction == BUTTON_SHORT_RELEASE_IND) {
              switch (clockMode) {
                case Mode::SET_HOUR:
                  hours++;
                  if (hours >= 24) { hours = 0; }
                  break;
                case Mode::SET_MINUTE:
                  minutes++;
                  if (minutes >= 60) { minutes = 0; }
                  break;
                case Mode::ALARM_HOUR:
                  alarmHour++;
                  if (alarmHour >= 24) { alarmHour = 0; }
                  break;
                case Mode::ALARM_MINUTE:
                  alarmMinute++;
                  if (alarmMinute >= 60) { alarmMinute = 0; }
                  break;
                default: break;
              }
            }
            else if (buttonAction == BUTTON_LONG_PRESSED_IND) {
              alarmEnabled = !alarmEnabled;
              if (alarmEnabled) digitalWrite(LED_2_PIN, LOW);
              else digitalWrite(LED_2_PIN, HIGH);
            }
            else if (buttonAction == BUTTON_LONG_RELEASE_IND) {
              MFS.blinkLeds(LED_4_PIN, 50, true);
            }
            break;
          case 3:
            if (buttonAction == BUTTON_LONG_PRESSED_IND) {
              if (clockMode == Mode::CLOCK) {
                clockMode = Mode::ALARM_HOUR;
                digitalWrite(LED_4_PIN, LOW);
              }
              else if (clockMode == Mode::ALARM_HOUR) {
                clockMode = Mode::ALARM_MINUTE;
                MFS.blinkLeds(LED_4_PIN, 50, true);
                digitalWrite(LED_4_PIN, LOW);
              }
              else if (clockMode == Mode::ALARM_MINUTE) {
                clockMode = Mode::CLOCK;
                digitalWrite(LED_4_PIN, HIGH);
              };
            }
            break;
        }
      }
    }
  }
}





void Shield::initShield() {
  pinMode(LED[0], OUTPUT);
  pinMode(LED[1], OUTPUT);
  pinMode(LED[2], OUTPUT);
  pinMode(LED[3], OUTPUT);

  digitalWrite(LED[0], HIGH);
  digitalWrite(LED[1], HIGH);
  digitalWrite(LED[2], HIGH);
  digitalWrite(LED[3], HIGH);

  pinMode(LATCH_DIO,OUTPUT);        // Устанавливаем вывод как выход
  pinMode(CLK_DIO,OUTPUT);          // Устанавливаем вывод как выход
  pinMode(DATA_DIO,OUTPUT);         // Устанавливаем вывод как выход

  pinMode(BEEPER_PIN, OUTPUT);
  digitalWrite(BEEPER_PIN, HIGH);

  pinMode(BUTTON_1_PIN, INPUT);
  pinMode(BUTTON_2_PIN, INPUT);
  pinMode(BUTTON_3_PIN, INPUT);
}

byte Shield::readButton(byte btn, byte &btnVal) {
  int val = digitalRead(btn);
  if (btnVal == LOW && val == LOW && m_timerButton == BUTTON_SAMPLE_INTERVAL) {
    m_timerButton = 0;
    return BUTTON_LONG_PRESSED_IND;
  }
  else if (val == HIGH && btnVal != val && m_timerButton < BUTTON_SAMPLE_INTERVAL) {
    btnVal = HIGH;
    m_timerButton = 0;
    return BUTTON_SHORT_RELEASE_IND;
  }
  else if (val == HIGH && btnVal != val) {
    btnVal = HIGH;
    m_timerButton = 0;
    return BUTTON_LONG_RELEASE_IND;
  }
  else if (val == LOW) {
    btnVal = val;
    return BUTTON_PRESSED_IND;
  }
  return 0;
}

byte Shield::getButton() {
  static byte s_valButton_1;
  static byte s_valButton_2;
  static byte s_valButton_3;
  unsigned long tm = millis();
  if (m_timerButtonNext < tm) {
    m_timerButtonNext = tm + m_timerButtonTo;
    if (s_valButton_1 == LOW || s_valButton_2 == LOW || s_valButton_3 == LOW)
      m_timerButton += BUTTON_SAMPLE_INTERVAL / 2;
    if (m_timerButton > BUTTON_SAMPLE_INTERVAL) m_timerButton = 0;
  }

  byte val;
  val = readButton(BUTTON_1_PIN, s_valButton_1);
  if (val) return 1 | val;
  val = readButton(BUTTON_2_PIN, s_valButton_2);
  if (val) return 2 | val;
  val = readButton(BUTTON_3_PIN, s_valButton_3);
  if (val) return 3 | val;
  
  return 0;
}


void Shield::beep(unsigned int on_period,
          unsigned int off_period,
          byte cycles,
          unsigned int loop_cycles,
          unsigned int loop_delay_period
         ) {
  cycles = cycles == 0 ? 1 : cycles;
  loop_cycles = loop_cycles == 0 ? 1 : loop_cycles;

  if (on_period == 0) {
    digitalWrite(BEEPER_PIN, OFF);
    return;
  }

  m_timerBeeperOnPeriod = 0;
  m_timerBeeperOffPeriod = 0;
  m_timerBeeperLoopDelayPeriod = 0;

  unsigned long tm = millis();
  if (m_timerBeeperNext < tm) {
    m_timerBeeperNext = tm + m_timerBeeperTo;
    if (m_timerBeeperOnPeriod > on_period) m_timerBeeperOnPeriod = 0;
    if (m_timerBeeperOffPeriod > off_period) m_timerBeeperOffPeriod = 0;
    if (m_timerBeeperLoopDelayPeriod > loop_delay_period) m_timerBeeperLoopDelayPeriod = 0;

    for (int i = 0; i < cycles; ++i) {
      for (int j = 0; j < loop_cycles; ++j) {
        digitalWrite(BEEPER_PIN, LOW);
        delay(on_period);
        digitalWrite(BEEPER_PIN, HIGH);
        delay(off_period);
      }
      delay(loop_delay_period);
    }
  }

}

void Shield::blinkLeds(byte led, int millsec, bool turnoff_delay) {
  digitalWrite(led, LOW);
  delay(millsec);
  digitalWrite(led, HIGH);
  if (turnoff_delay) delay(millsec);
}

void Shield::blinkLeds(byte led1, byte led2, int millsec, bool turnoff_delay) {
  digitalWrite(led1, LOW);
  digitalWrite(led2, LOW);
  delay(millsec);
  digitalWrite(led1, HIGH);
  digitalWrite(led2, HIGH);
  if (turnoff_delay) {
    delay(millsec);
  }
}

void Shield::blinkLeds(byte led1, byte led2, byte led3, int millsec, bool turnoff_delay) {
  digitalWrite(led1, LOW);
  digitalWrite(led2, LOW);
  digitalWrite(led3, LOW);
  delay(millsec);
  digitalWrite(led1, HIGH);
  digitalWrite(led2, HIGH);
  digitalWrite(led3, HIGH);
  if (turnoff_delay) {
    delay(millsec);
  }
}

void Shield::blinkLeds(int millsec, bool turnoff_delay) {
  for (int i = 10; i < 14; ++i) {
    digitalWrite(i, LOW);
  }
  delay(millsec);
  for (int i = 10; i < 14; ++i) {
    digitalWrite(i, HIGH);
  }
  if (turnoff_delay)  delay(millsec);
}

void Shield::writeValue(byte seg, byte val) {
  digitalWrite(LATCH_DIO, LOW);
  shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, val);
  shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_NUM[seg]);
  digitalWrite(LATCH_DIO, HIGH);
}

void Shield::writeNumber(byte seg, byte val) {
  digitalWrite(LATCH_DIO, LOW);
  byte s = SEGMENT_DIGITS[val];
  if(seg == 1)
    s &= ~0x80;
  shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, s);
  shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_NUM[seg]);
  digitalWrite(LATCH_DIO, HIGH);
}

void Shield::writeDisplay(int val) {
  writeNumber(0, (val / 1000) % 10);
  writeNumber(1, (val / 100) % 10);
  writeNumber(2, (val / 10) % 10);
  writeNumber(3, val % 10);
  writeNumber(4, 0);
}
