//Kevin Rye - 2016
//Version 1.0
//compiled on Arduino 1.6.7
//This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
//http://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_US

//I2C backpack from Adafruit....
// Written by Tony DiCola for Adafruit Industries.
// Released under a MIT license: https://opensource.org/licenses/MIT
#include <Wire.h>
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"
#include "RTClib.h"
#include <Tone.h>

// I2C address of the display. (default address of 0x70)
#define DISPLAY_ADDRESS   0x70

// Create display and DS1307 objects.  These are global variables that
// can be accessed from both the setup and loop function below.
Adafruit_7segment clockDisplay = Adafruit_7segment();
RTC_DS1307 rtc = RTC_DS1307();

Tone beep;

//time
int hours = 0;
int minutes = 0;
int seconds = 0;
int adjHours;
int displayTime;

//I/O
const int hourButton = 2;
const int minButton = 3;
const int alarmButton = 4;
const int silenceButton = 5;

//for LEDs
unsigned long LEDtime = millis();
unsigned long colonTime = millis();
unsigned long flashTime = millis();

//alarm stuff
const int speaker = 6;
bool inAlarmMode = false;
bool startAlarm = false;
int alarmMinute = 0;
int alarmSecond = 0;
int displayAlarmTime;
bool flashLEDs = false;
bool alarmKilled = false;

//colon
bool blinkColon = false;

void setup() {

  //LEDs
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);

  //buttons
  pinMode(hourButton, INPUT);
  pinMode(minButton, INPUT);
  pinMode(alarmButton, INPUT);
  pinMode(silenceButton, INPUT);

  Serial.begin(57600);
  Serial.println("Clock starting!");

  // Setup the display.
  clockDisplay.begin(DISPLAY_ADDRESS);

  // Setup the RTC
  rtc.begin();

  // Set the RTC clock if it hasn't been set before.
  bool setClockTime = !rtc.isrunning();
  // Alternatively you can force the clock to be set again by
  // uncommenting this line:
  //setClockTime = true;
  if (setClockTime) {
    Serial.println("Setting DS3231 time!");
    // This line sets the DS1307 time to the exact date and time the
    // sketch was compiled:
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // Alternatively you can set the RTC with an explicit date & time,
    // for example to set January 21, 2014 at 3am you would uncomment:
    //rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  //audio
  beep.begin(speaker);

}

void loop() {

  get_time();

  //pass RTC values into a new variable -
  //this way one variable stores time in 24 hour mode and another that can be converted to 12 hour mode
  //time is monitored 'in the background' in 24 hour mode using the RTC, but is displayed in an adjusted 12-hr format
  adjHours = hours;

  //convert to 12 hr mode for display
  if (hours < 1) {
    adjHours = 12;
  }

  if ((hours > 12) && (hours < 24)) {
    adjHours = hours - 12;
  }

  if (false == inAlarmMode) {

    // Blink the colon by flipping its value every second
    if (millis() - colonTime > 1000) {
      colonTime = millis();
      blinkColon = !blinkColon;
    }

    // Show the time on the display by turning it into a numeric
    // value, like 3:30 turns into 330, by multiplying the hour by
    // 100 and then adding the minutes.
    displayTime = adjHours * 100 + minutes;

    // Now print the time value to the display.
    clockDisplay.print(displayTime, DEC);

    //handle blanking zeroes
    if (adjHours < 10) {

      clockDisplay.writeDigitNum(0, 0);

    }

    if (minutes < 10) {

      clockDisplay.writeDigitNum(3, 0);

    }

    clockDisplay.drawColon(blinkColon);

    // Now push out to the display the new values that were set above.
    clockDisplay.writeDisplay();

    // Now increase the seconds by one.
    seconds += 1;
    // If the seconds go above 59 then the minutes should increase and
    // the seconds should wrap back to 0.
    if (seconds > 59) {
      seconds = 0;
      minutes += 1;
      // Again if the minutes go above 59 then the hour should increase and
      // the minutes should wrap back to 0.
      if (minutes > 59) {
        minutes = 0;
        hours += 1;
        // Note that when the minutes are 0 (i.e. it's the top of a new hour)
        // then the start of the loop will read the actual time from the DS1307
        // again.  Just to be safe though we'll also increment the hour and wrap
        // back to 0 if it goes above 23 (i.e. past midnight).
        if (hours > 23) {
          hours = 0;
        }
      }
    }
  } else { //in alarm mode....display alrm time

    //alarm time
    //dispaly alarm time as set
    if (false == startAlarm) {

      //alarm time is not 0:00, so display it
      displayAlarmTime = alarmMinute * 100 + alarmSecond;

      // Now print the time value to the display.
      clockDisplay.print(displayAlarmTime, DEC);

      //handle blanking zeroes
      if (alarmSecond < 10) {

        clockDisplay.writeDigitNum(3, 0);

      }

      if (alarmMinute < 10) {

        clockDisplay.writeDigitNum(0, 0);

      }

      if (alarmMinute == 0) {

        clockDisplay.writeDigitNum(1, 0);

      }

      //display colon
      blinkColon = true; //do not flash the colon, setting this true keeps it on

      clockDisplay.drawColon(blinkColon);

      // Now push out to the display the new values that were set above.
      clockDisplay.writeDisplay();

    }

    //if timer is running, display the count down
    if (true == startAlarm) {

      //time is deremented by 1 second in he function below that scrolls through the LEDs

      //alarm time is not 0:00, so display it
      displayAlarmTime = alarmMinute * 100 + alarmSecond;

      // Now print the time value to the display.
      clockDisplay.print(displayAlarmTime, DEC);

      //handle blanking zeroes
      if (alarmSecond < 10) {

        clockDisplay.writeDigitNum(3, 0);

      }

      if (alarmMinute < 10) {

        clockDisplay.writeDigitNum(0, 0);

      }

      if (alarmMinute == 0) {

        clockDisplay.writeDigitNum(1, 0);

      }

      //display colon
      blinkColon = true; //do not flash the colon, setting this true keeps it on
      clockDisplay.drawColon(blinkColon);

      // Now push out to the display the new values that were set above.
      clockDisplay.writeDisplay();

      if ((alarmMinute == 0) && (alarmSecond == 0)) {
        clockDisplay.writeDigitNum(0, 0);
        clockDisplay.writeDigitNum(1, 0);
        clockDisplay.writeDigitNum(2, 0);
        clockDisplay.writeDigitNum(3, 0);
        //activate alarm tone
      }
    }
  }

  //handle buttons
  //hours button pressed
  if ((digitalRead(hourButton) == LOW) && (false == inAlarmMode)) { //not in alarm mode

    //debaounce
    delay(150);

    hours++;
    if (hours > 23) hours = 0; // loop back
    set_time();
  }

  if ((digitalRead(hourButton) == LOW) && (true == inAlarmMode) && (false == startAlarm) && (false == flashLEDs) && (false == alarmKilled)) { //in alarm mode and alarm not running or has ended

    //debaounce
    delay(150);

    alarmMinute++;
    if (alarmMinute > 59) alarmMinute = 0; // loop back
  }

  //minutes button pressed
  if ((digitalRead(minButton) == LOW) && (false == inAlarmMode)) { //not in alarm mode

    //debaounce
    delay(150);

    minutes++;
    if (minutes > 59) minutes = 0; // loop back
    seconds = 0;
    set_time();
  }

  if ((digitalRead(minButton) == LOW) && (true == inAlarmMode) && (false == startAlarm) && (false == flashLEDs) && (false == alarmKilled)) { //in alarm mode and alarm not running or has ended

    //debaounce
    delay(150);

    alarmSecond++;
    if (alarmSecond > 59) alarmSecond = 0; // loop back

  }

  //alarm button pressed
  if ((digitalRead(alarmButton) == LOW) && (false == inAlarmMode)) { //not in alarm mode and alarm not running, or ended

    //debaounce
    delay(300); //longer delay so an accidental double click doesn't start the timer on entry (it'll count up for some reason)

    inAlarmMode = true;

  }

  if ((digitalRead(alarmButton) == LOW) && (true == inAlarmMode) && (true == alarmKilled)) { // in alarm mode and alarm killed

    //debaounce
    delay(300); //longer delay so an accidental double click doesn't put you back into alarm mode

    //go back to time mode
    inAlarmMode = false;

    //clean up alarm
    alarmKilled = false;

    //turn off any LEDs that may still be on.
    PORTD &= ~_BV(PD7); //pin 7 LOW
    PORTB &= ~_BV(PB0); //pin 8 LOW
    PORTB &= ~_BV(PB1); //pin 9 LOW
    PORTB &= ~_BV(PB2); //pin 10 LOW

    //reset alrarm time
    alarmMinute = 0;
    alarmSecond = 0;

  }

  if ((digitalRead(alarmButton) == LOW) && (true == inAlarmMode) && (false == startAlarm) && (false == flashLEDs) && ((alarmMinute != 0) || (alarmSecond !=0))) { //in alarm mode and alarm is not running or has ended

    //debaounce
    delay(150);

    //in alarm mode and alarm pressed, start the countdown

    startAlarm = true;

  }

  //silence button pressed
  if ((digitalRead(silenceButton) == LOW) && (true == inAlarmMode)) {

    //debaounce
    delay(300);

    startAlarm = false;  //ideally, this should stop the timer and freeze the display, not take you out of alarm mode
    alarmKilled = true; //restores functionality of the alarm button
  }

  if ((digitalRead(silenceButton) == LOW) && (true == flashLEDs)) {

    //debaounce
    delay(500);

    flashLEDs = false;

    //kill LEDs
    PORTD &= ~_BV(PD7); //pin 7 LOW
    PORTB &= ~_BV(PB0); //pin 8 LOW
    PORTB &= ~_BV(PB1); //pin 9 LOW
    PORTB &= ~_BV(PB2); //pin 10 LOW
  }

  if (true == startAlarm) {

    //don't blink the colon in alarm mode
    // blinkColon = false;

    //scroll through LEDs (use direct port manipulation since digitalWrite is too slow and ghosting is seen on the 'off' LEDs)
    //LED 1 on pin 7
    if (millis() - LEDtime < 250) {

      PORTD |= _BV(PD7); //pin 7 HIGH
      PORTB &= ~_BV(PB0); //pin 8 LOW
      PORTB &= ~_BV(PB1); //pin 9 LOW
      PORTB &= ~_BV(PB2); //pin 10 LOW

    }

    //LED 2 on pin 8
    if (millis() - LEDtime > 250) {

      PORTD &= ~_BV(PD7); //pin 7 LOW
      PORTB |= _BV(PB0); //pin 8 HIGH
      PORTB &= ~_BV(PB1); //pin 9 LOW
      PORTB &= ~_BV(PB2); //pin 10 LOW

    }

    //LED 3 on pin 9
    if (millis() - LEDtime > 500) {

      PORTD &= ~_BV(PD7); //pin 7 LOW
      PORTB &= ~_BV(PB0); //pin 8 LOW
      PORTB |= _BV(PB1); //pin 9 HIGH
      PORTB &= ~_BV(PB2); //pin 10 LOW

    }

    //LED 4 on pin 10
    if (millis() - LEDtime > 750) {

      PORTD &= ~_BV(PD7); //pin 7 LOW
      PORTB &= ~_BV(PB0); //pin 8 LOW
      PORTB &= ~_BV(PB1); //pin 9 LOW
      PORTB |= _BV(PB2); //pin 10 HIGH

    }

    if (millis() - LEDtime > 1000) {
      LEDtime = millis();

      // otherwise, decrease the seconds by one.
      alarmSecond -= 1;
      // wrap back to 0.
      if ((alarmSecond < 0) && (alarmMinute > 0))  {
        alarmSecond = 59;
        alarmMinute -= 1;
      }
    }

    if ((alarmMinute == 0) && (alarmSecond == 0)) {

      startAlarm = false;
      flashLEDs = true;
      alarmMinute = 0;
      alarmSecond = 0;
    }
  }

  //flash  LEDs
  if (true == flashLEDs) {

    if (millis() - flashTime < 400) {

      //set all LEDs high
      PORTD |= _BV(PD7); //pin 7 HIGH
      PORTB |= _BV(PB0); //pin 8 HIGH
      PORTB |= _BV(PB1); //pin 9 HIGH
      PORTB |= _BV(PB2); //pin 10 HIGH

      //beep
      beep.play(1000, 200);
    }

    //set LEDs low
    if (millis() - flashTime > 400) {

      PORTD &= ~_BV(PD7); //pin 7 LOW
      PORTB &= ~_BV(PB0); //pin 8 LOW
      PORTB &= ~_BV(PB1); //pin 9 LOW
      PORTB &= ~_BV(PB2); //pin 10 LOW
    }

    if (millis() - flashTime > 800) {
      flashTime = millis();
    }
  }

} //end of loop



void set_time() {
  Wire.beginTransmission(104);
  Wire.write(0);
  Wire.write(decToBcd(seconds));
  Wire.write(decToBcd(minutes));
  Wire.write(decToBcd(hours));
  Wire.endTransmission();
}

void get_time() {
  Wire.beginTransmission(104);
  Wire.write(0);//set register to 0
  Wire.endTransmission();
  Wire.requestFrom(104, 3);//get 3 bytes (seconds,minutes,hours);
  seconds = bcdToDec(Wire.read() & 0x7f);
  minutes = bcdToDec(Wire.read());
  hours = bcdToDec(Wire.read() & 0x3f);
}

byte decToBcd(byte val) {
  return ((val / 10 * 16) + (val % 10));
}

byte bcdToDec(byte val) {
  return ((val / 16 * 10) + (val % 16));
}
