//
// RTC: configure real-time clock
//

#include "stm32f0xx_hal.h"
#include "rtc.h"
#include "gpio.h"


static RTC_HandleTypeDef hrtc;
static volatile uint32_t seconds_ctr = 0;


void rtc_incseconds(void)
{
	//HAL_GPIO_TogglePin(LED_BLUE);
	seconds_ctr += 1;
}

static void Error_Handler(void)
{
	volatile uint8_t crap = 1;

	for(uint16_t i=0; i<300; i++)
	{
		HAL_GPIO_TogglePin(LED_BLUE);
		HAL_Delay(100);
	}
}

// Initialize RTC
void rtc_init(void)
{
	__HAL_RCC_RTC_ENABLE();

    
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sDate;
	RTC_AlarmTypeDef sAlarm;

	HAL_PWR_EnableBkUpAccess();

	hrtc.Instance = RTC;
	hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
	hrtc.Init.AsynchPrediv = 127; // Note that both these dividers actually divide by their value + 1
	hrtc.Init.SynchPrediv = 311; // About 1Hz with 40kHz LSI
	hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
 	hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
	hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;

	if (HAL_RTC_Init(&hrtc) != HAL_OK)
	{
		Error_Handler();
	}

	sTime.Hours = 0x0;
	sTime.Minutes = 0x0;
	sTime.Seconds = 0x0;
	sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
	sTime.StoreOperation = RTC_STOREOPERATION_RESET;
	if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
	{
		Error_Handler();
	}

	sDate.WeekDay = RTC_WEEKDAY_MONDAY;
	sDate.Month = RTC_MONTH_JANUARY;
	sDate.Date = 0x01;
	sDate.Year = 0x19;

	if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
	{
		Error_Handler();
	}


	/**Enable the Alarm A
	*/
	sAlarm.AlarmTime.Hours = 0x0;
	sAlarm.AlarmTime.Minutes = 0x0;
	sAlarm.AlarmTime.Seconds = 0x0;
	sAlarm.AlarmTime.SubSeconds = 0x0;
	sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
	sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;

	// Alarm will trigger on the Xth second of every minute
	sAlarm.AlarmMask = RTC_ALARMMASK_ALL; // Trigger every second for now
	sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_SS14; //RTC_ALARMSUBSECONDMASK_ALL;
	sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
	sAlarm.AlarmDateWeekDay = RTC_WEEKDAY_MONDAY;
	sAlarm.Alarm = RTC_ALARM_A;
	if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BCD) != HAL_OK)
	{
		Error_Handler();
	}

	HAL_NVIC_SetPriority(RTC_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(RTC_IRQn);

	HAL_RTC_WaitForSynchro(&hrtc);


}

static RTC_TimeTypeDef time_last = {0};

// Get the time from the RTC
RTC_TimeTypeDef* rtc_time(void)
{
	HAL_RTC_GetTime(&hrtc, &time_last, RTC_FORMAT_BCD);
	return &time_last;
}

// Calculate value in milliseconds from subseconds of the RTC
uint16_t __rtc_millis(uint32_t sub_seconds)
{
	// Subsecond counter counts down from SynchPrediv value to zero
	return (hrtc.Init.SynchPrediv - sub_seconds) * 999 / hrtc.Init.SynchPrediv; // 0 - 999 mS
}

#define JULIAN_DATE_BASE 2440588 // Unix epoch time in Julian calendar (UnixTime = 00:00:00 01.01.1970 => JDN = 2440588)

// Calculate timestamp in milliseconds since Unix epoch
uint64_t rtc_timestamp(void)
{
	RTC_TimeTypeDef time = {0};
	RTC_DateTypeDef date = {0};

	HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD);
	HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BCD);

	// Thanks to LonelyWolf: https://github.com/LonelyWolf/stm32/blob/master/stm32l-dosfs/RTC.c
	// Convert Date/Time structures to epoch time
	uint8_t  a;
	uint16_t y;
	uint8_t  m;
	uint64_t JDN;

	// These hardcore math's are taken from http://en.wikipedia.org/wiki/Julian_day

	// Calculate some coefficients
	a = (14 - date.Month) / 12;
	y = (date.Year + 2000) + 4800 - a; // years since 1 March, 4801 BC
	m = date.Month + (12 * a) - 3; // since 1 March, 4801 BC

	// Gregorian calendar date compute
	JDN  = date.Date;
	JDN += (153 * m + 2) / 5;
	JDN += 365 * y;
	JDN += y / 4;
	JDN += -y / 100;
	JDN += y / 400;
	JDN  = JDN - 32045;
	JDN  = JDN - JULIAN_DATE_BASE;    // Calculate from base date
	JDN *= 86400;                     // Days to seconds
	JDN += time.Hours * 3600;    // ... and today seconds
	JDN += time.Minutes * 60;
	JDN += time.Seconds;

	JDN *= 1000; // Prepare to add ms

	JDN += __rtc_millis(time.SubSeconds);

	return JDN;

}

uint32_t rtc_timestamp_seconds(void)
{
	return seconds_ctr;
}


// Calibrate the RTC somehow
void rtc_cal(void)
{
	// Do something with hrtc.Instance->CALR; // this has a plus and minus component, see refman
}


// RTC accessor
RTC_HandleTypeDef* rtc_gethandle(void)
{
	return &hrtc;
}


