#include "rtc.h"

// stm32 wants 8 bit address 1010010b << 1 10100100b
// 52 in hex(0101 = 5, 0010 = 2)
#define RV3028_I2C_ADDRESS          (0x52U << 1)

#define RV3028_I2C_TIMEOUT_MS       100U
#define RV3028_EEPROM_TIMEOUT_MS    100U

// what is RTC is status
// currently doing or what events have happened.
// checks EEPROM status and interrupt/event flags
// used with EEPROM. EEPROM takes time. RV3028_REG_STATUS sends ready
// pg 12
#define RV3028_REG_STATUS           0x0EU

// see Page 23 and page 24
// Control 1 selects alarm/update sources and configures the periodic countdown timer
#define RV3028_REG_CONTROL1         0x0FU
/*
  7   TRPT  Countdown timer repeat mode: 1 = repeat, 0 = run once
  6   —     Not used; reads as 0
  5   WADA  Alarm selection: 1 = date alarm, 0 = weekday alarm
  4   USEL  Periodic update selection: 1 = minute update, 0 = second update
  3   EERD  Automatic EEPROM refresh disable: 1 = disabled, 0 = enabled
  2   TE    Countdown timer enable: 1 = enabled, 0 = disabled
  1:0 TD    Countdown timer clock-source selection
*/

// Control 2 enables interrupt outputs, selects the hour format, timestamps, and RESET
#define RV3028_REG_CONTROL2         0x10U
/*
    // MSB                         LSB
    //  7   6   5   4   3   2   1   0
  7   TSE    Time-stamp function enable
  6   CLKIE  Interrupt-controlled CLKOUT enable
  5   UIE    Periodic time-update interrupt enable
  4   TIE    Periodic countdown timer interrupt enable
  3   AIE    Alarm interrupt enable
  2   EIE    External-event interrupt enable
  1   12_24  Hour format selection
  0   RESET  Reset/synchronize clock and calendar prescaler operation
*/

// unix timestamps uses uint_32
// needs four bytes and four addresses
// auto-increments its internal register to RV3028_REG_UNIX_TIME_1, ...
#define RV3028_REG_UNIX_TIME_0      0x1BU

// special command values to read or write its internal EEPROM
#define RV3028_REG_EE_COMMAND       0x27U

// read and get EEPROM memory keeps after power is removed.
#define RV3028_REG_EEPROM_BACKUP    0x37U

// mask to temporarily stop automatic EEPROM-to-RAM refreshing
#define RV3028_CONTROL1_EERD        (1U << 3)

// mask to  reset 1Hz timer
// xxxxxxx1 to RV3028_REG_CONTROL2 starts fresh 1Hz counter (the prescaler)
// switches automatically to xxxxxxx0
#define RV3028_CONTROL2_RESET       (1U << 0)

// read; 1 means the EEPROM is still busy, and 0 means it is ready
#define RV3028_STATUS_EEBUSY        (1U << 7)

// mask for backup battery setting
#define RV3028_BACKUP_BSM_MASK      (3U << 2)

// values to read into for backup battery setting
#define RV3028_BACKUP_BSM_LSM       (3U << 2)


#define RV3028_REG_TIMER_VALUE_0     0x0AU
// three types of alarm interrupts 
// 1) periodic countdown timer interrupt, where we set the freq[Hz]
// 2) every second or every minute
// 3) calendar alarm

#define RV3028_STATUS_TF             (1U << 3) // mask for value TF, bit 3 = TF countdown timer flag, clears the timer


//pg 63
/*
TD = 00 -> 4096 Hz
TD = 01 -> 64 Hz
TD = 10 -> 1 Hz
TD = 11 -> 1/60 Hz
*/

#define RV3028_CONTROL1_TRPT         (1U << 7) // 10000000
/* TRPT = 1 selects repeating countdown mode. */
// what should happen after the countdown reaches zero?

#define RV3028_CONTROL1_TE           (1U << 2) // 00000100
/* TE = 1 starts the periodic countdown timer. */
// TE = 1 -> timer running

#define RV3028_CONTROL1_TD_MASK      (3U << 0) // 00000011
// TD occupies bits 1:0 of Control 1, need to clear thos bits

#define RV3028_CONTROL1_TD_1HZ       (2U << 0) // 00000010 // 1Hz

#define RV3028_CONTROL2_TIE          (1U << 4) // 00100000
// TIE = 1 enables countdown timer interrupt pulses

// i think using the masks above hold int LOW for about 7.8 ms, then INT releases HIGH

#define RV3028_TIMER_PERIOD_SECONDS  15U



static I2C_HandleTypeDef *rtc_i2c = NULL;
static volatile bool rtc_interrupt_pending = false;

static HAL_StatusTypeDef RTC_Read(uint8_t reg, uint8_t *data, uint16_t length)
{
    if ((rtc_i2c == NULL) || (data == NULL) || (length == 0U))
    {
        return HAL_ERROR;
    }

    return HAL_I2C_Mem_Read(rtc_i2c,
                            RV3028_I2C_ADDRESS,
                            reg,
                            I2C_MEMADD_SIZE_8BIT,
                            data,
                            length,
                            RV3028_I2C_TIMEOUT_MS);
}

static HAL_StatusTypeDef RTC_Write(uint8_t reg,
                                   const uint8_t *data,
                                   uint16_t length)
{
    if ((rtc_i2c == NULL) || (data == NULL) || (length == 0U))
    {
        return HAL_ERROR;
    }

    return HAL_I2C_Mem_Write(rtc_i2c,
                             RV3028_I2C_ADDRESS,
                             reg,
                             I2C_MEMADD_SIZE_8BIT,
                             (uint8_t *)data,
                             length,
                             RV3028_I2C_TIMEOUT_MS);
}

static HAL_StatusTypeDef RTC_WaitForEEPROM(void)
{
    uint8_t status;
    uint32_t start = HAL_GetTick();

    do
    {
        if (RTC_Read(RV3028_REG_STATUS, &status, 1U) != HAL_OK){return HAL_ERROR;}
        if ((status & RV3028_STATUS_EEBUSY) == 0U){return HAL_OK;}
    }
    while ((HAL_GetTick() - start) < RV3028_EEPROM_TIMEOUT_MS);

    return HAL_TIMEOUT;
}

// sets rtc to use to VBACKUP when VDD falls below 2.0 V.
// this is only set in temp RAM, needs to call 
// RTC_SaveConfigurationToEEPROM to save temp RAM config to EEPROM
static HAL_StatusTypeDef RTC_EnableBackupBatteryRAM(void)
{
    uint8_t backup;

    if (RTC_Read(RV3028_REG_EEPROM_BACKUP, &backup, 1U) != HAL_OK)
    {
        return HAL_ERROR;
    }

    backup &= (uint8_t)~RV3028_BACKUP_BSM_MASK;
    backup |= RV3028_BACKUP_BSM_LSM;

    return RTC_Write(RV3028_REG_EEPROM_BACKUP, &backup, 1U);
}

// save RAM config to EEPROM
// Copy configuration RAM registers 0x30–0x37 into EEPROM
// it does not read the register values
// RV3028_REG_EE_COMMANDs do that for you
static HAL_StatusTypeDef RTC_SaveConfigurationToEEPROM(void)
{
    uint8_t control1;
    uint8_t command;
    HAL_StatusTypeDef result = HAL_ERROR;

    if (RTC_WaitForEEPROM() != HAL_OK){return HAL_ERROR;}

    if (RTC_Read(RV3028_REG_CONTROL1, &control1, 1U) != HAL_OK){return HAL_ERROR;}

    control1 |= RV3028_CONTROL1_EERD;
    if (RTC_Write(RV3028_REG_CONTROL1, &control1, 1U) != HAL_OK){return HAL_ERROR;}

    command = 0x00U;
    if (RTC_Write(RV3028_REG_EE_COMMAND, &command, 1U) != HAL_OK){goto restore_refresh;}

    command = 0x11U;
    if (RTC_Write(RV3028_REG_EE_COMMAND, &command, 1U) != HAL_OK){goto restore_refresh;}

    result = RTC_WaitForEEPROM();

    restore_refresh:
        control1 &= (uint8_t)~RV3028_CONTROL1_EERD;
        if (RTC_Write(RV3028_REG_CONTROL1, &control1, 1U) != HAL_OK){return HAL_ERROR;}

    return result;
}

HAL_StatusTypeDef RTC_Init(I2C_HandleTypeDef *hi2c)
{
    if (hi2c == NULL)
    {
        return HAL_ERROR;
    }

    rtc_i2c = hi2c;
    rtc_interrupt_pending = false;

    return RTC_IsReady();
}

HAL_StatusTypeDef RTC_IsReady(void)
{
    if (rtc_i2c == NULL){return HAL_ERROR;}

    return HAL_I2C_IsDeviceReady(
        rtc_i2c,
        RV3028_I2C_ADDRESS,
        2U,
        RV3028_I2C_TIMEOUT_MS);
}

HAL_StatusTypeDef RTC_SetUnixTime(uint32_t unix_time)
{
    uint8_t control2;
    uint8_t data[4];

    if (RTC_Read(RV3028_REG_CONTROL2, &control2, 1U) != HAL_OK){return HAL_ERROR;}

    // start 1Hz timer back to 0
    control2 |= RV3028_CONTROL2_RESET;
    if (RTC_Write(RV3028_REG_CONTROL2, &control2, 1U) != HAL_OK){return HAL_ERROR;}

    data[0] = (uint8_t)(unix_time);
    data[1] = (uint8_t)(unix_time >> 8);
    data[2] = (uint8_t)(unix_time >> 16);
    data[3] = (uint8_t)(unix_time >> 24);

    // auto-increments its internal register to RV3028_REG_UNIX_TIME_1, RV3028_REG_UNIX_TIME_2, ...
    return RTC_Write(RV3028_REG_UNIX_TIME_0, data, sizeof(data));
}

HAL_StatusTypeDef RTC_GetUnixTime(uint32_t *unix_time)
{
    uint8_t data[4];

    if (unix_time == NULL){return HAL_ERROR;}

    if (RTC_Read(RV3028_REG_UNIX_TIME_0, data, sizeof(data)) != HAL_OK){return HAL_ERROR;}

    *unix_time = ((uint32_t)data[0])
               | ((uint32_t)data[1] << 8)
               | ((uint32_t)data[2] << 16)
               | ((uint32_t)data[3] << 24);

    return HAL_OK;
}

HAL_StatusTypeDef RTC_EnableOneTimeBackupBattery(void)
{
    if (RTC_EnableBackupBatteryRAM() != HAL_OK){return HAL_ERROR;}

    return RTC_SaveConfigurationToEEPROM();
}

HAL_StatusTypeDef RV3028_ClearTimerFlag(void)
{ 
    // my trsting showed the periodic timer still works with out clearing this
    // but i think you should anyway ?
    uint8_t status;

    if (RTC_Read(
            RV3028_REG_STATUS,
            &status,
            1U) != HAL_OK)
    {
        return HAL_ERROR;
    }

    status &= (uint8_t)~RV3028_STATUS_TF;

    return RTC_Write(
        RV3028_REG_STATUS,
        &status,
        1U);
}

HAL_StatusTypeDef RV3028_StartPeriodicTimer(uint16_t seconds)
{
    uint8_t control1;
    uint8_t control2;
    uint8_t timer_value[2];

    if ((seconds == 0U) || (seconds > 4095U))
    {
        return HAL_ERROR;
    }

    if (RTC_Read(
            RV3028_REG_CONTROL1,
            &control1,
            1U) != HAL_OK)
    {
        return HAL_ERROR;
    }

    control1 &= (uint8_t)~RV3028_CONTROL1_TE;

    if (RTC_Write(
            RV3028_REG_CONTROL1,
            &control1,
            1U) != HAL_OK)
    {
        return HAL_ERROR;
    }

    if (RTC_Read(
            RV3028_REG_CONTROL2,
            &control2,
            1U) != HAL_OK)
    {
        return HAL_ERROR;
    }

    control2 &= (uint8_t)~RV3028_CONTROL2_TIE;

    if (RTC_Write(
            RV3028_REG_CONTROL2,
            &control2,
            1U) != HAL_OK)
    {
        return HAL_ERROR;
    }

    if (RV3028_ClearTimerFlag() != HAL_OK)
    {
        return HAL_ERROR;
    }

    control1 &= (uint8_t)~(
        RV3028_CONTROL1_TRPT |
        RV3028_CONTROL1_TE |
        RV3028_CONTROL1_TD_MASK);

    control1 |= (uint8_t)(
        RV3028_CONTROL1_TRPT |
        RV3028_CONTROL1_TD_1HZ);

    if (RTC_Write(
            RV3028_REG_CONTROL1,
            &control1,
            1U) != HAL_OK)
    {
        return HAL_ERROR;
    }

    timer_value[0] = (uint8_t)(seconds & 0xFFU);
    timer_value[1] = (uint8_t)((seconds >> 8) & 0x0FU);

    if (RTC_Write(
            RV3028_REG_TIMER_VALUE_0,
            timer_value,
            sizeof(timer_value)) != HAL_OK)
    {
        return HAL_ERROR;
    }

    control2 |= RV3028_CONTROL2_TIE;

    if (RTC_Write(
            RV3028_REG_CONTROL2,
            &control2,
            1U) != HAL_OK)
    {
        return HAL_ERROR;
    }

    control1 |= RV3028_CONTROL1_TE;

    return RTC_Write(
        RV3028_REG_CONTROL1,
        &control1,
        1U);
}

// caled in main.c
void RTC_OnInterrupt(void)
{
    rtc_interrupt_pending = true;
}

bool RTC_InterruptPending(void)
{
    if (!rtc_interrupt_pending)
    {
        return false;
    }

    rtc_interrupt_pending = false;
    return true;
}