#include "WProgram.h"
#include "DS1302.h"


/*** Time definitions ***/

Time::Time(uint16_t yr, uint8_t mon, uint8_t date,
           uint8_t hr, uint8_t min, uint8_t sec,
           uint8_t day)
{
  this->yr   = yr;
  this->mon  = mon;
  this->date = date;
  this->hr   = hr;
  this->min  = min;
  this->sec  = sec;
  this->day  = day;
}


Time::Time()
{
  Time(2000, 1, 1, 0, 0, 0, 7);
}


/*** DS1302 definitions ***/

DS1302::DS1302(uint8_t ce_pin, uint8_t io_pin, uint8_t sclk_pin)
{
  _ce_pin = ce_pin;
  _io_pin = io_pin;
  _sclk_pin = sclk_pin;

  pinMode(ce_pin, OUTPUT);
  pinMode(sclk_pin, OUTPUT);
}


void DS1302::_write_out(uint8_t value)
{
  pinMode(_io_pin, OUTPUT);
  shiftOut(_io_pin, _sclk_pin, LSBFIRST, value);
}


uint8_t DS1302::_read_in()
{
  uint8_t input_value = 0;
  uint8_t bit = 0;
  pinMode(_io_pin, INPUT);

  for (int i = 0; i < 8; ++i) {
    bit = digitalRead(_io_pin);
    input_value |= (bit << i);

    digitalWrite(_sclk_pin, HIGH);
    delayMicroseconds(1);
    digitalWrite(_sclk_pin, LOW);
  }

  return input_value;
}


uint8_t DS1302::_register_bcd_to_dec(reg_t reg, uint8_t high_bit)
{
  uint8_t val = read_register(reg);
  uint8_t mask = (1 << (high_bit + 1)) - 1;
  val &= mask;
  val = (val & 15) + 10 * ((val & (15 << 4)) >> 4);
  return val;
}


uint8_t DS1302::_register_bcd_to_dec(reg_t reg)
{
  return _register_bcd_to_dec(reg, 7);
}


void DS1302::_register_dec_to_bcd(reg_t reg, uint8_t value, uint8_t high_bit)
{
  uint8_t regv = read_register(reg);
  uint8_t mask = (1 << (high_bit + 1)) - 1;

  /* convert value to bcd in place */
  uint8_t tvalue = value / 10;
  value = value % 10;
  value |= (tvalue << 4);

  /* replace high bits of value if needed */
  value &= mask;
  value |= (regv &= ~mask);

  write_register(reg, value);
}


void DS1302::_register_dec_to_bcd(reg_t reg, uint8_t value)
{
  _register_dec_to_bcd(reg, value, 7);
}


uint8_t DS1302::read_register(reg_t reg)
{
  uint8_t cmd_byte = 129;  /* 1000 0001 */
  uint8_t reg_value;
  cmd_byte |= (reg << 1);

  digitalWrite(_sclk_pin, LOW);
  digitalWrite(_ce_pin, HIGH);

  _write_out(cmd_byte);
  reg_value = _read_in();

  digitalWrite(_ce_pin, LOW);

  return reg_value;
}


void DS1302::write_register(reg_t reg, uint8_t value)
{
  uint8_t cmd_byte = (128 | (reg << 1));

  digitalWrite(_sclk_pin, LOW);
  digitalWrite(_ce_pin, HIGH);

  _write_out(cmd_byte);
  _write_out(value);

  digitalWrite(_ce_pin, LOW);
}


void DS1302::write_protect(bool enable)
{
  write_register(WP_REG, (enable << 7));
}


void DS1302::halt(bool enable)
{
  uint8_t sec = read_register(SEC_REG);
  sec &= ~(1 << 7);
  sec |= (enable << 7);
  write_register(SEC_REG, sec);
}


/*** Get time ***/

uint8_t DS1302::seconds()
{
  return _register_bcd_to_dec(SEC_REG, 6);
}


uint8_t DS1302::minutes()
{
  return _register_bcd_to_dec(MIN_REG);
}


uint8_t DS1302::hour()
{
  uint8_t hr = read_register(HR_REG);
  uint8_t adj;
  if (hr & 128)  /* 12-hour mode */
    adj = 12 * ((hr & 32) >> 5);
  else           /* 24-hour mode */
    adj = 10 * ((hr & (32 + 16)) >> 4);
  hr = (hr & 15) + adj;
  return hr;
}


uint8_t DS1302::date()
{
  return _register_bcd_to_dec(DATE_REG, 5);
}


uint8_t DS1302::month()
{
  return _register_bcd_to_dec(MON_REG, 4);
}


uint8_t DS1302::day()
{
  return _register_bcd_to_dec(DAY_REG, 2);
}


uint16_t DS1302::year()
{
  return 2000 + _register_bcd_to_dec(YR_REG);
}


Time DS1302::time()
{
  Time t;
  t.sec  = seconds();
  t.min  = minutes();
  t.hr   = hour();
  t.date = date();
  t.mon  = month();
  t.day  = day();
  t.yr   = year();
  return t;
}


/*** Set time ***/

void DS1302::seconds(uint8_t sec)
{
  _register_dec_to_bcd(SEC_REG, sec, 6);
}


void DS1302::minutes(uint8_t min)
{
  _register_dec_to_bcd(MIN_REG, min, 6);
}


void DS1302::hour(uint8_t hr)
{
  write_register(HR_REG, 0);  /* set 24-hour mode */
  _register_dec_to_bcd(HR_REG, hr, 5);
}


void DS1302::date(uint8_t date)
{
  _register_dec_to_bcd(DATE_REG, date, 5);
}


void DS1302::month(uint8_t mon)
{
  _register_dec_to_bcd(MON_REG, mon, 4);
}


void DS1302::day(uint8_t day)
{
  _register_dec_to_bcd(DAY_REG, day, 2);
}


void DS1302::year(uint16_t yr)
{
  yr -= 2000;
  _register_dec_to_bcd(YR_REG, yr);
}


void DS1302::time(Time t)
{
  seconds(t.sec);
  minutes(t.min);
  hour(t.hr);
  date(t.date);
  month(t.mon);
  day(t.day);
  year(t.yr);
}
