/**
 * \file
 *
 * \brief Sleep Walking Hands-on
 * after the application initialization function, the user will use
 * these buttons to change the state of the sequential state machine
 * implemented in the code as follow:
 * After having set the Clock
 * •   CS0 used to wake up the SAM4L device from the Sleepwalking™ mode
 * •   PB0 used to go back in Sleepwalking™.
 * The 7segments LCD display is used to display the state if the state
 *  machine is in Sleepwalking or in Active State
 * At power up, the application jumps directly in Sleepwalking state
 * with the Peripheral Event System enabled, allowing the
 * interconnection between peripherals. 2 different configurations
 * (the last one is optional) are implemented in the code:
 * •  1 generator and 1 user:
 * •	AST as generator
 * •	CATB as user
 * •  1 generator and 2 users (optional)
 * •	AST as generator
 * •	CATB as user
 * •	ADCIFE as user
 */

/**
 * \mainpage User Application template doxygen documentation
 *
 * \par Empty user application template
 *
 * Bare minimum empty user application template
 *
 * \par Content
 *
 * -# Include the ASF header files (through asf.h)
 * -# Minimal main function that starts with a call to board_init()
 * -# "Insert application code here" comment
 *
 */

/*
 * Include header files for all drivers that have been imported from
 * Atmel Software Framework (ASF).
 */
/***********************************************************************/
/*                          Include                                    */
/***********************************************************************/
#include <asf.h>

/* Include sleepwalking_example header */
#include "sleepwalking_appnote.h"

extern volatile char seq_state;

volatile uint8_t event_string[1] = "0";

volatile uint8_t data[4] = "0000";
volatile uint32_t counter;
/* Initial values of the CALV register used for the AST calendar Mode */
struct ast_calendar calendar = {
	.FIELD.sec = 0,
	.FIELD.min = 0,
	.FIELD.hour = 0
};
volatile bool button_state_prev;
volatile bool button_press_hold_detect = false;

volatile bool button_press_long_hold_detect = false;
volatile uint32_t counter2sleep = 0;

volatile uint32_t state = SET_MINUTE;

volatile uint8_t data_sec[2] = "00";
volatile uint8_t data_setm1[7] = "Set Min";
volatile uint8_t data_seth1[7] = "Set Hr ";
volatile uint8_t data_clr[7] = "       ";


//extern struct ast_calendar calendar; 

/***********************************************************************/
/*                          Interrupt Handlers                         */
/***********************************************************************/

/**
 * PDCA transfer interrupt callback.
 */
static void pdca_tranfer_done(enum pdca_channel_status status)
{
	uint8_t lcd_num[3];

	/* Get PDCA channel status and check if PDCA transfer is completed */
	if (status == PDCA_CH_TRANSFER_COMPLETED) {
		/* Reload PDCA data transfer */
		pdca_channel_write_reload(PEVC_ID_USER_PDCA_0,
				(void *)event_string,
				sizeof(event_string));
	}

	/* Get the calendar new value from the AST.CALV register*/
	calendar = ast_read_calendar_value(AST);

	/* Secondes */
	sprintf((char *)lcd_num, "%2d", (int)calendar.FIELD.sec);
	lcdca_write_packet(LCDCA_TDG_14SEG4COM, 16, (uint8_t const *)&lcd_num,
			C42364A_WIDTH_14SEG_4C, LCDCA_CMCFG_DREV_LEFT);
	/* Minutes */
	sprintf((char *)lcd_num, "%2d", (int)calendar.FIELD.min);
	lcdca_write_packet(LCDCA_TDG_7SEG4COM, 6, (uint8_t const *)&lcd_num,
			C42364A_WIDTH_7SEG_4C, LCDCA_CMCFG_DREV_LEFT);
	/* Hour */
	sprintf((char *)lcd_num, "%2d", (int)calendar.FIELD.hour);
	lcdca_write_packet(LCDCA_TDG_7SEG4COM, 10, (uint8_t const *)&lcd_num,
			C42364A_WIDTH_7SEG_4C, LCDCA_CMCFG_DREV_LEFT);
	/* Add missing zero */
	if (calendar.FIELD.min <= 9) {
		lcdca_write_packet(LCDCA_TDG_7SEG4COM, 6, (uint8_t const *)"0",
				C42364A_WIDTH_7SEG_4C, LCDCA_CMCFG_DREV_LEFT);
	}

	if (calendar.FIELD.hour <= 9) {
		lcdca_write_packet(LCDCA_TDG_7SEG4COM, 10, (uint8_t const *)"0",
				C42364A_WIDTH_7SEG_4C, LCDCA_CMCFG_DREV_LEFT);
	}

	if (calendar.FIELD.sec <= 9) {
		lcdca_write_packet(LCDCA_TDG_14SEG4COM, 16,
				(uint8_t const *)"0",
				C42364A_WIDTH_7SEG_4C, LCDCA_CMCFG_DREV_LEFT);
	}

	lcdca_toggle_pixel(C42364A_ICON_COLON); /* toggle colon */
	lcdca_set_pixel(C42364A_ICON_ARM);
}

/***********************************************************************/
/*                          Local Functions                            */
/***********************************************************************/

/**
 * Function used to switch the main clock from RCFAST
 * to PLL0 using OSC0 oscillator
 */
void switch_to_full_speed_clock(void)
{
	/* Disable the power scaling feature */
	bpm_power_scaling_cpu(BPM, BPM_PS_0);

	/* Init the sysclock: OSC0 as PLL clock source */
	osc_enable(OSC_ID_OSC0);
	/* wait while the OSC has not started */
	osc_wait_ready(OSC_ID_OSC0);

	/*
	 * Switch to PLL0 running at 96 MHz. Use OSC0 as the
	 * source
	 */
	struct pll_config pcfg;
	pll_config_init(&pcfg, PLL_SRC_OSC0, 1, (48000000UL / BOARD_OSC0_HZ));//can be replaced by CONFIG_PLL0_MUL when conf_board is updated
	pll_enable(&pcfg, 0);
	pll_wait_for_lock(0);

	/* MAIN Clock selection */
	/* PLL as main clock source */
	sysclk_set_source(PM_MCCTRL_MCSEL(2));

	/* Disable the RCFAST oscillator */
	osc_disable(OSC_ID_RCFAST);
}

/**
 * Set and display hour and minutes
 */
static void set_hour_minute(void)
{
	switch (state) {
		case SET_MINUTE:
		lcdca_write_packet(LCDCA_TDG_14SEG4COM, 36, data_setm1, 7, LCDCA_CMCFG_DREV_LEFT);
		if (button_press_hold_detect != false) {
			state = SET_HOUR;
		}

		if (button_state_prev && !ioport_get_pin_level(GPIO_PUSH_BUTTON_0)) {
			/*reset seconds to 00 */
			data_sec[0] = 0x30;
			event_string[0] = 0x30;
			if (data[3] >= 0x39) {
				data[3] = 0x30;
				if (data[2] >= 0x35) {
					data[2] = 0x30;
					} else {
					data[2]++;
				}
				} else {
				data[3]++;
			}

			lcdca_write_packet(LCDCA_TDG_7SEG4COM, C42364A_FIRST_7SEG_4C, data, 4, LCDCA_CMCFG_DREV_LEFT);
		}

		break;

		case SET_HOUR:
		lcdca_write_packet(LCDCA_TDG_14SEG4COM, 36, data_seth1, 7, LCDCA_CMCFG_DREV_LEFT);
		if (button_press_hold_detect != false) {
			state = NORMAL;
			lcdca_write_packet(LCDCA_TDG_14SEG4COM, 36, data_clr, 7, LCDCA_CMCFG_DREV_LEFT);
		}

		if (button_state_prev && !ioport_get_pin_level(GPIO_PUSH_BUTTON_0)) {
			/*reset seconds to 00 */
			data_sec[0] = 0x30;
			event_string[0] = 0x30;

			if ((data[1] >= 0x39) || (data[1] >= 0x33 && data[0] == 0x32)) {
				data[1] = 0x30;
				if (data[0] >= 0x32) {
					data[0] = 0x30;
					} else {
					data[0]++;
				}
				} else {
				data[1]++;
			}

			lcdca_write_packet(LCDCA_TDG_7SEG4COM, C42364A_FIRST_7SEG_4C, data, 4, LCDCA_CMCFG_DREV_LEFT);
		}

		break;

		case NORMAL:
		if (button_press_hold_detect != false) {
			state = SET_MINUTE;
		}

		if (button_press_long_hold_detect) {
			state = LOW_POWER;
		}

		break;

		case LOW_POWER:
		lcdca_set_pixel(C42364A_ICON_WLESS);
		/* Enter in sleep mode */

		/* no exit from this state except RESET */
		sleepmgr_enter_sleep();

		break;

		default:             /*do nothing*/
		break;
	}

	if (!ioport_get_pin_level(GPIO_PUSH_BUTTON_0)) {
		if (button_state_prev == ioport_get_pin_level(GPIO_PUSH_BUTTON_0)) {
			if (counter >= 1500) {
				button_press_hold_detect = true;
				counter = 0;
				} else {
				counter++;
				button_press_hold_detect = false;
			}

			if (counter2sleep >= 5000) {
				button_press_long_hold_detect = true;
				} else {
				counter2sleep++;
			}
		}
		} else {
		counter = 0;
		counter2sleep = 0;
		button_press_hold_detect = false;
	}

	/* store the current button status */
	button_state_prev = ioport_get_pin_level(GPIO_PUSH_BUTTON_0);
	delay_us(1000);
}

/***********************************************************************/
/*                          External Functions                         */
/***********************************************************************/


/**
 * Function used to switch the main clock from PLL0 using OSC0
 * to RCFAST oscillator
 */
void switch_to_slower_clock(void)
{
	/*  init the sysclock: FASTRC4/8/12 MHz as main clock  */
	/* 12MHz selected range. */
	osc_enable(OSC_ID_RCFAST);
	while (!(osc_is_ready(OSC_ID_RCFAST)));

	/*  MAIN Clock selection  */
	/* FastRC as main clock source */
	sysclk_set_source(PM_MCCTRL_MCSEL(5));

	/* Disable the previous clock setting if needed */
	osc_disable(OSC_ID_OSC0);
	pll_disable(0);
}


/**
 * Initialize the Peripheral Event Controller for PDCA.
 */
void PDCA_PEVC_init(void)
{
	/* struct events_conf    events_config; */
	struct events_ch_conf ch_config;

	/* Initialize event module */
	events_enable();

	/* Peripheral Event system Clock enable at peripheral level */
	sysclk_enable_peripheral_clock(PEVC_ADDR);

	/*
	 * Configure an event channel
	 * - AST periodic event 0 --- Generator
	 * - PDCA       --- User
	 */
	events_ch_get_config_defaults(&ch_config);
	ch_config.channel_id = PEVC_ID_USER_PDCA_0;
	ch_config.generator_id = PEVC_ID_GEN_AST_2;
	ch_config.shaper_enable = true;
	events_ch_configure(&ch_config);

	/* Enable the channel */
	events_ch_enable(PEVC_ID_USER_PDCA_0);
}

/**
 * Initialize the LCDA for the example.
 */
void init_lcda(void)
{
	struct lcdca_config lcdca_cfg;
	/* Automated display configuration */
	struct lcdca_automated_char_config automated_char_cfg;
	/*Circular shift configuration*/
	struct lcdca_circular_shift_config cs_cfg;

	lcdca_clk_init();
	lcdca_cfg.port_mask = C42364A_PORT_MASK;
	lcdca_cfg.x_bias = false;
	lcdca_cfg.lp_wave = true;
	lcdca_cfg.duty_type = 0;
	lcdca_cfg.lcd_pres = false;
	lcdca_cfg.lcd_clkdiv = 3;
	lcdca_cfg.fc1 = 2;
	lcdca_cfg.contrast = LCD_CONTRAST_LEVEL;
	lcdca_set_config(&lcdca_cfg);
	lcdca_enable();
	lcdca_enable_timer(LCDCA_TIMER_FC1);

	/* Turn on LCD back light */
	ioport_set_pin_level(LCD_BL_GPIO, IOPORT_PIN_LEVEL_HIGH);

	/* Display some texts and icon on segment LCD */
	lcdca_set_pixel(C42364A_ICON_ARM);
	lcdca_set_pixel(C42364A_ICON_COLON);

	/* Autonomous segment animation of 7-pixels area of the LCD */
	cs_cfg.lcd_csr_timer = LCDCA_TIMER_FC1;
	cs_cfg.lcd_csr_dir = LCDCA_CSR_RIGHT;
	cs_cfg.size = 7;    /* Total 7-pixels */
	cs_cfg.data = 0x03; /* Display 2 pixel at one time */
	lcdca_circular_shift_set_config(&cs_cfg);
	lcdca_circular_shift_enable();

	/* Automated sequential character string display */
	automated_char_cfg.automated_mode = LCDCA_AUTOMATED_MODE_SEQUENTIAL;
	automated_char_cfg.automated_timer = LCDCA_TIMER_FC1;
	automated_char_cfg.lcd_tdg = LCDCA_TDG_14SEG4COM;
	automated_char_cfg.stseg = 12 /*FIRST_7SEG_4C*/;
	automated_char_cfg.dign = 1;
	automated_char_cfg.steps = 0;
	automated_char_cfg.dir_reverse = LCDCA_AUTOMATED_DIR_REVERSE;
	lcdca_automated_char_set_config(&automated_char_cfg);
	LCDCA->LCDCA_ACMCFG |= LCDCA_ACMCFG_EN;
}

/**
 * Initialize the PDCA transfer for the example.
 */
void init_pdca(void)
{
	/* PDCA channel options */
	static const pdca_channel_config_t pdca_tx_configs = {
		.addr = (void *)event_string,
		.pid = 37,
		.size = sizeof(event_string),
		.r_addr = 0,
		.r_size = 0,
		.ring = false,
		.etrig = true,
		.transfer_size = PDCA_MR_SIZE_BYTE
	};

	/* Enable PDCA module */
	pdca_enable(PDCA);

	/* Init PDCA channel with the pdca_options.*/
	pdca_channel_set_config(PEVC_ID_USER_PDCA_0, &pdca_tx_configs);

	/* Set callback for PDCA channel */
	pdca_channel_set_callback(PEVC_ID_USER_PDCA_0, pdca_tranfer_done,
			PDCA_0_IRQn, 1, PDCA_IER_TRC | PDCA_IER_TERR);

	/* Enable PDCA channel */
	pdca_channel_enable(PEVC_ID_USER_PDCA_0);
}

/**
 * Set the clock time.
 */
void set_clock_time(void)
{
	/* Minutes */
	lcdca_write_packet(LCDCA_TDG_7SEG4COM, 6, (uint8_t const *)"00",
			C42364A_WIDTH_7SEG_4C, LCDCA_CMCFG_DREV_LEFT);
	/* Hour */
	lcdca_write_packet(LCDCA_TDG_7SEG4COM, 10, (uint8_t const *)"00",
			C42364A_WIDTH_7SEG_4C, LCDCA_CMCFG_DREV_LEFT);

	/* Change the inital clock time parameter (hour and minutes) */
	while (state != NORMAL) {
		set_hour_minute();
	}

	/* Update the Calendar value in the CALV register bitfield */
	calendar.FIELD.hour = (data[1] - 0x30) + ((data[0] - 0x30) * 10);
	calendar.FIELD.min  = (data[3] - 0x30) + ((data[2] - 0x30) * 10);
	calendar.FIELD.sec = 0;
}

/**
 * Black_Box App init.
 */
void black_box_app_init(void)
{
	init_lcda();
	init_pdca();
	set_clock_time();
	PDCA_PEVC_init();
}

/**
* Black_Box prepare_to_sleepwalking
*/
void black_box_prepare_to_sleepwalking(void)
{
	/* Disable PCDA to reduce consumption */
	//Wait for any previous event to complete
	while(events_ch_is_ready(PEVC_ID_USER_PDCA_0)==0);
	events_ch_disable(PEVC_ID_USER_PDCA_0);
	pdca_disable(PDCA);
	
	/* disable LCD & back light to reduce consumption */
	lcdca_disable();
	sysclk_disable_peripheral_clock(LCDCA);
	ioport_set_pin_level(LCD_BL_GPIO, IOPORT_PIN_LEVEL_LOW);
}

/**
* Black_Box prepare_to_active
*/
void black_box_prepare_to_active(void)
{
	/* Enable PCDA used to manage transfer data to the LCD */
	pdca_enable(PDCA);
	events_ch_enable(PEVC_ID_USER_PDCA_0);

	/* Enable LCD back light to display the time*/
	ioport_set_pin_level(LCD_BL_GPIO,IOPORT_PIN_LEVEL_HIGH);

	/* initialize the LCD controller */
	init_lcda();
}


