#include "rtc.h"
#include "settings_api.h"
#include "common_config.h"
#include <string.h>
#include <stdio.h>


// Days in a month
uint8_t TM_RTC_Months[2][12] = {
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},	// Not leap year
	{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}	// Leap year
};

// monthly correction data sheet
const uint8_t table_week[12] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};

// monmonth data table of common year
const uint8_t mon_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

// Internal RTC defines 
#define TM_RTC_LEAP_YEAR(year) 			((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0))
#define TM_RTC_DAYS_IN_YEAR(x)			TM_RTC_LEAP_YEAR(x) ? 366 : 365
#define TM_RTC_OFFSET_YEAR				1970
#define TM_RTC_SECONDS_PER_DAY			86400
#define TM_RTC_SECONDS_PER_HOUR			3600
#define TM_RTC_SECONDS_PER_MINUTE		60
#define TM_RTC_BCD2BIN(x)				((((x) >> 4) & 0x0F) * 10 + ((x) & 0x0F))
#define TM_RTC_CHAR2NUM(x)				((x) - '0')
#define TM_RTC_CHARISNUM(x)				((x) >= '0' && (x) <= '9')


extern SemaphoreHandle_t flash_mutex;



/**
  * @brief  rtc peripheral initialization.
  * @param  calendar
  * @retval 0: rtc already init
            1: rtc init
  */
uint8_t TM_RTC_Init(void)
{
    TM_RTC_t datatime;
    
    uint16_t tmp = 0;
    
    // enable pwc and bpr clocks
    crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE);
    crm_periph_clock_enable(CRM_BPR_PERIPH_CLOCK, TRUE);
    
    // enable the battery-powered domain write operations
    pwc_battery_powered_domain_access(TRUE);
    
    tmp = bpr_data_read(BACKUP_RTC_KEY);
    printf("RTC: %X\r\n", tmp);
    
    // check if rtc is initialized 
    if (bpr_data_read(BACKUP_RTC_KEY) != 0x1234)
    {
        // reset battery-powered domain register
        bpr_reset();
        
        // enable the lext osc
        crm_clock_source_enable(CRM_CLOCK_SOURCE_LEXT, TRUE);
        // wait lext is ready
        while(crm_flag_get(CRM_LEXT_STABLE_FLAG) == RESET);
        // select the rtc clock source
        crm_rtc_clock_select(CRM_RTC_CLOCK_LEXT);
        
        // enable rtc clock
        crm_rtc_clock_enable(TRUE);
        
        // wait for rtc registers update 
        rtc_wait_update_finish();
        
        // wait for the register write to complete
        rtc_wait_config_finish();
        
        // enable the rtc second
        nvic_irq_enable(RTC_IRQn, 6, 0);
        rtc_interrupt_enable(RTC_TS_INT, TRUE);
                
        // set rtc divider: set rtc period to 1sec 
        rtc_divider_set(32767);
        
        // wait for the register write to complete
        rtc_wait_config_finish();
        
        // set date and time
        datatime.date    = 1;
        datatime.day     = 1;
        datatime.month   = 1;
        datatime.year    = 0;
        datatime.hours   = 0;
        datatime.minutes = 0;
        datatime.seconds = 0;
        
        TM_RTC_SetDateTime(&datatime);
        
        // writes data to bpr register 
        bpr_data_write(BACKUP_RTC_KEY, 0x1234);
        
        return 1;
    }
    else
    {
        // wait for rtc registers update
        rtc_wait_update_finish();

        // wait for the register write to complete
        rtc_wait_config_finish();
    
        // enable the rtc second
        nvic_irq_enable(RTC_IRQn, 6, 0);
        rtc_interrupt_enable(RTC_TS_INT, TRUE);
        
        return 0;    
    }
}


//
void TM_RTC_SetDataTimeUnix(uint32_t unixTime)
{
    TM_RTC_t data;
  
    TM_RTC_GetDateTimeFromUnix(&data, unixTime);
    rtc_counter_set(unixTime);
    rtc_wait_config_finish();
}


/**
  * @brief  set time. convert the input clock to a second.
  *         the time basic : 1970.1.1
  *         legitimate year: 1970 ~ 2099
  * @param  calendar
  * @retval 0: set time right.
  *         1: set time failed.
  */
TM_RTC_Result_t TM_RTC_SetDateTime(TM_RTC_t* data)
{
    uint32_t seccount = 0;
    
    if (data->year > 99 || 
		data->month == 0 || 
		data->month > 12 ||
		data->date == 0 ||
		data->date > TM_RTC_Months[TM_RTC_LEAP_YEAR(2000 + data->year) ? 1 : 0][data->month - 1] ||
		data->hours > 23 ||
		data->minutes > 59 ||
		data->seconds > 59 ||
		data->day == 0 ||
		data->day > 7) 
    {
		return TM_RTC_Result_Error; 
	}
    
    // enable pwc and bpr clocks
    crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE);
    crm_periph_clock_enable(CRM_BPR_PERIPH_CLOCK, TRUE);
    
    // enable write access to bpr domain
    pwc_battery_powered_domain_access(TRUE);
    
    // set the rtc counter value
    seccount = TM_RTC_GetUnixTimeStamp(data);
    rtc_counter_set(seccount);
    
    // wait for the register write to complete
    rtc_wait_config_finish();
    
    return TM_RTC_Result_Ok;
}


//
TM_RTC_Result_t TM_RTC_SetDateTimeString(char* str) 
{
	TM_RTC_t tmp;
	uint8_t i = 0;
	
	// Get date
	tmp.date = 0;
	while (TM_RTC_CHARISNUM(*(str + i))) {
		tmp.date = tmp.date * 10 + TM_RTC_CHAR2NUM(*(str + i));
		i++;
	}
	i++;
	
	// Get month
	tmp.month = 0;
	while (TM_RTC_CHARISNUM(*(str + i))) {
		tmp.month = tmp.month * 10 + TM_RTC_CHAR2NUM(*(str + i));
		i++;
	}
	i++;
	
	// Get year
	tmp.year = 0;
	while (TM_RTC_CHARISNUM(*(str + i))) {
		tmp.year = tmp.year * 10 + TM_RTC_CHAR2NUM(*(str + i));
		i++;
	}
	i++;
	
	// Get day in a week
	tmp.day = 0;
	while (TM_RTC_CHARISNUM(*(str + i))) {
		tmp.day = tmp.day * 10 + TM_RTC_CHAR2NUM(*(str + i));
		i++;
	}
	i++;
	
	// Get hours
	tmp.hours = 0;
	while (TM_RTC_CHARISNUM(*(str + i))) {
		tmp.hours = tmp.hours * 10 + TM_RTC_CHAR2NUM(*(str + i));
		i++;
	}
	i++;
	
	// Get minutes
	tmp.minutes = 0;
	while (TM_RTC_CHARISNUM(*(str + i))) {
		tmp.minutes = tmp.minutes * 10 + TM_RTC_CHAR2NUM(*(str + i));
		i++;
	}
	i++;
	
	// Get seconds
	tmp.seconds = 0;
	while (TM_RTC_CHARISNUM(*(str + i))) {
		tmp.seconds = tmp.seconds * 10 + TM_RTC_CHAR2NUM(*(str + i));
		i++;
	}
	i++;
	
	// Return status from set date time function 
	return TM_RTC_SetDateTime(&tmp);
}


//
void TM_RTC_GetDateTime(TM_RTC_t* data, TM_RTC_Format_t format) 
{
    (void)format;
	uint32_t unix = rtc_counter_get();

    TM_RTC_GetDateTimeFromUnix(data, unix);
}


//
uint32_t TM_RTC_GetUnixTimeStamp(TM_RTC_t* data) 
{
	uint32_t days = 0, seconds = 0;
	uint16_t i;
	uint16_t year = (uint16_t)(data->year + 2000);

	// Year is below offset year
	if (year < TM_RTC_OFFSET_YEAR) {
		return 0;
	}
	// Days in back years
	for (i = TM_RTC_OFFSET_YEAR; i < year; i++) {
		days += TM_RTC_DAYS_IN_YEAR(i);
	}
	// Days in current year
	for (i = 1; i < data->month; i++) {
		days += TM_RTC_Months[TM_RTC_LEAP_YEAR(year)][i - 1];
	}
	// Day starts with 1
	days += data->date - 1;
	seconds = days * TM_RTC_SECONDS_PER_DAY;
	seconds += data->hours * TM_RTC_SECONDS_PER_HOUR;
	seconds += data->minutes * TM_RTC_SECONDS_PER_MINUTE;
	seconds += data->seconds;
	
	// seconds = days * 86400;
	return seconds;
}


//
void TM_RTC_GetDateTimeFromUnix(TM_RTC_t* data, uint32_t unix) 
{
	uint16_t year;
	
	// Store unix time to unix in struct
	data->unix = unix;
	// Get seconds from unix
	data->seconds = unix % 60;
	// Go to minutes
	unix /= 60;
	// Get minutes
	data->minutes = unix % 60;
	// Go to hours 
	unix /= 60;
	// Get hours
	data->hours = unix % 24;
	// Go to days
	unix /= 24;
	
	// Get week day
	// Monday is day one
	data->day = (unix + 3) % 7 + 1;

	// Get year
	year = 1970;
	while (1) {
		if (TM_RTC_LEAP_YEAR(year)) {
			if (unix >= 366) {
				unix -= 366;
			} else {
				break;
			}
		} else if (unix >= 365) {
			unix -= 365;
		} else {
			break;
		}
		year++;
	}
	// Get year in xx format
	data->year = (uint8_t) (year - 2000);
	// Get month
	for (data->month = 0; data->month < 12; data->month++) {
		if (TM_RTC_LEAP_YEAR(year) && unix >= (uint32_t)TM_RTC_Months[1][data->month]) {
			unix -= TM_RTC_Months[1][data->month];
		} else if (unix >= (uint32_t)TM_RTC_Months[0][data->month]) {
			unix -= TM_RTC_Months[0][data->month];
		} else {
			break;
		}
	}
	// Get month
	// Month starts with 1
	data->month++;
	// Get date
	// Date starts with 1
	data->date = unix + 1;
}

//
void TM_RTC_PrintTime(void)
{
    TM_RTC_t data;
    
    uint32_t unix = rtc_counter_get();
    
    TM_RTC_GetDateTimeFromUnix(&data, unix);
	
	printf("%02d.%02d.%02d %02d:%02d:%02d\r\n", data.date, data.month, data.year,
			data.hours, data.minutes, data.seconds);
}

//
uint32_t RTC_GetUnixTime(void)
{
    TM_RTC_t currentTime;
  
    TM_RTC_GetDateTime(&currentTime, TM_RTC_Format_BIN);
    return TM_RTC_GetUnixTimeStamp(&currentTime);
}

//
void rtc_subtim_init(void)
{
    crm_clocks_freq_type crm_clocks_freq_struct = {0};
    
    crm_periph_clock_enable(CRM_TMR5_PERIPH_CLOCK, TRUE);

    crm_clocks_freq_get(&crm_clocks_freq_struct);
    tmr_base_init(TMR5, 9999, 24000 - 1);
    tmr_cnt_dir_set(TMR5, TMR_COUNT_UP);

    tmr_flag_clear(TMR5, TMR_OVF_FLAG);
    
    NVIC_ClearPendingIRQ(TMR5_GLOBAL_IRQn);
    nvic_irq_enable(TMR5_GLOBAL_IRQn, 5, 0);
       
    tmr_counter_enable(TMR5, TRUE);
    tmr_interrupt_enable(TMR5, TMR_OVF_INT, TRUE);
}

//
uint64_t rtc_get_ms(void)
{
    return ((uint64_t)RTC_GetUnixTime()*1000 + TMR5->cval/10);
}

//
uint32_t rtc_foo(void)
{
    return tmr_counter_value_get(TMR5);
}

//
void rtc_set_in_ms(uint64_t ms)
{
    TM_RTC_SetDataTimeUnix(ms);
}

//
void TMR5_GLOBAL_IRQHandler(void)
{
    if (tmr_flag_get(TMR5, TMR_OVF_FLAG) != RESET)
    {
        tmr_flag_clear(TMR5, TMR_OVF_FLAG);
        tmr_interrupt_enable(TMR5, TMR_OVF_INT, FALSE);
        tmr_counter_enable(TMR5, FALSE);
    }
}

//
void RTC_IRQHandler(void)
{
    if (rtc_flag_get(RTC_TS_FLAG) != RESET)
    {
        rtc_flag_clear(RTC_TS_FLAG);
        
        tmr_interrupt_enable(TMR5, TMR_OVF_INT, TRUE);
        tmr_counter_enable(TMR5, TRUE);
        TMR5->cval = 0;
    }
}