//compiled on Arduino IDE 1.6.10
//Kevin Rye, 2016
//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

#include <Wire.h>
#include <Sodaq_DS3231.h> //for DateTime dt

//shift register interface
const int latchPin = 7; //pin connected to pin 12 of 74HC595
const int clockPin = 6; //pin connected to pin 11 of 74HC595
const int dataPin = 5; //pin connected to DSpin 14 of 74HC595

const int invpwr = 8; //power to inverter for decimal

//RTC stuff
int seconds = 0;
int minutes = 0;
int hours = 0;
int adjHours = 0;
int dayOfWeek; //not used (but needed for DS3231 function), DOW is calculated and pushed to 'returnedDOWvar'
int dayOfMonth = 1;
int month = 1;
int year = 16;

//year, month, date, hour, min, sec and week-day(starts from 0 and goes to 6)
//writing any non-existent time-data may interfere with normal operation of the RTC.
//Take care of week-day also.
DateTime dt(2016, 6, 21, 18, 37, 0, 2);

//date calc stuff
#define CalendarYrToTm(Y) ((Y) - 1970)
#define LEAP_YEAR(Y) (((1970+Y)>0) && !((1970+Y)%4) && (((1970+Y)%100) || !((1970+Y)%400)))
static  const uint8_t monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // API starts months from 1, this array starts from 0

//decimals
const int dp1 = 9; //decimal
const int dp2 = 8; //am/pm
const int lowA3 = A3;

//i/o
const int modeButton = 10;
const int hourMonthButton = 2;
const int minDayButton = 3;
const int yearButton = 4;
bool displayDate = false;

//for data out
byte DataOut1 = B11111111;
byte DataOut2 = B11111111;
byte DataOut3 = B11111111;

int hourdigit1 = 0; //xN:xx:xx
int hourdigit2 = 0; //Nx:xx:xx
int mindigit1 = 0; //xx:xN:xx
int mindigit2 = 0; //xx:Nx:xx
int secdigit1 = 0; //xx:xx:xN
int secdigit2 = 0; //xx:xx:Nx

void setup () {

  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(invpwr, OUTPUT);

  pinMode(dp1, OUTPUT);
  pinMode(dp2, OUTPUT);
  pinMode(lowA3, OUTPUT);
  digitalWrite(lowA3, LOW);

  pinMode(modeButton, INPUT);
  pinMode(hourMonthButton, INPUT);
  pinMode(minDayButton, INPUT);
  pinMode(yearButton, INPUT);

  digitalWrite(dp1, HIGH); //decimals HIGH = ON, LOW = OFF
  digitalWrite(dp2, HIGH); //am/pm

  Serial.begin(57600);
  Wire.begin();
  // rtc.setDateTime(dt); //Adjust date-time as defined 'dt' above

} //end setup

void loop () {

  if (false == displayDate) {
    //get data from DS3231
    get_time();

    adjHours = hours;

    if (hours > 23) {
      hours = 0;
    }

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

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

    //pm indicator
     if (hours > 11) {
      digitalWrite(invpwr, HIGH);

    } else {
      digitalWrite(invpwr, LOW);
    }

    //end of display time mode
  }

  //display date
  else {
    //get data from DS3231
    get_date();

    //turn off PM indicator
    digitalWrite(invpwr, LOW);

    //end of display date mode
  }


  //now display time or date based on current mode
  DispTimeOrDate();

  //handle button presses
  //mode
  if (digitalRead(modeButton) == LOW) {

    delay(200);
   
    displayDate = !displayDate; //toggle dispaly mode
  }

  //hourMonth
  if (digitalRead(hourMonthButton) == LOW) {

    delay(200);

    if (false == displayDate) {
      hours++; // add hour
      if (hours > 23) hours = 0; // loop back
      set_time();

    } else { //in date mode
      month++; // add month
      if (month > 12) month = 1; // loop back
      set_date();
    }
  }

  //minDayButton
  if (digitalRead(minDayButton) == LOW) {

    delay(200);

    if (false == displayDate) {
      minutes++; // add minute
      if (minutes > 59) minutes = 0; // loop back
      seconds = 0;
      set_time();

    } else { //in date mode
      dayOfMonth++; // add day
        if (dayOfMonth > getMonthDays(CalendarYrToTm(year), month)) dayOfMonth = 1; // loop back
        set_date();
    }
  }

  //yearButton
  if (digitalRead(yearButton) == LOW) {

    delay(200);

    if (false == displayDate) {
      seconds = 0;
      set_time();
    } else { //in date mode
      year++; // add year
      if (year > 99) year = 16; // loop back
      set_date();
    }
  }

} //end loop




// Displays the time
void DispTimeOrDate() {

  //shift out time
  if (false == displayDate) {
    hourdigit1 = (adjHours) / 10;
    hourdigit2 = (adjHours) % 10;

    mindigit1 = (minutes) / 10;
    mindigit2 = (minutes) % 10;

    secdigit1 = (seconds) / 10;
    secdigit2 = (seconds) % 10;

    int BinNum2 = B1010; //invalid number

    if (adjHours > 9) {
      SetDigit(1, hourdigit1); //will display "1" for 10, 11, 12
      SetDigit(2, hourdigit2);
      SetDigit(3, mindigit1);
      SetDigit(4, mindigit2);
      SetDigit(5, secdigit1);
      SetDigit(6, secdigit2);

    }    else {
      SetDigit(1, BinNum2); //send invalid number to blank to digit for hours 1-9
      SetDigit(2, hourdigit2);
      SetDigit(3, mindigit1);
      SetDigit(4, mindigit2);
      SetDigit(5, secdigit1);
      SetDigit(6, secdigit2);
    }
  } //end shift out time

  //shift out date
  else {
    hourdigit1 = (month) / 10;
    hourdigit2 = (month) % 10;

    mindigit1 = (dayOfMonth) / 10;
    mindigit2 = (dayOfMonth) % 10;

    secdigit1 = (year) / 10;
    secdigit2 = (year) % 10;

    SetDigit(1, hourdigit1);
    SetDigit(2, hourdigit2);
    SetDigit(3, mindigit1);
    SetDigit(4, mindigit2);
    SetDigit(5, secdigit1);
    SetDigit(6, secdigit2);

  }

  shiftOut();
}

// Sets a digit of the display. Code from Nixie-Bot by Scott-Bot.com, 2013
void SetDigit(int Digit, int Number) {

  byte BinNum;

  if ( Number == 0) {
    //The number in binary + 1 (don't know why it's +1, but it is)
    BinNum = B0000;
  }
  else if ( Number == 1) {
    //The number in binarry + 1
    BinNum = B0001;
  }
  else if ( Number == 2) {
    //The number in binary + 1
    BinNum = B0010;
  }
  else if ( Number == 3) {
    //The number in binary + 1
    BinNum = B0011;
  }
  else if ( Number == 4) {
    //The number in binary + 1
    BinNum = B0100;
  }
  else if ( Number == 5) {
    //The number in binary + 1
    BinNum = B0101;
  }
  else if ( Number == 6) {
    //The number in binary + 1
    BinNum = B0110;
  }
  else if ( Number == 7) {
    //The number in binary + 1
    BinNum = B0111;
  }
  else if ( Number == 8) {
    //The number in binary + 1
    BinNum = B1000;
  }
  else if ( Number == 9) {
    //The number in binary + 1
    BinNum = B1001;
  }
  else {
    //invalid number (blank display)
    BinNum = B1010;
  }

  if ( Digit == 1) {
    DataOut1 &= B00001111;
    DataOut1 |= (BinNum << 4);
  }
  else if ( Digit == 2) {
    DataOut1 &= B11110000;
    DataOut1 |= BinNum;
  }
  else if ( Digit == 3) {
    DataOut2 &= B00001111;
    DataOut2 |= (BinNum << 4);
  }
  else if ( Digit == 4) {
    DataOut2 &= B11110000;
    DataOut2 |= BinNum;
  }
  else if ( Digit == 5) {
    DataOut3 &= B00001111;
    DataOut3 |= (BinNum << 4);
  }
  else if ( Digit == 6) {
    DataOut3 &= B11110000;
    DataOut3 |= BinNum;
  }
}

// Shifts out the data to the shift registers. Code from Nixie-Bot by Scott-Bot.com, 2013
void shiftOut() {
  // This shifts 8 bits out MSB first,
  //on the rising edge of the clock,
  //clock idles low

  //internal function setup
  int i = 0;
  int pinState;

  //clear everything out just in case to
  //prepare shift register for bit shifting
  //ground latchPin and hold low for as long as you are transmitting
  digitalWrite(latchPin, 0);
  digitalWrite(dataPin, 0);
  digitalWrite(clockPin, 0);

  //for each bit in the byte myDataOut
  //NOTICE THAT WE ARE COUNTING DOWN in our for loop
  //This means that %00000001 or "1" will go through such
  //that it will be pin Q0 that lights.
  for (i = 7; i >= 0; i--)  {
    digitalWrite(clockPin, 0);

    //if the value passed to myDataOut and a bitmask result
    // true then... so if we are at i=6 and our value is
    // %11010100 it would the code compares it to %01000000
    // and proceeds to set pinState to 1.
    if ( DataOut1 & (1 << i) ) {
      pinState = 1;
    }
    else {
      pinState = 0;
    }

    //Sets the pin to HIGH or LOW depending on pinState
    digitalWrite(dataPin, pinState);
    //register shifts bits on upstroke of clock pin
    digitalWrite(clockPin, 1);
    //zero the data pin after shift to prevent bleed through
    digitalWrite(dataPin, 0);
  }

  //stop shifting
  digitalWrite(clockPin, 0);

  // Shift the second word
  for (i = 7; i >= 0; i--)  {
    digitalWrite(clockPin, 0);

    if ( DataOut2 & (1 << i) ) {
      pinState = 1;
    }
    else {
      pinState = 0;
    }

    digitalWrite(dataPin, pinState);
    digitalWrite(clockPin, 1);
    digitalWrite(dataPin, 0);
  }

  digitalWrite(clockPin, 0);

  // Shift the third word
  for (i = 7; i >= 0; i--)  {
    digitalWrite(clockPin, 0);

    if ( DataOut3 & (1 << i) ) {
      pinState = 1;
    }
    else {
      pinState = 0;
    }

    digitalWrite(dataPin, pinState);
    digitalWrite(clockPin, 1);
    digitalWrite(dataPin, 0);
  }

  digitalWrite(clockPin, 0);
  digitalWrite(latchPin, 1);
}

//DS3231 stuff
void set_date() {
  Wire.beginTransmission(104);
  Wire.write(3);//set register to 3 (day)
  Wire.write(decToBcd(dayOfWeek)); //this is actually useless. Could be anything really. The day of week is being calculated above based on the date, not what's stored on the DS3231
  Wire.write(decToBcd(dayOfMonth));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));
  Wire.endTransmission();
}

void get_date() {
  Wire.beginTransmission(104);
  Wire.write(3);//set register to 3 (day)
  Wire.endTransmission();
  Wire.requestFrom(104, 4); //get 5 bytes(day,date,month,year,control);
  dayOfWeek   = bcdToDec(Wire.read()); //this is actually useless. Could be anything really. The day of week is being calculated above based on the date, not what's stored on the DS3231
  dayOfMonth  = bcdToDec(Wire.read());
  month = bcdToDec(Wire.read());
  year  = bcdToDec(Wire.read());

  if (year < 16) {
    year = 16;
  }
}

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));
}


//calculate how many days are in the displayed month
uint8_t getMonthDays(uint8_t y, uint8_t m) {
  return ((m == 2) && LEAP_YEAR(y)) ? 29 : monthDays[m - 1];
}

//calculate the day of week based on the given date
byte calcDayOfWeek(unsigned int y, byte m, byte d)
{
  // cast out multiples of 400 from y
  y -= (2000 * (y >> 11));
  while (y >= 400) y -= 400;

  boolean leap = ((y & 3) == 0);
  if ((y == 100) || (y == 200) || (y == 300)) leap = false;

  if (d > 31 || d == 0) return 0;

  byte w = 6;
  while (y >= 100) {
    y -= 100;
    w -= 2;
  }

  w += (y + (y >> 2));

  // correction for Jan. and Feb. of leap year
  if (leap && (m <= 2)) w--;

  // using subtraction iso addition makes the while at end possible 1 iteration faster.
  switch (m)
  {
    case 1:
      w++;
      break;
    case 2:
      if (d > (leap ? 29 : 28)) return 0;
      w += 4;
      break;
    case 3:
      w += 4;
      break;
    case 5:
      w += 2;
      break;
    case 7:
      break;
    case 8:
      w += 3;
      break;
    case 10:
      w++;
      break;
    case 12:
      w += 6;
      break;
    default:
      if (d > 30) return 0;
      switch (m)
      {
        case 4:
          break;
        case 6:
          w += 5;
          break;
        case 9:
          w += 6;
          break;
        case 11:
          w += 4;
          break;
        default:
          return 0;
      }
  }

  w += d;

  // there are only 7 days in a week, so we "cast out" sevens
  while (w > 7) w = (w >> 3) + (w & 7);
  return w;
}

