#include "bootloader.h"
#include "custom_receive_and_write.h"

extern FLASH_EraseInitTypeDef EraseInitStruct;

struct flag FLAGS;
struct Bootloader_Errors_Counters BOOTLOADER_ERRORS_COUNTERS;
struct Bootloader_Errors BOOTLOADER_ERRORS;

uint32_t app_current_add; // address for writing pages
int cnt_tim_reset = 0; // WD Tim counter


uint8_t ReceiveDataUART[4]; // buffer for CMD
uint8_t Data2Write[PAGE_SIZE*2]; // DMA buffer for application
uint8_t CAN_Data[8];


CAN_TxHeaderTypeDef TxHeader;
CAN_RxHeaderTypeDef RxHeader;
uint32_t TxMailBox = 0; // num of used mail


extern UART_HandleTypeDef huart_boot; // uart handler for boot uart
extern DMA_HandleTypeDef hdma_usart_boot_rx; // dma handler for boot uart
struct UARTSettings UARTSet; // settings for uart

void Bootloader_Init(void)
{	
	HAL_Init();	
	Boot_SystemClock_Config();
	MX_DMA_Init();
	//Если используется ЮСАРТ - инициализация
#ifdef __USART_H__
	
	UARTSet.UARTx = UART_BOOT;
	UARTSet.UART_Speed = UART_SPEED;
	UARTSet.GPIOx = UART_PORT;
	UARTSet.GPIO_PIN_RX = UART_PIN_RX;
	UARTSet.GPIO_PIN_TX = UART_PIN_TX;
	UARTSet.DMAChannel = DMA_UART_Channel;
	
	User_UART_Init(&huart_boot, &hdma_usart_boot_rx, &UARTSet);
#endif

	//Если используется SD-карта - инициализация
#ifdef __SDIO_H__
	BSP_SD_ITConfig();
#endif
	
#ifdef __CAN_H__	
  MX_CAN_Init();
#endif
	
	//Инициализация светодиодов
	MX_GPIO_Init();
	MX_TIM7_Init();
}

HAL_StatusTypeDef res_hal;		
void Bootloader_main(void) // main function, that defines bootloader behaviour
{
	/*
	// Настройка доступной переферии с помощью define
	Bootloader_Init();
	*/
	
  /* START APPLICATION */
	if (ReadKey() == BL_KEY_APP_WRITTEN)
	{		
		// jump to main app
		//Задаётся адрес программы со смещением от начала вектора прерываний
		uint32_t app_jump_adr;
		app_jump_adr=*((volatile uint32_t*)(MAIN_APP_START_ADR+4));
		void(*GoToApp)(void);
		
		//Деинициализация HAL
		HAL_DeInit();
		
		GoToApp = (void (*) (void)) app_jump_adr;
		//Перенос вектора прерываний на начало зашитой программы
		__disable_irq();
		__set_MSP(*((volatile uint32_t*)MAIN_APP_START_ADR));
		__enable_irq();
		//Переход к выполнению зашитой программы
		GoToApp();
	}
	else  /* START PROGRAMMING MCU */
	{
		/* MCU Configuration for bootloader-------------------------------------------*/
		/* Reset of all peripherals, Initializes the Flash BootloaderCMD and the Systick. */
		Bootloader_Init();
		FLAGS.InitOrWait = 0;
		
		// waif for commant for programming
		do{
			res_hal=HAL_UART_Receive_IT(&huart_boot, ReceiveDataUART, sizeof(ReceiveDataUART));
			if(res_hal!=HAL_OK){
				BOOTLOADER_ERRORS_COUNTERS.USART_RECEIVE++;
				BOOTLOADER_ERRORS.USART_RECEIVE=1;
			}
		}while(res_hal!=HAL_OK); // if usart err - try start receive again
		
		HAL_CAN_Start(&hcan);
		HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
		
CAN_FilterTypeDef canFilterConfig;
canFilterConfig.FilterBank = 0;
canFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
canFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
canFilterConfig.FilterIdHigh = 0x0000;
canFilterConfig.FilterIdLow = 0x0000;
canFilterConfig.FilterMaskIdHigh = 0x0000;
canFilterConfig.FilterMaskIdLow = 0x0000;
canFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
canFilterConfig.FilterActivation = ENABLE;
canFilterConfig.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan, &canFilterConfig);
		
//		FLAGS.StartInit=1;			
//		FLAGS.reInitMCU=1;
//		FLAGS.BootloaderCMD = 2;
		app_current_add = MAIN_APP_START_ADR;
//		HAL_CAN_Start(&hcan);
		/* Infinite loop */
		while (1)
		{					
//			HAL_Delay(10);
//			HAL_UART_Transmit(&huart_boot, (uint8_t *)"000000", 6, HAL_MAX_DELAY);
			
			// choose interface for programming
			if (FLAGS.StartInit)
			{
				
				
				if (FLAGS.reInitMCU) // if its reInit request - erase outdate app
				{
					GPIOB->ODR^=0x2000;
					res_hal=FLASH_Erase_App();
					if(res_hal!=HAL_OK)
					{
						FLAGS.StartInit=1;
						
						BOOTLOADER_ERRORS.FLASH_ERASE = 1;
						continue;
					}
					BOOTLOADER_ERRORS.FLASH_ERASE = 0;
					GPIOB->ODR^=0x2000;
					FLAGS.reInitMCU = 0;
				}
				FLAGS.StartInit = 0;
				
				HAL_UART_AbortReceive_IT(&huart_boot);
				HAL_CAN_Stop(&hcan);
				HAL_CAN_DeactivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
				
				switch ((int)FLAGS.BootloaderCMD) 
				{
					// UART
					#ifdef __USART_H__
					case 1:/* UART	*/
						FLAGS.InitBootPeriph = 1;
						// start receiving app.bin
						res_hal = HAL_UART_Receive_DMA(&huart_boot, Data2Write, PAGE_SIZE*2);
						if(res_hal!=HAL_OK)
						{
							FLAGS.StartInit=1;
							BOOTLOADER_ERRORS_COUNTERS.USART_RECEIVE++;
							BOOTLOADER_ERRORS.USART_RECEIVE = 1;
							break;
						}
						// code of writing app in flash in callbacks functions:
						// HAL_UART_RxHalfCpltCallback and HAL_UART_RxCpltCallback
						break;
					#endif
					
					#ifdef __CAN_H__
					// CAN
					case 2: /* CAN	*/ 
						// activate can and start receiving app.bin
						FLAGS.InitOrWait=1;
						res_hal = HAL_CAN_Start(&hcan);
						res_hal = HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_ERROR | CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE);
						// code of writing app in flash in callback functions:
						// HAL_CAN_RxFifo0MsgPendingCallback
						break;
					#endif
					
					// SD
					#ifdef __SDIO_H__
					case 3: /*  SD	*/ 
						if (FLAGS.SDCardIn==0) // wait for sd-card
						{
							FLAGS.StartInit=1; // if there is no card, exit programmator and go in again
							BOOTLOADER_ERRORS.SD_CARD_NOT_INSERTED = 1;
							//Ошибка SD карты. Начать приём новой cmd
							break;
						}
						HAL_Delay(100); // wait for inputting SD Card
						if (FLAGS.SDCardIn==0)
						{
							FLAGS.StartInit=1;
							BOOTLOADER_ERRORS.SD_CARD_NOT_INSERTED = 1;
							break;
						}
						
						FRESULT res_fresult;
						SD_Programming(&res_fresult, &res_hal); // programming from sd card
						// res_fresual - status for sd card read // res_hal - status for flash write
						//Если всё удачно - выполнить код ниже. Если нет - StartInit = 1, break
						if(res_fresult!=FR_OK || res_hal!=HAL_OK)
						{
							FLAGS.StartInit=1;
							
							if(res_fresult!=FR_OK){
								BOOTLOADER_ERRORS_COUNTERS.SD_CARD_READ++;
								BOOTLOADER_ERRORS.SD_CARD_READ = 1;
							}
							if(res_hal!=HAL_OK){
								BOOTLOADER_ERRORS_COUNTERS.FLASH_WRITE++;
								BOOTLOADER_ERRORS.FLASH_WRITE = 1;
							}		
							break;
						}
						// write Key: application in Flash, and reset MCU
						{
							ResetKey();
							SetKey();
							NVIC_SystemReset();
						}// reset mcu
						break;
					#endif
					
					default: // if command is incorrect - wait CMD
						BOOTLOADER_ERRORS_COUNTERS.CMD_INVALID++; // uncorrect command
						BOOTLOADER_ERRORS.CMD_INVALID=1;
						do
						{
							res_hal=HAL_UART_Receive_IT(&huart_boot, ReceiveDataUART, sizeof(ReceiveDataUART));
							if(res_hal!=HAL_OK){
								BOOTLOADER_ERRORS_COUNTERS.USART_RECEIVE++; // err when initialize uart
								BOOTLOADER_ERRORS.USART_RECEIVE=1;}
						}while(res_hal!=HAL_OK);
						HAL_CAN_Start(&hcan);
						HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
				}
			}
			
//	TxHeader.StdId = 0x200; // ID OF MESSAGE
//	TxHeader.ExtId = 0; // STANDART FRAME (NOT EXTENTED)
//	TxHeader.RTR = CAN_RTR_DATA; // TRANSMIT DATA OR 
//	TxHeader.IDE = CAN_ID_STD; // STANDART FRAME
//	TxHeader.DLC = 8; // DATA SIZE
//	TxHeader.TransmitGlobalTime = DISABLE; //THIS MODE IS NOT USED, SO DISABLE
//	uint8_t asd[8] = "ABCDEFGL";
//	while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0); // wait for free mail box
//		res_hal =	HAL_CAN_AddTxMessage(&hcan, &TxHeader, asd, &TxMailBox); // add to mail for transmit
//	HAL_Delay(1000);
			
			
		}
	}
}




void SD_Programming(FRESULT *res_fresult, HAL_StatusTypeDef *res_hal)
{					
	// init peripth
	MX_FATFS_Init();
	MX_SDIO_SD_Init();
	FIL MFile;
	int SizeApp;
	int AppCount;
	
	// mount disk
	uint8_t MOUNT=1;	
	*res_fresult = f_mount(&SDFatFS, (TCHAR const*)SDPath, 1);
	if(*res_fresult != FR_OK) return;

	// name of the application file
	static char path[8] = "app.bin";
	path[7] = '\0';
	
	
	// OPEN AND READ
	*res_fresult = f_open(&MFile, (const TCHAR*)path, FA_READ);	
	if (*res_fresult == FR_OK)
	{
		SizeApp = MFile.fsize;
		AppCount = 0; // counter of written bytes
		
		
		unsigned int bytesRead;
		// reading and writing two pages
		while(SizeApp - AppCount >= PAGE_SIZE*2) // read while count of rest bytes more than size of two pages
		{
			*res_fresult = f_read(&MFile, Data2Write, PAGE_SIZE*2, &bytesRead);
			
			if(*res_fresult != FR_OK) return;
			
			AppCount += PAGE_SIZE*2;
			
			*res_hal = FLASH_Write_Page(&app_current_add, Data2Write, PAGE_SIZE*2);
			
			if(*res_hal != HAL_OK) return;
			
			GPIOB->ODR^=0x2000; // indicate written two pages
		}
		// reading and writing rest bytes (less than two pages)
		if(SizeApp != AppCount)
		{
			int NumOfLastBytes = SizeApp - AppCount;
			
			*res_fresult = f_read(&MFile, Data2Write, NumOfLastBytes, &bytesRead);
			
			if(*res_fresult != FR_OK) return;
			
			AppCount += PAGE_SIZE*2;
			
			*res_hal = FLASH_Write_Page(&app_current_add, Data2Write, NumOfLastBytes);
			
			if(*res_hal != HAL_OK) return;
			
			GPIOB->ODR^=0x2000;
		}
		
		*res_fresult = f_close(&MFile); // indicate written two pages
	}
}

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) // writing first half of dma buffer (1 page)
{	
	if (huart->Instance == UART_BOOT)
	{
		if (FLAGS.InitOrWait)
		{		
			HAL_StatusTypeDef res_hal;
			
			if (FLAGS.InitBootPeriph) // if its first received page
			{
				//HAL_TIM_Base_Start_IT(&htim_boot); 	// start "wd" timer
				FLAGS.InitBootPeriph = 0;						// 5 sec silent on RX - mcu reset
			}
			
			res_hal = FLASH_Write_Page(&app_current_add, Data2Write, PAGE_SIZE);
			if (res_hal != HAL_OK)
			{
				BOOTLOADER_ERRORS_COUNTERS.FLASH_WRITE++; // err when initialize uart
				BOOTLOADER_ERRORS.FLASH_WRITE=1;
			}
	//		HAL_UART_DMAStop(&huart_boot);
	//		HAL_UART_AbortReceive_IT(&huart_boot);
	//		HAL_UART_AbortReceive(&huart_boot);
			
			FLAGS.HalfOfWrite = 1; // switch unwritten half of DMA buffer
			
			cnt_tim_reset = 0; // upd WD Tim
			GPIOB->ODR^=0x2000; // indicate written page	
		}
	}
}	

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)  // writing second half of dma buffer (1 page)
{
if (huart->Instance == UART_BOOT)
{
	if (FLAGS.InitOrWait == 0) // if its wait mode (not writting app)
	{
		// set Init mode, start WDTim after receiving first page
		Check_CMD_USART(ReceiveDataUART); // check received uart data
		FLAGS.InitOrWait = 1;
		FLAGS.StartInit = 1;
		FLAGS.InitBootPeriph = 1;
		app_current_add = MAIN_APP_START_ADR; // set adress for app		
	}
	else
	{	
		HAL_StatusTypeDef res_hal;
		res_hal = FLASH_Write_Page(&app_current_add, Data2Write+PAGE_SIZE, PAGE_SIZE);
		if (res_hal != HAL_OK)
		{
			BOOTLOADER_ERRORS_COUNTERS.FLASH_WRITE++; // err when initialize uart
			BOOTLOADER_ERRORS.FLASH_WRITE=1;
		}
		
		FLAGS.HalfOfWrite = 0; // switch unwritten half of DMA buffer
		
		cnt_tim_reset = 0; // upd WD Tim
		GPIOB->ODR^=0x2000; // indicate written page
	}
}
}


uint32_t temp_app_cur_add;
int app_size;
/*void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
	
	if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, CAN_Data) == HAL_OK)
	{	
#ifdef _CAN_PUSH_ID_TO_ADDR_		
		if(FLAGS.InitOrWait)
		{	// if its 1 - Init: writting app.bin in flash
			temp_app_cur_add = app_current_add+(RxHeader.ExtId&(0x0000FFFF));
		
			FLASH_Write_Page(&temp_app_cur_add, CAN_Data, 8);
			app_size-=8; // decrease app_size 
			if (app_size<=0) // when its gone - reset system and go to the app
			{
				__ASM("");
				NVIC_SystemReset();
			}
		}
		else
	{	// if its 0 - Wait: parsing address to writting and size of app.bin
		app_current_add = Data2Write[0]<<24 | Data2Write[1]<<16 | Data2Write[2]<<8 | Data2Write[3];
		app_size = Data2Write[4]<<24 | Data2Write[5]<<16 | Data2Write[6]<<8 | Data2Write[7];
		FLAGS.InitOrWait = 1; // switch to firmware (init)
	}
#endif
		if(FLAGS.InitOrWait)
		{
			if (FLAGS.InitBootPeriph) // if its first received page
			{
				HAL_TIM_Base_Start_IT(&htim_boot); 	// start "wd" timer
				FLAGS.InitBootPeriph = 0;						// 5 sec silent on RX - mcu reset
			}
			FLASH_Write_Page(&app_current_add, CAN_Data, 8);
			cnt_tim_reset=0;
		}
		else
		{

		app_current_add = MAIN_APP_START_ADR; // set adress for app		
			Check_CMD_USART(CAN_Data);
			FLAGS.InitBootPeriph=1;		
			FLAGS.InitOrWait = 1;
			FLAGS.StartInit = 1;
		}
	}
}

*/
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{
	uint32_t er;
	er = HAL_CAN_GetError(hcan);
		__ASM("");	
}
void Bootloader_TIM_Handler(void) // reset mcu after writing application is done
{ // add this to the TIM handler that is used (TIMx is seting up in project_setup.h)
	HAL_StatusTypeDef res_fresult;
	cnt_tim_reset++;
	if (cnt_tim_reset > 5) // writing is done if there is silence on Rx for 5 second
	{
		if(FLAGS.BootloaderCMD==1)
		{
			FLAGS.InitMCUReady = 1;
			ResetKey();
			SetKey(); // write key for going to application
			int NumOfLastBytes = (PAGE_SIZE - DMA1_Channel3->CNDTR%PAGE_SIZE); // read size of new data in dma buffer
			if (NumOfLastBytes != 0)
			{			
				res_fresult = FLASH_Write_Page(&app_current_add, Data2Write+PAGE_SIZE*FLAGS.HalfOfWrite, NumOfLastBytes); // writing last data
			}
			NVIC_SystemReset(); // reset mcu
		}
		else if(FLAGS.BootloaderCMD==2)
		{
			FLAGS.InitMCUReady = 1;
			ResetKey();
			SetKey();
			NVIC_SystemReset();
		}
	}
}


void Check_CMD_USART(uint8_t *DataUART) // choose CMD (interface)
{	// 4 byte: 0xFF - erase app, else - just write app
	//USART
	if ((DataUART[0]|
				DataUART[1]<<8|
				DataUART[2]<<16)	== 0xFFFFFF)
	{
		FLAGS.BootloaderCMD = 1;
	}
	//CAN
	else if ((DataUART[0]|
				DataUART[1]<<8|
				DataUART[2]<<16)	== 0xAAAFFF)
	{
		FLAGS.BootloaderCMD = 2;
	}
	//SDIO
	else if ((DataUART[0]|
				DataUART[1]<<8|
				DataUART[2]<<16)	== 0xAAFAFF)
	{
		FLAGS.BootloaderCMD = 3;
		BSP_SD_DetectIT();
	}
	if (DataUART[3]== 0xFF)
	{
		FLAGS.reInitMCU = 1;
	}
	else
	{
		FLAGS.reInitMCU = 0;
	}
		
}




// reset/set key function
void SetKey(void)
{	
	HAL_FLASH_Unlock();
	
	HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, BOOTLOADER_KEY_ADR, BL_KEY_APP_WRITTEN);
	
	HAL_FLASH_Lock();	
}

uint32_t ReadKey(void)
{	
	return (*(__IO uint32_t*)BOOTLOADER_KEY_ADR);
}

void ResetKey(void)
{	
	HAL_FLASH_Unlock();
	uint32_t PageError = 0x00;
	
	EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;// erase pages
	EraseInitStruct.PageAddress = BOOTLOADER_KEY_ADR;   //address
	EraseInitStruct.NbPages     = 0x01;// num of erased  pages
	
	HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
	HAL_FLASH_Lock();
	
}


HAL_StatusTypeDef BSP_SD_ITConfig(void)
{ 
  /* Code to be updated by the user or replaced by one from the FW pack (in a stmxxxx_sd.c file) */
  GPIO_InitTypeDef gpio_init_structure;

  /* Configure Interrupt mode for SD detection pin */  
  gpio_init_structure.Pin = SDIO_SDCard_In_PIN;
  gpio_init_structure.Pull = GPIO_PULLUP;
  gpio_init_structure.Speed = GPIO_SPEED_HIGH;
  gpio_init_structure.Mode = GPIO_MODE_IT_RISING_FALLING;
  HAL_GPIO_Init(SDIO_SDCard_In_PORT, &gpio_init_structure);

  /* Enable and set SD detect EXTI Interrupt to the lowest priority */
  HAL_NVIC_SetPriority((SDIO_SDCard_In_IRQn), 0x00, 0x00);
  HAL_NVIC_EnableIRQ((SDIO_SDCard_In_IRQn));

  return HAL_OK;
//  return (uint8_t)0;
}


void BSP_SD_DetectIT(void)
{ // add this to the EXTI handler that is used (EXTIx (GPIO_PIN_x) is seting up in project_setup.h)
	
	if(SDIO_SDCard_In_PORT->IDR&SDIO_SDCard_In_PIN)
		FLAGS.SDCardIn = 0;
	else
		FLAGS.SDCardIn = 1;
}


void Boot_SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  HAL_RCC_MCOConfig(RCC_MCO, RCC_MCO1SOURCE_PLLCLK, RCC_MCODIV_1);
}