/*
 * i2c_ds3231.c
 *
 *  Created on: May 3, 2024
 *      Author: tm
 */

#include "system.h"
#include "user_io.h"
#include "uartstdio.h"  // User local version with larger RX buffer
#include "i2c_ds3231.h"

#include "i2c0.h"

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include "inc/hw_i2c.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/i2c.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "sleep.h"

static const uint8_t DS3231_ADDR = 0x68U;

char const * DS3231_DAY_OF_WEEK_to_string(DS3231_DAY_OF_WEEK dow)
{
    char const * temp = "";
    switch(dow)
    {
        case DS3231_DOW_SUNDAY:
        {
            temp = "Sunday";
            break;
        }
        case DS3231_DOW_MONDAY:
        {
            temp = "Monday";
            break;
        }
        case DS3231_DOW_TUESDAY:
        {
            temp = "Tuesday";
            break;
        }
        case DS3231_DOW_WEDNESDAY:
        {
            temp = "Wednesday";
            break;
        }
        case DS3231_DOW_THURSDAY:
        {
            temp = "Thursday";
            break;
        }
        case DS3231_DOW_FRIDAY:
        {
            temp = "Friday";
            break;
        }
        case DS3231_DOW_SATURDAY:
        {
            temp = "Saturday";
            break;
        }
        default:
        {
            temp = "Unknown";
            break;
        }
    }

    return temp;
}

int ds3231_init(bool en_alarm1, bool en_alarm2, bool* out_osc_stopped)
{
    uint16_t temp;

    // read first so we can check the OSF bit
    if( i2c0_read_reg16(DS3231_ADDR, DS3231_REG_CTRL1, &temp) <= 0 )
    {
        return 0;
    }

    if(out_osc_stopped)
    {
        *out_osc_stopped = (temp & 0x8000U) ? (true) : (false);
    }

    temp = 0x0004U; // enable osc, interrupt on alarm match (no sqw), no bbsqw, no alarms, no 32kHz

    // enable alarms if asked
    if(en_alarm1)
    {
        temp |= 0x0001U;
    }
    else
    {
        temp &= ~(0x0001U);
    }

    if(en_alarm2)
    {
        temp |= 0x0002U;
    }
    else
    {
        temp &= ~(0x0002U);
    }

    if( i2c0_write_reg16(DS3231_ADDR, DS3231_REG_CTRL1, temp) <= 0 )
    {
        return 0;
    }

    return 1;
}

int ds3231_get_status(uint8_t* out_status)
{
    if( i2c0_read_reg8(DS3231_ADDR, DS3231_REG_CTRL2, out_status) <= 0)
    {
        return 0;
    }

    return 1;
}



// reads the Tiva RTC and programs the ds3231 rtc with the same time
void ds3231_set_time(void)
{
    const time_t t_i = read_hib_rtcc();
    uint32_t u32time;
    uint8_t reg = DS3231_REG_SEC;
    uint8_t temp[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    int retVal;
    bool ds3231_osc_stopped = false;

    retVal = ds3231_init(false, false, &ds3231_osc_stopped);
    if( retVal <= 0 )
    {
        // ds3231 failed, but we will keep going
        // uprintf("\r\nWarning: ds3231 init failed\r\n");
        //sprintf(ebuf, "Warning: ds3231 init failed");
        //LOGERRORMSG(ebuf, (int)INIT_ERROR, apf_eeprom.sys_message_enable);
    }
    else
    {
         if(ds3231_osc_stopped)
         {
            prn_nl(); uprintf("Warning: ds3231 osc stopped"); prn_nl();
             //sprintf(ebuf, "Warning: ds3231 osc stopped");
             //LOGERRORMSG(ebuf, (int)INIT_ERROR, apf_eeprom.sys_message_enable);
         }
    }

    struct tm * time_info = gmtime(&u32time);
    time_info = get_RTC_time();                           //populate time struct
    u32time = read_hib_rtcc();       //read integer time, APFTODO, check time

#ifdef DEBUGCODE
    prn_nl();
    uprintf("get_RTC_time: %02d/%02d/%04d %02d:%02d:%02d ti_time %u, unix_time %u\r\n",
            time_info->tm_mon  + 1,
            time_info->tm_mday,
            time_info->tm_year + 1900,
            time_info->tm_hour,
            time_info->tm_min,
            time_info->tm_sec,
            u32time,
            u32time - TI_CCS_UNIX_EPOCH_OFFSET
            );
     prn_nl();
#endif

    temp[0] = decToBcd(time_info->tm_sec)  & 0x7FU;
    temp[1] = decToBcd(time_info->tm_min)  & 0x7FU;
    temp[2] = decToBcd(time_info->tm_hour) & 0x3FU;
    temp[3] = decToBcd(time_info->tm_wday) & 0x07U;
    temp[4] = decToBcd(time_info->tm_mday) & 0x3FU;
    temp[5] = decToBcd(time_info->tm_mon + 1) & 0x1FU;


    temp[5] &= ~(0x80U);
    temp[6] = decToBcd(time_info->tm_year - 100);


    if( i2c0_write_then_write(DS3231_ADDR, &reg, 1, temp, 7) <= 0)
    {
        prn_nl(); uprintf("Failed to write ds3231 i2c"); prn_nl();
        return;
    }
#ifdef DEBUGCODE
    uprintf("Write to i2c was successful"); prn_nl();

    DS3231_DateTime ds3231DateTime;

    ds3231_get_time(&ds3231DateTime);

    prn_nl();
    uprintf("ds3231 DateTime: %02d/%02d/%04d %02d:%02d:%02d",   ds3231DateTime.month, ds3231DateTime.day_of_month, ds3231DateTime.year,
            ds3231DateTime.hours, ds3231DateTime.minutes, ds3231DateTime.seconds);   prn_nl();

    prn_nl();
#endif
}

int ds3231_set_time_test(DS3231_DateTime const * const time)
{
    uint8_t reg = DS3231_REG_SEC;
    uint8_t temp[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    temp[0] = decToBcd(time->seconds)      & 0x7FU;
    temp[1] = decToBcd(time->minutes)      & 0x7FU;
    temp[2] = decToBcd(time->hours)        & 0x3FU;  //was 1F
    temp[3] = time->day_of_week            & 0x07U;
    temp[4] = decToBcd(time->day_of_month) & 0x3FU;

    if(time->year > 2099)
    {
        temp[5] = decToBcd(time->month) & 0x1FU;
        temp[5] |= 0x80U;
        temp[6] = decToBcd(time->year - 2100U);
    }
    else
    {
        temp[5] = decToBcd(time->month) & 0x1FU;
        temp[5] &= ~(0x80U);
        temp[6] = decToBcd(time->year - 2000U);
    }

    if( i2c0_write_then_write(DS3231_ADDR, &reg, 1, temp, 7) <= 0)
    {
        return 0;
    }

    return 1;
}

void cmd_ds3231_time_get(void)
{
    uint8_t reg = DS3231_REG_SEC;
    uint8_t temp[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    DS3231_DateTime ds3231DateTime;

    if( i2c0_write_then_read(DS3231_ADDR, &reg, 1, temp, 7) <= 0)
    {
        return;
    }

    // This assumes the 24h mode set by set_time is used
    ds3231DateTime.seconds      = bcdToDec(temp[0] & 0x7FU);
    ds3231DateTime.minutes      = bcdToDec(temp[1] & 0x7FU);
    ds3231DateTime.hours        = bcdToDec(temp[2] & 0x3FU);    //was 1F
    ds3231DateTime.day_of_week  = temp[3] & 0x07U;
    ds3231DateTime.day_of_month = bcdToDec(temp[4] & 0x3FU);
    ds3231DateTime.month        = bcdToDec(temp[5] & 0x1FU);
    ds3231DateTime.year         = bcdToDec(temp[6]) + 2000U;

    if( temp[5] & 0x80U )
    {
        ds3231DateTime.year += 100U;
    }

#ifdef OLDCODE
    //prn_nl();
    uprintf("ds3231 DateTime: %02d/%02d/%04d %02d:%02d:%02d",   ds3231DateTime.month, ds3231DateTime.day_of_month, ds3231DateTime.year,
            ds3231DateTime.hours, ds3231DateTime.minutes, ds3231DateTime.seconds);   prn_nl();
#endif
    ds3231DateTime.year -= 2000U;
    uprintf("%02d/%02d/%02d %02d:%02d:%02d",   ds3231DateTime.month, ds3231DateTime.day_of_month, ds3231DateTime.year,
            ds3231DateTime.hours, ds3231DateTime.minutes, ds3231DateTime.seconds);   prn_nl();
}


int ds3231_get_time(DS3231_DateTime* out_time)
{
    uint8_t reg = DS3231_REG_SEC;
    uint8_t temp[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    if( i2c0_write_then_read(DS3231_ADDR, &reg, 1, temp, 7) <= 0)
    {
        return 0;
    }

    // This assumes the 24h mode set by set_time is used
    out_time->seconds      = bcdToDec(temp[0] & 0x7FU);
    out_time->minutes      = bcdToDec(temp[1] & 0x7FU);
    out_time->hours        = bcdToDec(temp[2] & 0x1FU);
    out_time->day_of_week  = temp[3] & 0x07U;
    out_time->day_of_month = bcdToDec(temp[4] & 0x3FU);
    out_time->month        = bcdToDec(temp[5] & 0x1FU);
    out_time->year         = bcdToDec(temp[6]) + 2000U;

    if( temp[5] & 0x80U )
    {
        out_time->year += 100U;
    }

    return 1;
}

int ds3231_get_temp(uint16_t* out_temp)
{
    uint16_t temp;
    if( i2c0_read_reg16(DS3231_ADDR, DS3231_REG_TEMPH, &temp) <= 0)
    {
        return 0;
    }

    *out_temp = __revsh(temp);

    return 1;
}

float ds3231_temp_reg_to_degC(uint16_t temp_reg)
{
    DS3231_Temp temp;
    temp.reg = (temp_reg >> 6);
    return temp.reg * 0.25f;
}

int ds3231_set_alarm1(DS3231_Alarm const * const alarm, bool day_ndate, DS3231_ALARM1_MATCH match)
{
    uint8_t reg = DS3231_REG_ALARM1_SEC;
    uint8_t temp[4] = {0x00, 0x00, 0x00, 0x00};

    if(day_ndate && (match == DS3231_ALARM1_MATCH_SEC_MIN_HR_DATE))
    {
        return 0;
    }

    temp[0] = decToBcd(alarm->seconds) & 0x7FU;
    temp[1] = decToBcd(alarm->minutes) & 0x7FU;
    temp[2] = decToBcd(alarm->hours)   & 0x1FU;
    if(day_ndate)
    {
        temp[3] = alarm->day_of_week & 0x07U;
        temp[3] |= 0x40U;
    }
    else
    {
        temp[3] = alarm->day_of_month & 0x1FU;
    }

    switch(match)
    {
        case DS3231_ALARM1_EACH_SEC:
        {
            temp[0] |= 0x80U;
            temp[1] |= 0x80U;
            temp[2] |= 0x80U;
            temp[3] |= 0x80U;
            break;
        }
        case DS3231_ALARM1_MATCH_SEC:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x80U;
            temp[2] |= 0x80U;
            temp[3] |= 0x80U;
            break;
        }
        case DS3231_ALARM1_MATCH_SEC_MIN:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x00U;
            temp[2] |= 0x80U;
            temp[3] |= 0x80U;
            break;
        }
        case DS3231_ALARM1_MATCH_SEC_MIN_HR:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x00U;
            temp[2] |= 0x00U;
            temp[3] |= 0x80U;
            break;
        }
        case DS3231_ALARM1_MATCH_SEC_MIN_HR_DATE:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x00U;
            temp[2] |= 0x00U;
            temp[3] |= 0x00U;
            break;
        }
        case DS3231_ALARM1_MATCH_SEC_MIN_HR_DAY:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x00U;
            temp[2] |= 0x00U;
            temp[3] |= 0x00U;
            break;
        }
        default:
        {
            return 0;
        }
    }

    if( i2c0_write_then_write(DS3231_ADDR, &reg, 1, temp, 4) <= 0)
    {
        return 0;
    }

    return 1;
}
int ds3231_set_alarm2(DS3231_Alarm const * const alarm, bool day_ndate, DS3231_ALARM2_MATCH match)
{
    uint8_t reg = DS3231_REG_ALARM2_MIN;
    uint8_t temp[3] = {0x00, 0x00, 0x00};

    if(day_ndate && (match == DS3231_ALARM2_MATCH_MIN_HR_DATE))
    {
        return 0;
    }

    temp[0] = decToBcd(alarm->minutes) & 0x7FU;
    temp[1] = decToBcd(alarm->hours)   & 0x1FU;
    if(day_ndate)
    {
        temp[2] = alarm->day_of_week & 0x07U;
        temp[2] |= 0x40U;
    }
    else
    {
        temp[2] = alarm->day_of_month & 0x1FU;
    }

    switch(match)
    {
        case DS3231_ALARM2_EACH_MIN:
        {
            temp[0] |= 0x80U;
            temp[1] |= 0x80U;
            temp[2] |= 0x80U;
            break;
        }
        case DS3231_ALARM2_MATCH_MIN:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x80U;
            temp[2] |= 0x80U;
            break;
        }
        case DS3231_ALARM2_MATCH_MIN_HR:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x00U;
            temp[2] |= 0x80U;
            break;
        }
        case DS3231_ALARM2_MATCH_MIN_HR_DATE:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x00U;
            temp[2] |= 0x00U;
            break;
        }
        case DS3231_ALARM2_MATCH_MIN_HR_DAY:
        {
            temp[0] |= 0x00U;
            temp[1] |= 0x00U;
            temp[2] |= 0x00U;
            break;
        }
        default:
        {
            return 0;
        }
    }

    if( i2c0_write_then_write(DS3231_ADDR, &reg, 1, temp, 3) <= 0)
    {
        return 0;
    }

    return 1;
}
int ds3231_enable_alarm(bool en1, bool en2)
{
    uint16_t temp;
    if( i2c0_read_reg16(DS3231_ADDR, DS3231_REG_CTRL1, &temp) <= 0)
    {
        return 0;
    }

    if(en1)
    {
        temp |= 0x0001U;
    }
    else
    {
        temp &= ~(0x0001U);
    }

    if(en2)
    {
        temp |= 0x0002U;
    }
    else
    {
        temp &= ~(0x0002U);
    }

    temp |= 0x0004U;    // Set alarm mode
    temp &= ~(0x0300U); // Clear alarm flag bits

    if( i2c0_write_reg16(DS3231_ADDR, DS3231_REG_CTRL1, temp) <= 0)
    {
        return 0;
    }

    return 1;
}

// BCD to decimal conversion
uint8_t bcdToDec(uint8_t val) {
    return ((val >> 4) * 10) + (val & 0x0F);
}

// Decimal to BCD conversion
uint8_t decToBcd(uint8_t val) {
    return ((val / 10) << 4) + (val % 10);
}
