#include "pcf85063a.hpp"

#include <chrono>
#include <cstdio>

// binary coded decimal conversion helper functions
uint8_t bcd_encode(uint v) {
  uint v10 = v / 10, v1 = v - (v10 * 10); return v1 | (v10 << 4); }
int8_t bcd_decode(uint v) {
  uint v10 = (v >> 4) & 0x0f, v1 = v & 0x0f; return v1 + (v10 * 10); }

namespace pimoroni {

  void PCF85063A::init() {
    if(interrupt != PIN_UNUSED) {
      gpio_set_function(interrupt, GPIO_FUNC_SIO);
      gpio_set_dir(interrupt, GPIO_IN);
      gpio_set_pulls(interrupt, false, true);
    }
  }

  void PCF85063A::reset() {
    // magic soft reset command
    i2c->reg_write_uint8(address, Registers::CONTROL_1, 0x58);

    // read the oscillator status bit until it is cleared
    uint8_t status = 0x80;
    while(status & 0x80) {
      // attempt to clear oscillator stop flag, then read it back
      i2c->reg_write_uint8(address, Registers::OSCILLATOR_STATUS, 0x00);
      status = i2c->reg_read_uint8(address, Registers::OSCILLATOR_STATUS);
    }
  }

  // i2c helper methods
  i2c_inst_t* PCF85063A::get_i2c() const {
    return i2c->get_i2c();
  }

  int PCF85063A::get_address() const {
    return address;
  }

  int PCF85063A::get_sda() const {
    return i2c->get_sda();
  }

  int PCF85063A::get_scl() const {
    return i2c->get_scl();
  }

  int PCF85063A::get_int() const {
    return interrupt;
  }

  datetime_t PCF85063A::get_datetime() {
    static uint8_t result[7] = {0};

    i2c->read_bytes(address, Registers::SECONDS, result, 7);

    datetime_t dt = {
      .year   = (int16_t)(bcd_decode(result[6]) + 2000), // offset year
      .month  = ( int8_t) bcd_decode(result[5]),
      .day    = ( int8_t) bcd_decode(result[3]),
      .dotw   = ( int8_t) bcd_decode(result[4]),
      .hour   = ( int8_t) bcd_decode(result[2]),
      .min    = ( int8_t) bcd_decode(result[1]),
      .sec    = ( int8_t) bcd_decode(result[0] & 0x7f) // mask out status bit
    };

    return dt;
  }

  void PCF85063A::set_datetime(datetime_t *t) {
    static uint8_t data[7] = {
      bcd_encode((uint)t->sec),
      bcd_encode((uint)t->min),
      bcd_encode((uint)t->hour),
      bcd_encode((uint)t->day),
      bcd_encode((uint)t->dotw),
      bcd_encode((uint)t->month),
      bcd_encode((uint)t->year - 2000) // offset year
    };

    i2c->write_bytes(address, Registers::SECONDS, data, 7);
  }

  void PCF85063A::set_alarm(int second, int minute, int hour, int day) {
    uint8_t alarm[5] = {
      uint8_t(second != PARAM_UNUSED ? bcd_encode(second) : 0x80),
      uint8_t(minute != PARAM_UNUSED ? bcd_encode(minute) : 0x80),
      uint8_t(hour   != PARAM_UNUSED ? bcd_encode(  hour) : 0x80),
      uint8_t(day    != PARAM_UNUSED ? bcd_encode(   day) : 0x80),
      uint8_t(0x80)
    };

    i2c->write_bytes(address, Registers::SECOND_ALARM, alarm, 5);
  }

  void PCF85063A::set_weekday_alarm(
    int second, int minute, int hour, DayOfWeek dotw) {

    uint8_t alarm[5] = {
      uint8_t(second != PARAM_UNUSED ? bcd_encode(second) : 0x80),
      uint8_t(minute != PARAM_UNUSED ? bcd_encode(minute) : 0x80),
      uint8_t(hour   != PARAM_UNUSED ? bcd_encode(  hour) : 0x80),
      uint8_t(0x80),
      uint8_t(dotw   != DayOfWeek::NONE ? bcd_encode(  dotw) : 0x80)
    };

    i2c->write_bytes(address, Registers::SECOND_ALARM, alarm, 5);
  }

  void PCF85063A::enable_alarm_interrupt(bool enable) {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
    bits = enable ? (bits | 0x80) : (bits & ~0x80);
    bits |= 0x40; // ensure alarm flag isn't reset
    i2c->reg_write_uint8(address, Registers::CONTROL_2, bits);
  }

  bool PCF85063A::read_alarm_flag() {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
    return bits & 0x40;
  }

  void PCF85063A::clear_alarm_flag() {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
    bits &= ~0x40;
    i2c->reg_write_uint8(address, Registers::CONTROL_2, bits);
  }

  void PCF85063A::unset_alarm() {
    uint8_t dummy[5] = {0};
    i2c->write_bytes(address, Registers::SECOND_ALARM, dummy, 5);
  }

  void PCF85063A::set_timer(uint8_t ticks, TimerTickPeriod ttp) {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::TIMER_MODE);

    uint8_t timer[2] = {
      ticks,
      uint8_t((bits & ~0x18) | (ttp << 3) | 0x04) // mask out current ttp and set new + enable
    };

    i2c->write_bytes(address, Registers::TIMER_VALUE, timer, 2);
  }

  void PCF85063A::enable_timer_interrupt(bool enable, bool flag_only) {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::TIMER_MODE);
    bits = (bits & ~0x03) | (enable ? 0x02 : 0x00) | (flag_only ? 0x01 : 0x00);
    i2c->reg_write_uint8(address, Registers::TIMER_MODE, bits);
  }

  bool PCF85063A::read_timer_flag() {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
    return bits & 0x08;
  }

  void PCF85063A::clear_timer_flag() {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
    bits &= ~0x08;
    i2c->reg_write_uint8(address, Registers::CONTROL_2, bits);
  }

  void PCF85063A::unset_timer() {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::TIMER_MODE);
    bits &= ~0x04;
    i2c->reg_write_uint8(address, Registers::TIMER_MODE, bits);
  }

  // set the speed of (or turn off) the clock output
  void PCF85063A::set_clock_output(ClockOut co) {
    uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
    bits = (bits & ~0x07) | uint8_t(co);
    i2c->reg_write_uint8(
      address, Registers::CONTROL_2, bits);
  }

  void PCF85063A::set_byte(uint8_t v) {
    i2c->reg_write_uint8(address, Registers::RAM_BYTE, v);
  }

  uint8_t PCF85063A::get_byte() {
    return i2c->reg_read_uint8(address, Registers::RAM_BYTE);
  }

}
