/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_conf.h"
#include "Clock.h"
#include "Interrupts.h"
#include "Display.h"
#include "Sound.h"
#include "MotionControl.h"

/* Private definitions -------------------------------------------------------*/
#define __RTC_CLKSRC_VAL          0x00000100
#define __RTC_PERIOD              0x000003E8
#define __RTCCLK                  (32768)

/* Private variables ---------------------------------------------------------*/
volatile clock_State _clock_state = CLOCK_Calibrating;
volatile bool _clock_alarmed = FALSE;

/* Private function prototypes -----------------------------------------------*/
static void Clock_OnSecond();
static void Clock_OnAlarm();

/* Exported functions --------------------------------------------------------*/

// Clock_StartRTC()
// ----------------
// Start the real time clock.
extern void Clock_StartRTC()
{
	/* Initial state is calibrating servos -----------------------------------*/
	_clock_state = CLOCK_Calibrating;

	/* Enable PWR and BKP clocks ---------------------------------------------*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

	/* Allow access to BKP Domain --------------------------------------------*/
	PWR_BackupAccessCmd(ENABLE);

	/* Reset Backup Domain ---------------------------------------------------*/
	BKP_DeInit();

	/* Enable LSE ------------------------------------------------------------*/
	RCC_LSEConfig(RCC_LSE_ON);

	/* Wait till LSE is ready ------------------------------------------------*/
	while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);

	/* Select LSE as RTC Clock Source ----------------------------------------*/
	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

	/* Enable RTC Clock ------------------------------------------------------*/
	RCC_RTCCLKCmd(ENABLE);
	RTC_WaitForLastTask();

	/* Wait for RTC registers synchronization --------------------------------*/
	RTC_WaitForSynchro();

	/* Wait until last write operation on RTC registers has finished ---------*/
	RTC_WaitForLastTask();

	/* Set RTC prescaler: set RTC period to 1sec -----------------------------*/
	RTC_SetPrescaler(__RTC_PERIOD*__RTCCLK/1000-1); // RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)

	/* Wait until last write operation on RTC registers has finished ---------*/
	RTC_WaitForLastTask();

	/* Wait for RTC registers synchronization --------------------------------*/
	RTC_WaitForSynchro();

	/* Configure one bit for preemption priority */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	/* Enable the RTC Interrupt ----------------------------------------------*/
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* Configure EXTI Line17(RTC Alarm) to generate interrupt on rising edge -*/
	EXTI_DeInit();
	EXTI_ClearITPendingBit(EXTI_Line17);
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line17;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);

	/* Enable the RTC Alarm Interrupt ----------------------------------------*/
	NVIC_InitStructure.NVIC_IRQChannel = RTCAlarm_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* Enable the RTC Second -------------------------------------------------*/
	RTC_ITConfig(RTC_IT_SEC, ENABLE);
	RTC_WaitForLastTask();

	/* Enable the RTC Alarm interrupt ----------------------------------------*/
	RTC_ITConfig(RTC_IT_ALR, ENABLE);
	RTC_WaitForLastTask();

	/* Register the interrupt callbacks --------------------------------------*/
	Interrupts_RegisterCallback(INTERRUPTS_RTC, Clock_OnSecond);
	Interrupts_RegisterCallback(INTERRUPTS_RTCAlarm, Clock_OnAlarm);
}

// Clock_Init()
// ------------
// Initialize the clock module.
extern void Clock_Init()
{
    MotionControl_Init();
    Display_Init();
    Sound_Init();

	Display_EnableAlarm(FALSE);
}

// Clock_BeginCalibration()
// ------------------------
// Start the continuous reading of the ADC values from the servo calibration trimpots.
extern void Clock_BeginCalibration()
{
	Display_SetState(DISPLAY_Calibrating);
    Robot_BeginCalibration();
}

// Clock_UpdateCalibration()
// -------------------------
// Sets all servos to a known angle so that the servo calibration parameters can
// be correctly adjusted.
extern void Clock_UpdateCalibration()
{
	Display_Update();
	Robot_UpdateCalibration();
}

// Clock_EndCalibration()
// ----------------------
// Stop the ADC conversions and leave the servo calibration parameters in a fixed state.
extern void Clock_EndCalibration()
{
	Robot_EndCalibration();
	Display_SetState(DISPLAY_SettingTime);
}

// Clock_SetTime()
// ---------------
// Set the time.
extern void Clock_SetTime(uint32_t time)
{
    RTC_WaitForLastTask();
    RTC_SetCounter(time);
    RTC_WaitForLastTask();
    RTC_WaitForSynchro();
}

// Clock_SetAlarm()
// ----------------
// Set the alarm time.
extern void Clock_SetAlarm(uint32_t time)
{
    RTC_WaitForLastTask();
    RTC_SetAlarm(time);
    RTC_WaitForLastTask();
    RTC_WaitForSynchro();
}

// Clock_GetAlarm()
// ----------------
// Get the current alarm time.
extern uint32_t Clock_GetAlarm()
{
	uint16_t lvalue = RTC->ALRL;
	return (((uint32_t)RTC->ALRH << 16) | lvalue);
}


// Clock_ToggleAlarm()
// -------------------
// Enable/disable the alarm.
extern void Clock_ToggleAlarm()
{
	_clock_alarmed = !_clock_alarmed;

	if (_clock_alarmed)
	{
		Display_EnableAlarm(TRUE);
	}
	else
	{
		Display_EnableAlarm(FALSE);
	}
}

// Clock_SetNextState()
// --------------------
// Set the next state of the clock.
extern void Clock_SetNextState()
{
	switch (_clock_state)
	{
	    //case CLOCK_Calibrating:
	    case CLOCK_Running:
	    	_clock_state = CLOCK_Setting;
			MotionControl_SetState(MOTIONCONTROL_Configuring);
			Display_SetState(DISPLAY_SettingTime);
	    	break;

		default:
	    case CLOCK_Setting:
		case CLOCK_Alarm:
	    	_clock_state = CLOCK_Running;
	    	MotionControl_SetState(MOTIONCONTROL_Running);
			Display_SetState(DISPLAY_Normal);
	    	break;
	}
}

// Clock_GetState()
// ----------------
// Get the current clock state.
extern clock_State Clock_GetState()
{
	return _clock_state;
}

// Clock_Refresh()
// ---------------
// Update the display and robot position.
extern void Clock_Refresh()
{
	Display_Update();
	Robot_Calculate();
}

/* Private functions ---------------------------------------------------------*/

// Clock_OnSecond()
// ----------------
// One second interrupt handler.
static void Clock_OnSecond()
{
	int seconds = RTC_GetCounter();
	int second = seconds % 60;

	// if we are not in the process of setting the time...
	if (_clock_state == CLOCK_Running || _clock_state == CLOCK_Alarm)
	{
		// show the current time
		Display_SetTime(seconds);
	}

    // if the alarm is running play the sound every two seconds
	if (_clock_state == CLOCK_Alarm && (second % 2) == 1)
	{
		Sound_Play();
	}

	// update the robot position
	MotionControl_OnSecond(second);
}

// Clock_OnAlarm()
// ---------------
// Alarm interrupt handler.
static void Clock_OnAlarm()
{
	if (_clock_alarmed && _clock_state == CLOCK_Running)
	{
		_clock_state = CLOCK_Alarm;
		MotionControl_SetState(MOTIONCONTROL_Alarm);
	}
}
