//
// epaper clock on the humble ATmega328
//
#include <Wire.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/boot.h>
#include <avr/wdt.h>

#include "Roboto_Black_38.h"
#include "Roboto_Black_78.h"
#include <bb_epaper.h>
BBEPAPER bbep(EP295_128x296);
//BBEPAPER bbep(EP42B_400x300);
static int iRunCount = 0;
static int iSec, iMin, iHour, iWDay, iMDay, iMon;
int iSleepTime; // number of microseconds to sleep in each wakeup phase

// I2C base address of various RTCs
#define RTC_DS3231_ADDR 0x68
#define RTC_RV3032_ADDR 0x51
#define RTC_PCF8563_ADDR 0x51
#define RTC_ADDR RTC_DS3231_ADDR
#define RTC_START_REG 0
// Ugly solder job of Xiao ESP32-C6 and my e-paper adapter
//#define PIN_SDA 4
//#define PIN_SCL 3
//#define POWER_PIN 5
//#define PIN_DC 14
//#define PIN_BUSY 16
//#define PIN_RST 15
// Arduino Pro mini clone
#define PIN_DC 15
#define PIN_BUSY 16
#define PIN_RST 14
#define PIN_CS 10

const char *szDays[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
const char *szMonths[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
// AVR Power savings
// watchdog interrupt
ISR (WDT_vect) 
{        
   wdt_disable();  // disable watchdog
}  // end of WDT_vect
//
// Use the watchdog timer to sleep for a fixed time and then wake up
// 16ms delay shifted left by teh shift value
//
void WDSleepOneSec(void)
{
  ADCSRA = 0; // Disable ADC  
  // clear various "reset" flags
  MCUSR = 0;
  // allow changes, disable reset
  WDTCSR = bit (WDCE) | bit (WDE); // allow changes, disable reset
  /**
	 *	Setting the watchdog pre-scaler value with VCC = 5.0V and 16mHZ
	 *	WDP3 WDP2 WDP1 WDP0 | Number of WDT | Typical Time-out at Oscillator Cycles
	 *	0    0    0    0    |   2K cycles   | 16 ms
	 *	0    0    0    1    |   4K cycles   | 32 ms
	 *	0    0    1    0    |   8K cycles   | 64 ms
	 *	0    0    1    1    |  16K cycles   | 0.125 s
	 *	0    1    0    0    |  32K cycles   | 0.25 s
	 *	0    1    0    1    |  64K cycles   | 0.5 s
	 *	0    1    1    0    |  128K cycles  | 1.0 s
	 *	0    1    1    1    |  256K cycles  | 2.0 s
	 *	1    0    0    0    |  512K cycles  | 4.0 s
	 *	1    0    0    1    | 1024K cycles  | 8.0 s
	*/
  // For some reason, the AVR compiler is broken and these must be done as compile-time
  // constants. Experiments with building a mask from individual bits ended in failure
	WDTCSR  = (1<<WDIE) | (0<<WDP3) | (1<<WDP2) | (1<<WDP1) | (0<<WDP0);
	wdt_reset();  // pat the dog
	
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  noInterrupts ();           // timed sequence follows
  sleep_enable();

  // turn off brown-out enable in software
  MCUCR = bit (BODS) | bit (BODSE);
  MCUCR = bit (BODS);
  interrupts ();             // guarantees next instruction executed
  sleep_cpu ();

  // cancel sleep as a precaution
  sleep_disable();
  Serial.println("woke up!");
} /* WDSleepOneSec() */

int I2CWrite(unsigned char iAddr, unsigned char *pData, int iLen)
{
    Wire.beginTransmission(iAddr);
    Wire.write(pData, (unsigned char)iLen);
    Wire.endTransmission();
}
int I2CReadRegister(unsigned char iAddr, unsigned char u8Register, unsigned char *pData, int iLen)
{
  int rc;
  int i = 0;

  Wire.beginTransmission(iAddr);
  Wire.write(u8Register);
  Wire.endTransmission();
  Wire.requestFrom(iAddr, (unsigned char)iLen);
  while (i < iLen) {
     pData[i++] = Wire.read();
  }
}
//
// Minimal code to read the time and create
// a formatted ASCII string from the hh:mm:ss
//
void rtc_get_time()
{
uint8_t ucTemp[8];

      I2CReadRegister(RTC_ADDR, RTC_START_REG, ucTemp, 7);
      // Convert values from BCD
      iSec  = ((ucTemp[0] >> 4) * 10) + (ucTemp[0] & 0xf);
      iMin  = ((ucTemp[1] >> 4) * 10) + (ucTemp[1] & 0xf);
      // hours are stored in 24-hour format in the tm struct
      if (ucTemp[2] & 64) { // 12 hour format
          iHour = ucTemp[2] & 0xf;
          iHour += ((ucTemp[2] >> 4) & 1) * 10;
          iHour += ((ucTemp[2] >> 5) & 1) * 12; // AM/PM
      } else { // 24 hour format
          iHour = ((ucTemp[2] >> 4) * 10) + (ucTemp[2] & 0xf);
      }
      iWDay = ucTemp[3] - 1; // day of the week 0-6
      iMDay = ucTemp[4] & 0xf;
      iMDay += ((ucTemp[4] >> 4) * 10);
      iMon = ucTemp[5] & 0xf;
      iMon += (((ucTemp[5] >> 4) & 1) * 10);
      iMon--; // returns 1-12 instead of 0-11
} /* rtc_get_time() */

void DrawPlane(int bInvert, int iPlane, char *string1, char *string2)
{
  int iFG, iBG;

    if (bInvert) {
      iFG = BBEP_WHITE; iBG = BBEP_BLACK;
    } else {
      iFG = BBEP_BLACK; iBG = BBEP_WHITE;
    }
    bbep.setPlane(iPlane);
    bbep.fillScreen(iBG); // clear both buffers to white for full update
    bbep.setFont(Roboto_Black_78);
    bbep.setTextColor(iFG, iBG);
    bbep.drawString(string1, CENTER_X, 120);
    bbep.setFont(Roboto_Black_38);
    bbep.drawString(szDays[iWDay], CENTER_X, 200);
    bbep.drawString(string2, CENTER_X, 280);
}

void setup()
{
 // Serial.begin(115200);
 // Serial.println("Starting...");
 Wire.begin();
 Wire.setClock(400000);
} /* setup() */

void loop()
{
    char szTemp[32] ,szTemp2[32];
    int bPartial, iSleepTime, bInvert;
    
    bbep.initIO(PIN_DC, PIN_RST, PIN_BUSY, PIN_CS, MOSI, SCK, 4000000);
    while (1) {
      bInvert = 0;
      if ((iRunCount & 1) == 0) { // get time and update
          bPartial = ((iRunCount & 0x1f) != 0);
         rtc_get_time();
//         EPDInit((bPartial) ? MODE_PARTIAL : MODE_NORMAL);
        if (bbep.getChip() == BBEP_CHIP_UC81xx) {
        // On the UC81xx we have to explicitly write the old data and new for a partial update
        // the SSD16xx remembers the old properly and just needs to know the new
          if (bPartial) { // inverted too, with the old time strings
              DrawPlane(bInvert, PLANE_1, szTemp, szTemp2);
   //           bInvert = 0;
          }
        } // BBEP_CHIP_UC81xx
        // Print the current hour:minute
        sprintf(szTemp, "%02d:%02d", iHour, iMin);
        sprintf(szTemp2, "%s %d", szMonths[iMon], iMDay);

        if (!bPartial) {
            DrawPlane(bInvert, PLANE_1, szTemp, szTemp2);
        }
        DrawPlane(bInvert, PLANE_0, szTemp, szTemp2);
        bbep.refresh((bPartial) ? REFRESH_PARTIAL : REFRESH_FULL, false); // do a full refresh
//        iSleepTime = (bPartial) ? 750 : 4000; // sleep instead of watching the busy signal
        iSleepTime = (bPartial) ? 1 : 4; // sleep instead of watching the busy signal
    } else { // we're waking up after waiting for the update to finish
        bbep.sleep(1); // deep sleep
        iSleepTime = 59; // wake up in 59 seconds
    }
    //EPDDeInit(); // set GPIO to disconnected for lower power
    iRunCount++;
    if (!bPartial && iSleepTime == 59) iSleepTime = 56; // we already slept for 4 seconds
    for (int j=0; j<iSleepTime; j++) {
      WDSleepOneSec();
    }
    } // while (1)
} /* loop() */
