#include "bootloader.h"
#include "boot_flash.h"
#include "boot_uart.h"
#include "boot_can.h"

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

void SetKey(void);
uint32_t ReadKey(void);
void ResetKey(void);
void Boot_SystemClock_Config(void);
void JumpToApplocation(void);

void Bootloader_UART_Receive_Page(Bootloader_t *bl);
void Bootloader_CAN_Receive_Page(Bootloader_t *bl);
  
void Bootloader_Init(void)
{	
	HAL_Init();	
	Boot_SystemClock_Config();
  MX_BOOT_UART_Init();
  MX_BOOT_CAN_Init();
}

HAL_StatusTypeDef res_hal;


void Bootloader_Task(Bootloader_t *bl)
{
  switch (bl->state)
  {
    case BL_STATE_INIT:
      // Проверяем ключ, чтобы понять запускать приложение или программирование
      if (ReadKey() == BL_KEY_APP_WRITTEN)
      {
        bl->state = BL_STATE_JUMP_TO_APP;
        break; // не инициализируем, а сразу прыгаем в приложение
      }
      else
      {
        bl->state = BL_STATE_IDLE;
      }
      
      // Инициализация периферии
      Bootloader_Init();
      bl->huart = &huart_boot;
      bl->hcan  = &hcan_boot;
      bl->addr = MAIN_APP_START_ADR;
      break;

    case BL_STATE_IDLE:
      // Ждем команды по UART или CAN
      if(bl->state != bl->prev_state)
      {
        static uint8_t Data[8] = {0};
        static uint32_t TxMail = {0};
        Data[0] = 0xFA;
        HAL_CAN_AddTxMessage(bl->hcan, &bl->TxHeader, Data, &TxMail);
        HAL_UART_Transmit()
      }
      if (Receive_FW_Command(bl) == 0xFF)   // функция обработки команд, возвращает 1 если ошибка
      {
        bl->state = BL_STATE_ERROR;
      }
      break;

    case BL_STATE_ERASE:
      ResetKey();
      if (FLASH_Erase_App() == HAL_OK)  // твоя функция стирания MAIN_APP_PAGE..NUM
      {
        bl->state = BL_STATE_IDLE;
      }
      else
      {
        bl->state = BL_STATE_ERROR;
      }
      break;

    case BL_STATE_RECEIVE_UART:
      // В этом состоянии мы просто принимаем страницу + CRC
      Bootloader_UART_Receive_Page(bl);
      break;
    case BL_STATE_RECEIVE_CAN:
      Bootloader_CAN_Receive_Page(bl);
      break;
      
    case BL_STATE_WRITE:
      if (FLASH_Write_Page(&bl->addr, bl->fw_buffer, bl->fw_len) == HAL_OK)  // запись блока во Flash
      {
        bl->state = BL_STATE_IDLE;  // ждём следующего блока
      }
      else
      {
        bl->state = BL_STATE_ERROR;
      }
      break;

    case BL_STATE_VERIFY:
      if (/*Verify_Flash_CRC(bl->fw_crc)*/0 == HAL_OK)
      {
        ResetKey();
        SetKey();  // отметка, что прошивка записана
        bl->state = BL_STATE_IDLE;
      }
      else
      {
        bl->state = BL_STATE_ERROR;
      }
      break;

    case BL_STATE_JUMP_TO_APP:
      JumpToApplocation();
      break;

    case BL_STATE_ERROR:
      // обработка ошибок (можно светодиод, повторная инициализация, лог по UART)
      Error_Handler();
      break;

    default:
      bl->state = BL_STATE_ERROR;
      break;
  }
  bl->prev_state = bl->state;
  
}

/**
 * @brief  Обработка команд прошивки по UART или CAN
 * @param  bl: указатель на структуру бутлоадера
 * @retval 0x00 - команда принята и обработана, 0xFF - ошибка
 */
uint8_t Receive_FW_Command(Bootloader_t *bl)
{
  BootloaderCommand_t cmd = 0;
  HAL_StatusTypeDef res = HAL_ERROR;

  // ---------------------------
  // Чтение команды по UART
  // ---------------------------
  res = HAL_UART_Receive(bl->huart, &cmd, 1, 10); // таймаут 10 ms
  if (res == HAL_OK)
  {
    switch(cmd)
    {
      case CMD_ERASE: // команда: стереть Flash
        bl->state = BL_STATE_ERASE;
        return 0x00;
      case CMD_START_RECEIVE: // команда: принять блок
        bl->state = BL_STATE_RECEIVE_UART;
        return 0x00;
      case CMD_WRITE: // команда: записать блок
        bl->state = BL_STATE_WRITE;
        return 0x00;      
      case CMD_VERIFY: // команда: проверка прошивки
        bl->state = BL_STATE_VERIFY;
        return 0x00;
      case CMD_GOTOAPP: // команда: прыжок в приложение
        bl->state = BL_STATE_JUMP_TO_APP;
        return 0x00;

      default:
        return 0xFF; // неизвестная команда
    }
  }

  // ---------------------------
  // Чтение команды по CAN
  // ---------------------------
  CAN_RxHeaderTypeDef canHeader;
  uint8_t canData[8];
  if (HAL_CAN_GetRxFifoFillLevel(bl->hcan, CAN_RX_FIFO0) > 0)
  {
    if (HAL_CAN_GetRxMessage(bl->hcan, CAN_RX_FIFO0, &canHeader, canData) == HAL_OK)
    {
      cmd = canData[0]; // предполагаем, что команда в первом байте
      switch(cmd)
      {
        case CMD_ERASE:
          bl->state = BL_STATE_ERASE;
          return 0x00;
        case CMD_START_RECEIVE:
          // запускаем прием первой страницы + crc, затем после приема всего массива ждем следующей комманды
          bl->state = BL_STATE_RECEIVE_CAN;
          return 0x00;        
        case CMD_WRITE: // команда: записать блок
          bl->state = BL_STATE_WRITE;
          return 0x00;           
        case CMD_VERIFY:
          bl->state = BL_STATE_VERIFY;
          return 0x00;
        case CMD_GOTOAPP:
          bl->state = BL_STATE_JUMP_TO_APP;
          return 0x00;
        
        default:
          return 0xFF;
      }
    }
  }

#ifdef TEST_CAN
	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";
  res_hal =	HAL_CAN_AddTxMessage(&hcan_boot, &TxHeader, asd, &TxMailBox); // add to mail for transmit
	HAL_Delay(1000);
#endif
  
  return 0x00; // если команды нет, ничего не делаем, остаёмся в BL_STATE_IDLE
}




// 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)
{	
	FLASH_EraseInitTypeDef EraseInitStruct;
	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();
	
}



void JumpToApplocation(void)
{
  // 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();
}



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

  /* Включаем внутренний генератор HSI */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_OFF; // без PLL
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
      Error_Handler();
  }

  /* Настройка шин AHB/APB */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; // HSI = 8 MHz
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;     // HCLK = 8 MHz
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;      // PCLK1 = 8 MHz
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;      // PCLK2 = 8 MHz
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
      Error_Handler();
  }
}

void Error_Handler(void)
{
  NVIC_SystemReset();
}