/******************************************************************************
 * @file    bootloader.c
 * @brief   Бутлоадер STM32 — реализован как конечный автомат (state machine).
 *
 * @details
 *          Логика работы:
 *          - Структура Bootloader_t содержит текущее состояние и ошибки.
 *          - После сброса проверяются предыдущие ошибки.
 *          - Проверяется BOOT KEY, чтобы определить: запускать основное приложение или оставаться в бутлоадере.
 *          - Основной цикл — state machine:
 *              INIT        — проверка ключа, инициализация периферии
 *              IDLE        — ожидание команд по UART/CAN
 *              RECEIVE_UART/RECEIVE_CAN — приём страницы прошивки
 *              WRITE       — запись страницы во Flash
 *              ERASE       — стирание приложения
 *              JUMP_TO_APP — проверка прошивки и переход к приложению
 *              JUMP_TO_BOOT— возврат в бутлоадер
 *              RESET       — программный сброс
 *              ERROR       — обработка ошибок и уведомление внешнего интерфейса
 *          - Команды прошивки (BootloaderCommand_t) обрабатываются через Receive_FW_Command().
 *          - Проверка целостности данных осуществляется через CRC32.
 *
 *          Подключение бутлоадера в основном приложении:
 *          0) Подключить boot_jump.h и boot_jump.c для взаимодействия с бутлоадером:
 *             @code  #include "boot_jump.h"  @endcode
 *          1) В начале main() вызвать App_Init(), чтобы установить VTOR на
 *             начало приложения:
 *             @code  App_Init();             @endcode
 *          2) Для перехода в бутлоадер (например, при ошибке или обновлении):
 *             @code  JumpToBootloader();     @endcode
 ******************************************************************************/
#include "bootloader.h"
#include "boot_gpio.h"
#include "boot_flash.h"
#include "boot_uart.h"
#include "boot_can.h"
#include "boot_jump.h"

// -----------------------------------------------------------------------------
// Глобальные переменные для HAL-периферии
// -----------------------------------------------------------------------------
HAL_StatusTypeDef res_hal;
uint32_t led_err_lasttick = 0;

// -----------------------------------------------------------------------------
// Локальные (static) функции
// -----------------------------------------------------------------------------
static uint8_t Receive_FW_Command(Bootloader_t *bl);

/**
 * @brief  Проверка после сброса MCU.
 *         Определяет причину предыдущего сброса, проверяет ошибки и при необходимости
 *         выставляет соответствующие биты в структуре ошибок бутлоадера.
 */
void Bootloader_StartCheck(Bootloader_t *bl)
{	
  uint32_t ErrCodeBoot = 0;
  uint32_t ErrCntBoot  = 0;
  
  // Проверка watchdog reset (IWDGRSTF или WWDGRSTF в RCC->CSR)
  if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) || __HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST))
  {
    //SaveErrorCode(0x0D0D);
    __HAL_RCC_CLEAR_RESET_FLAGS();      // Очистить флаги сброса, чтобы не повторялось
  }
  
  // Чтение сохранённого кода ошибки и количества сбоев
  ErrCodeBoot = GetErrorCode();
  ErrCntBoot  = GetErrorCnt();
  // Если ошибок было больше 5, фиксируем тип ошибки и уходим в бутлоадер
  // Данные ошибки фиксируются только в прерываниях бутлоадера. Hardfault в прерывании приложения не считается
  if(ErrCntBoot > 5)
  {
    ClearErrorCode();
    if(ErrCodeBoot == 0xDEAD)  // HardFault
    {
      ResetKey();
      bl->error.bit.hardfault_cycle = 1;
      bl->state = BL_STATE_ERROR;
    }
    else if(ErrCodeBoot == 0xBEEF)  // MemManage
    {
      ResetKey();
      bl->error.bit.memmanage_cycle = 1;
      bl->state = BL_STATE_ERROR;
    }
    /*else if(ErrCodeBoot == 0x0D0D) пока хз надо ли
    {
      ResetKey();
      bl->error.bit.watchdog_reset = 1;   // Добавь бит в структуру BootloaderError_
      bl->state = BL_STATE_ERROR;
    }*/
  }  
}


/**
 * @brief  Инициализация периферии бутлоадера (UART, CAN, системный такт).
 *         Привязывает дескрипторы HAL к структуре бутлоадера и задаёт
 *         начальный адрес приложения.
 */
void Bootloader_Init(Bootloader_t *bl)
{	
  HAL_Init();	
  
  Boot_SystemClock_Config();
  MX_BOOT_UART_Init();
  MX_BOOT_CAN_Init();
 
  // Привязка дескрипторов к структуре бутлоадера
  bl->huart = &huart_boot;
  bl->hcan  = &hcan_boot;
  bl->TxHeader.DLC = 8;
  bl->TxHeader.StdId = 123;
  bl->addr = MAIN_APP_START_ADR;  // адрес начала приложения
}


/**
 * @brief  Основной цикл работы бутлоадера (машина состояний).
 *         Обрабатывает состояния INIT, IDLE, RECEIVE, WRITE, ERASE, JUMP и ERROR.
 */
void Bootloader_Task(Bootloader_t *bl)
{
  int receive_uart_flag;

  switch (bl->state)
  {
    case BL_STATE_INIT:
      /*
       * Состояние инициализации.
       * - включаем индикацию (LED),
       * - проверяем ошибки,
       * - читаем "ключ" (метку, что приложение уже записано).
       * Если ключ установлен -> сразу переход в приложение.
       * Иначе -> переходим в режим ожидания команд от хоста (IDLE),
       *   инициализируем интерфейсы (CAN/UART, CRC и т.д.).
       */
      bl->prev_state = bl->state;
      MX_BOOT_GPIO_Init();
      Bootloader_StartCheck(bl);

      if ((ReadKey() == BL_KEY_APP_WRITTEN))
      {
        bl->state = BL_STATE_JUMP_TO_APP; 
      }
      else
      {
        bl->state = BL_STATE_IDLE;
        Bootloader_Init(bl);
      }      
      break;

    case BL_STATE_IDLE:
      /*
       * Состояние ожидания команд.
       * - если ошибка уже зафиксирована -> переход в ERROR,
       * - если это первый вход в IDLE -> отправляем "готов" (0x00) по CAN/UART,
       * - далее слушаем команды от хоста (erase, write, jump и т.п.).
       * Неизвестная команда -> ошибка.
       */
      if(bl->error.all)
      {
        bl->prev_state = bl->state;
        bl->state = BL_STATE_ERROR;
        break;
      }

      if((bl->state != bl->prev_state) && (bl->prev_state != BL_STATE_ERROR))
      {
        TXDataBoot[0] = 0x00;
        res_hal = HAL_CAN_AddTxMessage(bl->hcan, &bl->TxHeader, TXDataBoot, &TxMailBoxBoot);
        res_hal = HAL_UART_Transmit(bl->huart, TXDataBoot, 1, 100);
      }

      bl->prev_state = bl->state;

      if (Receive_FW_Command(bl) == 0xFF)
      {
        bl->error.bit.unknown_cmd = 1;
        bl->state = BL_STATE_ERROR;
      }
      break;
      
    case BL_STATE_RESET:
      /*
       * Состояние сброса.
       * Вызывает системный reset через NVIC -> контроллер запускается заново.
       */
      NVIC_SystemReset();
      break;      
      
    case BL_STATE_ERASE:
      /*
       * Состояние стирания Flash.
       * - сбрасываем "ключ" приложения,
       * - стираем область памяти под приложение,
       * - при успехе возвращаемся в IDLE,
       * - при ошибке отмечаем ошибку стирания и уходим в ERROR.
       * По завершению гасим LED.
       */
      bl->prev_state = bl->state;
      EraseKey();
      if (FLASH_Erase_App() == HAL_OK)
      {
        HAL_Delay(50);
        bl->state = BL_STATE_IDLE;
      }
      else
      {
        bl->error.bit.erase_err = 1;
        bl->state = BL_STATE_ERROR;
      }
      LED_BOOT_OFF();
      break;

    case BL_STATE_RECEIVE_UART:
    case BL_STATE_RECEIVE_CAN:
      /*
       * Состояние приёма страницы прошивки от хоста.
       * - различаем, пришло ли по UART или CAN,
       * - отправляем ACK (0x00),
       * - читаем блок данных (страницу) в буфер,
       * - после приёма проверяем CRC полученного блока,
       * - если CRC не совпадает -> очищаем буфер, фиксируем ошибку и уходим в ERROR.
       * По завершению приёма гасим LED.
       */
      receive_uart_flag = (bl->state == BL_STATE_RECEIVE_UART) ? 1 : 0;
    
      TXDataBoot[0] = 0x00;
      bl->prev_state = bl->state;

      if(receive_uart_flag)
      {
        res_hal = HAL_UART_Transmit(bl->huart, TXDataBoot, 1, 100);
        Bootloader_UART_Receive_Page(bl);
      }
      else
      {
        res_hal = HAL_CAN_AddTxMessage(bl->hcan, &bl->TxHeader, TXDataBoot, &TxMailBoxBoot);
        Bootloader_CAN_Receive_Page(bl);
      }

      uint32_t crc_calculated = CRC32_Compute((uint8_t *)bl->fw_buffer, bl->fw_len);
      if(crc_calculated != bl->fw_crc)
      {
        for(int i = 0; i < bl->fw_len; i++)
        {
          bl->fw_buffer[i] = 0;
        }
        bl->error.bit.crc_err = 1;
        bl->state = BL_STATE_ERROR;
      }
      LED_BOOT_OFF();
      break;
      
    case BL_STATE_WRITE:
      /*
       * Состояние записи страницы прошивки во Flash.
       * - пытаемся записать буфер в указанную область памяти,
       * - если успешно -> возвращаемся в IDLE (ждём следующего блока),
       * - если ошибка -> фиксируем ошибку записи и уходим в ERROR.
       * После завершения гасим LED.
       */
      bl->prev_state = bl->state;
      if (FLASH_Write_Page(&bl->addr, bl->fw_buffer, bl->fw_len) == HAL_OK)
      {
        bl->state = BL_STATE_IDLE;
      }
      else
      {
        bl->error.bit.write_err = 1;
        bl->state = BL_STATE_ERROR;
      }
      LED_BOOT_OFF();
      break;

    case BL_STATE_JUMP_TO_APP:
      /*
       * Состояние перехода в приложение.
       * - выполняем проверку корректности прошивки (Verify_Firmware),
       * - если проверка пройдена -> устанавливаем "ключ" приложения,
       *   чтобы пометить прошивку как валидную,
       * - если проверка не пройдена -> ошибка verify и переход в ERROR.
       * В случае успеха вызываем JumpToApplication(), передавая управление основному коду.
       */
      bl->prev_state = bl->state;
      if (Verify_Firmware() == HAL_OK)
      {
        EraseKey();
        SetKey();
      }
      else
      {
        bl->error.bit.verify_err = 1;
        bl->state = BL_STATE_ERROR;
        break;
      }
      JumpToApplocation();
      break;
    
    case BL_STATE_JUMP_TO_BOOT:
      /*
       * Состояние возврата в bootloader.
       */
      bl->prev_state = bl->state;
      JumpToBootloader();
      break;

    case BL_STATE_ERROR:
      /*
       * Состояние ошибки.
       * - при первом входе в ERROR отправляем код ошибки (0xFF + код ошибки),
       * - продолжаем слушать команды, чтобы можно было сбросить или стереть Flash,
       * - мигаем LED раз в 500 мс для визуальной индикации ошибки.
       */
      if(bl->state != bl->prev_state)
      {
        TXDataBoot[0] = 0xFF;
        TXDataBoot[1] = (bl->error.all >> 8) & (0xFF);
        TXDataBoot[2] = bl->error.all & (0xFF);
        res_hal = HAL_CAN_AddTxMessage(bl->hcan, &bl->TxHeader, TXDataBoot, &TxMailBoxBoot);
        res_hal = HAL_UART_Transmit(bl->huart, TXDataBoot, 1, 100);
      }
      bl->prev_state = bl->state;

      if (Receive_FW_Command(bl) == 0xFF)
      {
        bl->error.bit.unknown_cmd = 1;
        bl->state = BL_STATE_ERROR;
      }
      
      if(HAL_GetTick() - led_err_lasttick > 500)
      {
        led_err_lasttick = HAL_GetTick();
        LED_BOOT_TOOGLE();
      }
      break;

    default:
      /*
       * Попадание в неизвестное состояние.
       * Считается ошибкой: ставим unknown_cmd и переходим в ERROR.
       */
      bl->error.bit.unknown_cmd = 1;
      bl->state = BL_STATE_ERROR;
      break;
  }
}



/**
 * @brief  Устанавливает новое состояние бутлоадера в зависимости от команды.
 * @param  bl: указатель на структуру бутлоадера
 * @param  cmd: команда бутлоадера (BootloaderCommand_t)
 * @param  uart_flag: 1 — команда пришла по UART, 0 — по CAN
 * @retval 0x00 — команда успешно обработана, 0xFF — неизвестная команда
 */
static uint8_t SetBootState(Bootloader_t *bl, BootloaderCommand_t cmd, uint8_t uart_flag)
{
  switch(cmd)
  {
    case CMD_ERASE: // команда: стереть Flash
      bl->state = BL_STATE_ERASE;
      return 0x00;
    case CMD_START_RECEIVE: // команда: принять блок
      if(uart_flag)
        bl->state = BL_STATE_RECEIVE_UART;
      else
        bl->state = BL_STATE_RECEIVE_CAN;        
      return 0x00;
    case CMD_WRITE: // команда: записать блок
      bl->state = BL_STATE_WRITE;
      return 0x00;
    case CMD_GOTOAPP: // команда: прыжок в приложение
      bl->state = BL_STATE_JUMP_TO_APP;
      return 0x00;
    case CMD_RESET: // команда: прыжок в приложение
      bl->state = BL_STATE_RESET;
      return 0x00;
    case CMD_GOTOBOOT: // команда: прыжок в бутлоадер
      bl->state = BL_STATE_JUMP_TO_BOOT;
    case CMD_PING: // команда: пинг, отправка текущего состояния бутлоадера
      bl->prev_state = 0; // обнуляем предыдущее состоояние, чтобы снова отправить комманду с текущим состоянием
      return 0x00;

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

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

  // ---------------------------
  // Чтение команды по UART
  // ---------------------------
  cmd = Bootloader_UART_Receive(bl); // таймаут 10 ms
  if (cmd != NO_CMD)
  {
    ret_val = SetBootState(bl, cmd, 1);
  }

  // ---------------------------
  // Чтение команды по CAN
  // ---------------------------
  cmd = Bootloader_CAN_Receive(bl);
  if (cmd != NO_CMD)
  {
    ret_val = SetBootState(bl, cmd, 0);
  }

#ifdef TEST_CAN
	TxHeaderBoot.StdId = 0x200; // ID OF MESSAGE
	TxHeaderBoot.ExtId = 0; // STANDART FRAME (NOT EXTENTED)
	TxHeaderBoot.RTR = CAN_RTR_DATA; // TRANSMIT DATA OR 
	TxHeaderBoot.IDE = CAN_ID_STD; // STANDART FRAME
	TxHeaderBoot.DLC = 8; // DATA SIZE
	TxHeaderBoot.TransmitGlobalTime = DISABLE; //THIS MODE IS NOT USED, SO DISABLE
	uint8_t asd[8] = "ABCDEFGL";
  res_hal =	HAL_CAN_AddTxMessage(&hcan_boot, &TxHeaderBoot, asd, &TxMailBoxBoot); // add to mail for transmit
	HAL_Delay(1000);
#endif
  if((bl->state != BL_STATE_IDLE) && (bl->state != BL_STATE_ERROR))
  {
    LED_BOOT_ON();
  }
  return ret_val;
}



/**
 * @brief  Вычисление CRC32 блока данных.
 * @param  data: указатель на массив данных
 * @param  length: длина массива в байтах
 * @retval CRC32 вычисленное значение
 */
uint32_t CRC32_Compute(const uint8_t* data, uint32_t length)
{
    const uint32_t polynomial = 0x04C11DB7;
    uint32_t crc = 0xFFFFFFFF;

    for(uint32_t i = 0; i < length; i++)
    {
        crc ^= ((uint32_t)data[i] << 24);
        for(uint8_t j = 0; j < 8; j++)
        {
            if(crc & 0x80000000)
                crc = (crc << 1) ^ polynomial;
            else
                crc <<= 1;
        }
    }

    return crc ^ 0xFFFFFFFF;
}




/**
 * @brief  Конфигурация системного тактирования (должна быть переопределена пользователем).
 */
__WEAK void Boot_SystemClock_Config(void)
{
}



/**
 * @brief  Обработчик ошибок (может быть переопределён пользователем).
 */
__WEAK void Error_Handler(void)
{
  while(1);
}

