#include <Wire.h>
#include <TimeLib.h>

class DS1307RTC
  { public:
      bool          chipPresent() { return exists; }
      time_t        get();
      char          getCalibration();
      unsigned char isRunning();
      bool          read(tmElements_t &tm);
      bool          set(time_t t);
      void          setCalibration(char calValue);
      void          setup();
      bool          write(tmElements_t &tm);

    private:
      bool exists;
      uint8_t dec2bcd(uint8_t num);
      uint8_t bcd2dec(uint8_t num);
  };  // DS1307RTC

const int DS1307_CTRL_ID = 0x68;

uint8_t DS1307RTC::bcd2dec(uint8_t num)
  { // Convert Binary Coded Decimal (BCD) to Decimal
    return ((num/16 * 10) + (num % 16));
  } //  DS1307RTC::bcd2dec

uint8_t DS1307RTC::dec2bcd(uint8_t num)
  { // Convert Decimal to Binary Coded Decimal (BCD)
    return ((num/10 * 16) + (num % 10));
  } // DS1307RTC::dec2bcd

time_t DS1307RTC::get()
  { // Aquire data from buffer and convert to time_t
    tmElements_t tm;
    if (not read(tm))
      return 0;
    return(makeTime(tm));
  } // DS1307RTC::get()

char DS1307RTC::getCalibration()
  {
    Wire1.beginTransmission(DS1307_CTRL_ID);
    #if ARDUINO >= 100
      Wire1.write((uint8_t)0x07);
    #else
      Wire.send(0x07);
    #endif
    Wire1.endTransmission();

    Wire1.requestFrom(DS1307_CTRL_ID, 1);
    #if ARDUINO >= 100
      unsigned char calReg = Wire1.read();
    #else
      unsigned char calReg = Wire1.receive();
    #endif
    char out = calReg & 0x1f;
    if (!(calReg & 0x20))
      { out = -out; } // S bit clear means a negative value
    return out;
  } // DS1307RTC::getCalibration

unsigned char DS1307RTC::isRunning()
  { Wire1.beginTransmission(DS1307_CTRL_ID);
    #if ARDUINO >= 100
      Wire1.write((uint8_t)0x00);
    #else
      Wire1.send(0x00);
    #endif
    Wire1.endTransmission();

    // Just fetch the seconds register and check the top bit
    Wire1.requestFrom(DS1307_CTRL_ID, 1);
    #if ARDUINO >= 100
      return !(Wire.read() & 0x80);
    #else
      return !(Wire.receive() & 0x80);
    #endif
  } // DS1307RTC::isRunning()

bool DS1307RTC::read(tmElements_t &tm)
  { // Aquire data from the RTC chip in BCD format
    uint8_t sec;
    bool Result = false;

    Wire1.beginTransmission(DS1307_CTRL_ID);
    #if ARDUINO >= 100
      Wire1.write((uint8_t)0x00);
    #else
      Wire1.send(0x00);
    #endif

    exists = (Wire1.endTransmission() == 0);
    if (exists)
      { // exists
        // request the 7 data fields   (secs, min, hr, dow, date, mth, yr)
        Wire1.requestFrom(DS1307_CTRL_ID, tmNbrFields);
        if (Wire1.available() >= tmNbrFields)
          { // got data
            #if ARDUINO >= 100
              sec       = Wire1.read();
              tm.Second = bcd2dec(sec & 0x7f);
              tm.Minute = bcd2dec(Wire1.read() );
              tm.Hour   = bcd2dec(Wire1.read() & 0x3f);  // mask assumes 24hr clock
              tm.Wday   = bcd2dec(Wire1.read() );
              tm.Day    = bcd2dec(Wire1.read() );
              tm.Month  = bcd2dec(Wire1.read() );
              tm.Year   = y2kYearToTm((bcd2dec(Wire1.read())));
            #else
              sec       = Wire1.receive();
              tm.Second = bcd2dec(sec & 0x7f);
              tm.Minute = bcd2dec(Wire1.receive() );
              tm.Hour   = bcd2dec(Wire1.receive() & 0x3f);  // mask assumes 24hr clock
              tm.Wday   = bcd2dec(Wire1.receive() );
              tm.Day    = bcd2dec(Wire1.receive() );
              tm.Month  = bcd2dec(Wire1.receive() );
              tm.Year   = y2kYearToTm((bcd2dec(Wire1.receive())));
            #endif
            if ((sec & 0x80) == 0)
              { Result = true; }
          } // got data
      } // exists

    return Result;
  } // DS1307RTC::read

bool DS1307RTC::set(time_t t)
  { tmElements_t tm;
    breakTime(t, tm);
    return write(tm);
  } // DS1307RTC::set

void DS1307RTC::setCalibration(char calValue)
  { unsigned char calReg = abs(calValue) & 0x1f;
    if (calValue >= 0)
      { calReg |= 0x20; } // S bit is positive to speed up the clock
    Wire1.beginTransmission(DS1307_CTRL_ID);
    #if ARDUINO >= 100
      Wire1.write((uint8_t)0x07); // Point to calibration register
      Wire1.write(calReg);
    #else
      Wire1.send(0x07); // Point to calibration register
      Wire1.send(calReg);
    #endif
    Wire1.endTransmission();
  } // DS1307RTC::setCalibration

void DS1307RTC::setup()
  {  exists = false;
     Wire1.begin();
  } // DS1307RTC::setup

bool DS1307RTC::write(tmElements_t &tm)
  { // To eliminate any potential race conditions,
    // stop the clock before writing the values,
    // then restart it after.
    bool Result = false;

    Wire1.beginTransmission(DS1307_CTRL_ID);
    #if ARDUINO >= 100
      Wire1.write((uint8_t)0x00); // reset register pointer
      Wire1.write((uint8_t)0x80); // Stop the clock. The seconds will be written last
      Wire1.write(dec2bcd(tm.Minute));
      Wire1.write(dec2bcd(tm.Hour));      // sets 24 hour format
      Wire1.write(dec2bcd(tm.Wday));
      Wire1.write(dec2bcd(tm.Day));
      Wire1.write(dec2bcd(tm.Month));
      Wire1.write(dec2bcd(tmYearToY2k(tm.Year)));
    #else
      Wire1.send(0x00); // reset register pointer
      Wire1.send(0x80); // Stop the clock. The seconds will be written last
      Wire1.send(dec2bcd(tm.Minute));
      Wire1.send(dec2bcd(tm.Hour));      // sets 24 hour format
      Wire1.send(dec2bcd(tm.Wday));
      Wire1.send(dec2bcd(tm.Day));
      Wire1.send(dec2bcd(tm.Month));
      Wire1.send(dec2bcd(tmYearToY2k(tm.Year)));
    #endif

    exists = (Wire1.endTransmission() == 0);
    if (exists)
      { // Now go back and set the seconds, starting the clock back up as a side effect
        Wire1.beginTransmission(DS1307_CTRL_ID);
        #if ARDUINO >= 100
          Wire1.write((uint8_t)0x00); // reset register pointer
          Wire1.write(dec2bcd(tm.Second)); // write the seconds, with the stop bit clear to restart
        #else
          Wire1.send(0x00); // reset register pointer
          Wire1.send(dec2bcd(tm.Second)); // write the seconds, with the stop bit clear to restart
        #endif
        if (Wire1.endTransmission() == 0)
          { Result = true; }
        else
          { exists = false; }
      } // exists

    return Result;
  } // DS1307RTC::write

