#include "stm32f4xx.h"
#include "common_config.h"
#include "conf.h"
#include "main.h"
#include "led.h"
#include "systick.h"
#include "usart.h"
#include "httpserver.h"
#include "flash_if.h"
#include "settings_api.h"
#include "gpio_io.h"
#include "gpio.h"
#include "crc.h"
#include "wdg.h"
#include "tinystdio.h"
#include "time.h"
#include "string.h"
#include "stm32f4x7_eth.h"
#include "netconf.h"
#include "rng.h"
#ifdef SD_ENABLE
#include "SD_Card/sdio_sd.h"
#include "FATFS/ff.h"
#include "FATFS/diskio.h"
#endif
#ifdef LCD_ENABLE
#include "lcd.h"
#endif
#ifdef SLAVEBRD_ENABLE
#include "stm32sprog.h"
#endif
#ifdef FTP_ENABLE
#include "spi_flash.h"
#endif

/* Секция размещения СRC прошивки */
#if defined ( __GNUC__ )
uint32_t crc __attribute__ ((section (".crc"))) = 0xAABBCCDD;
#endif

#define FW_FILE_NAME    MAIN_FW_NAME

/*
 * Bootloader verification key section.
 * Use "openssl rand -hex 8" to generate new key.
 * */
uint64_t bootkey __attribute__ ((section (".bootkey"))) = 0x92dc73b8fef3b041;

bool IAPviaETH = false;
bool fDoneReset = false;
bool fUpload = false;
bool fInvalidFw = false;
bool fBootFailed = false;

volatile uint32_t resetCounter = 0;
bool UpdateTimeoutFlag = false;

#ifdef SD_ENABLE
extern FATFS    fs;
extern FIL      fil_obj;
#endif

/* this variable is used to create a time reference incremented by 10ms */
__IO uint32_t LocalTime = 0;

pFunction Jump_To_App;

uint32_t JumpAdd;

/**
  * @brief  Общая структура настроек
  */
extern SETTINGS_t sSettings;

void UpdateTimeout_Handler(void);


#ifdef SD_ENABLE
bool mmc_mount(void)
{
    if (disk_initialize(0) != RES_OK) return false;
    if (f_mount(&fs, "0:", 1) != FR_OK) return false;

    return true;
}
#endif


bool CheckFWIsValid(void)
{
	if (((*(__IO uint32_t *)USER_FLASH_FIRST_PAGE_ADDRESS) & 0x2FFE0000 ) == 0x20000000 &&
        *(__IO uint32_t *)USER_FLASH_CRC_ADDRESS != 0xFFFFFFFF) {
        return true;
    }
    printf("\r\nFW empty. Started bootloader\r\n");
    return false;
}

#ifdef FTP_ENABLE
void  spi_flash_update(void)
{
  printf("ftp: trying to update the firmware from the SPI flash\r\n");
  const uint32_t spif_firmware_offset = SPI_FLASH_SECTOR_SIZE * FIRMWARE_UPDATE_SECTOR_OFFSET;
  uint32_t fw_begin;

  spi_flash_init();
  spi_flash_read(spif_firmware_offset, &fw_begin, 4, 0);
  const uint32_t crc_offset = USER_FLASH_CRC_ADDRESS - USER_FLASH_FIRST_PAGE_ADDRESS;
  uint32_t expected_crc;
  spi_flash_read(spif_firmware_offset + crc_offset, &expected_crc, 4, 0);
  bool present_firmware = (fw_begin != 0xFFFFFFFF) || (expected_crc != 0xFFFFFFFF);
  if (!present_firmware) {
    printf("ftp: no firmware-like data is present on the SPI flash\r\n");
    return;
  }

  // check CRC
  CRC->CR = 1;
  for (uint32_t offset = 0; offset < crc_offset; offset += 4) {
    uint32_t data;
    spi_flash_read(spif_firmware_offset + offset, &data, sizeof(data), 0);
    CRC->DR = data;
  }
  uint32_t calculated_crc = CRC->DR;
  if (expected_crc != calculated_crc) {
    printf("ftp: calculated CRC (%lx) doesn't match the expected CRC (%lx)!\r\n", calculated_crc, expected_crc);
    return;
  }

  printf("ftp: writing the stm32 flash\r\n");
  FLASH_If_Erase(USER_FLASH_FIRST_PAGE_ADDRESS);
  FLASH_If_Init(); // unlock it again
  uint8_t buf[1024];
  for (uint32_t offset = 0; offset < MAIN_FW_SIZE; offset += sizeof(buf)) {
    spi_flash_read(spif_firmware_offset + offset, buf, sizeof(buf), 0);
    uint8_t *addr = USER_FLASH_FIRST_PAGE_ADDRESS + offset;
    FLASH_If_Write(&addr, buf, sizeof(buf) / 4);
  }

  printf("ftp: erasing the SPI flash\r\n");
  for (int sec = 0; sec < FIRMWARE_UPDATE_SECTOR_COUNT; ++sec) {
    spi_flash_erase_sector(SPI_FLASH_SECTOR_SIZE * (FIRMWARE_UPDATE_SECTOR_OFFSET + sec), 0);
  }

  printf("ftp: update successful, rebooting...\r\n");
  SET_FWUPDATED_FLAG();
  CLEAR_FWINVALID_FLAG();
  RTC_WriteBackupRegister(RTC_BKP_DR1, 0); // loadMode
  NVIC_SystemReset();
}
#endif // FTP_ENABLE

void main(void)
{
  	uint8_t bootTry;
  	uint8_t loadMode;

    gpio_init();
    WDG_Init();
    InitUSART();

#ifdef PRINTF_STDLIB
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
#endif

  /* Enable PWR peripheral clock */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

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

  /* Включаем тактирование модуля CRC */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE);
  
  /* Проверка флага, определяющего состояние устройства. */
  /* Флаг установлен - работает Bootloader               */
  /* Флаг сброшен - запускается основная программа       */

  SETTINGS_Load();
  
  /* Проверка флага bootTry. Если флаг установлен, значит произошел сбой в 
     основной прошивке. Нужно загружать bootloader и ждать обновления ПО */
  loadMode = RTC_ReadBackupRegister(RTC_BKP_DR1);
  bootTry = RTC_ReadBackupRegister(RTC_BKP_DR2);
  fInvalidFw = RTC_ReadBackupRegister(RTC_BKP_DR7);
  printf("\r\nloadMode: %d\r\nbootTry: %d\r\n", loadMode, bootTry);
  printf("fInvalidFw: %s\r\n", fInvalidFw ? "true" : "false");
  
  if (bootTry > 1) {
      bootTry--;
      RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);

      /* Check if valid stack address (RAM address) then jump to user application */
      if (CheckFWIsValid()) {
          CLEAR_FWINVALID_FLAG();
          /* Jump to user application */
          JumpAdd = *(__IO uint32_t *) (USER_FLASH_FIRST_PAGE_ADDRESS + 4);
          Jump_To_App = (pFunction) JumpAdd;
          /* Initialize user application's Stack Pointer */
          __set_MSP(*(__IO uint32_t *) USER_FLASH_FIRST_PAGE_ADDRESS);
          Jump_To_App();
      } else {
          /* Флеш пустая, нечего загружать, висим в аварийном режиме */
          SET_FWINVALID_FLAG();
      }
  } else if (bootTry == 1) {
      fBootFailed = 1;
      printf("\r\nFW boot failed. Started bootloader\r\n");

      bootTry = 0;
      loadMode = 1;
      RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);
      RTC_WriteBackupRegister(RTC_BKP_DR1, loadMode);
  }

  /* Флаг не установлен прыгаем на основную программу */
  if (loadMode == 0) {
      printf("Run main FW\r\n");
      //printf("*(__IO uint32_t*)(USER_FLASH_FIRST_PAGE_ADDRESS) = 0x%X\r\n", *(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS);
      //printf("*(__IO uint32_t*)(USER_FLASH_FIRST_PAGE_ADDRESS + 4) = 0x%X\r\n", *(__IO uint32_t*)(USER_FLASH_FIRST_PAGE_ADDRESS + 4));

      /* Set bootTry flag every time to ensure that
        * IAP will starts again if FW is corrupted */
      bootTry = BOOT_TRY;
      RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);

      /* Check if valid stack address (RAM address) then jump to user application */
      if (CheckFWIsValid()) {
          CLEAR_FWINVALID_FLAG();
          /* Jump to user application */
          JumpAdd = *(__IO uint32_t *) (USER_FLASH_FIRST_PAGE_ADDRESS + 4);
          Jump_To_App = (pFunction) JumpAdd;
          /* Initialize user application's Stack Pointer */
          __set_MSP(*(__IO uint32_t *) USER_FLASH_FIRST_PAGE_ADDRESS);
          Jump_To_App();
      }
  }
  
  /* Загружается Bootloader... */
  
  SysTick_Config(168000);//120000
  LED_Init();
  LED_On(LED_INIT_ERR);

  PRINT_USART("\n\rBootloader starting...   \n\r");

#ifdef LCD_ENABLE
    LCD_Init();
    LCD_PrintAligned(1, alignCENTER, "Обновление ПО");
#endif

  /* Random number generator */
  RNG_Init();

  ETH_BSP_Config();
  LwIP_Init();
  IAP_httpd_init();
  CRC_Init();

  //Если нажата DEF начинаем обновление с sd
  if (IO_BtnDefaultPressed()) {
#ifdef SD_ENABLE
      IAPviaETH = false;
      timer_AddFunction(500, &LED_Blinky_Red);
      SD_NVIC_Init();
#endif
  } else {
      IAPviaETH = true;
      timer_AddFunction(500, &LED_Blinky_Red);
  }
  
  /* Check if valid stack address (RAM address) */
  if (((*(__IO uint32_t *)USER_FLASH_FIRST_PAGE_ADDRESS) & 0x2FFE0000 ) == 0x20000000 && !fInvalidFw) {
#if UPDATE_TIMEOUT != 0
      timer_AddFunction(1000, &UpdateTimeout_Handler);
#endif
  } else {
      update_timeout = 0;
      /* Флеш пустая, нечего загружать, висим в аварийном режиме */
      SET_FWINVALID_FLAG();
#ifdef LCD_ENABLE
      LCD_PrintAligned(5, alignCENTER, "Аварийный режим");
      LCD_PrintAligned(7, alignCENTER, "Ошибка ПО");
#endif
  }

  if (fBootFailed) {
#ifdef LCD_ENABLE
      LCD_PrintAligned(5, alignCENTER, "Аварийный режим");
      LCD_ClearRow(7);
#endif
  }
#ifdef FTP_ENABLE
  spi_flash_update();
#endif // FTP_ENABLE

  while (1)
  {
      timer_Main();

      if (IAPviaETH) { // Обновление по ETH
          /* Handle received packets and periodic timers for LwIP */
          LwIP_Periodic_Handle(0);

          if (fDoneReset) {
              resetCounter++;
              if (resetCounter > 400000) {
                  loadMode = 0;
                  bootTry = BOOT_TRY;
                  RTC_WriteBackupRegister(RTC_BKP_DR1, loadMode);
                  RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);

                  SET_FWUPDATED_FLAG();
                  CLEAR_FWINVALID_FLAG();
#ifdef SLAVEBRD_ENABLE
                  /* Reboot daughter board */
                  stmReboot();
#endif
                  /* Self reboot */
                  NVIC_SystemReset();
              }
          }
      } else {
#ifdef SD_ENABLE
          // Обновление с SD
          // Пробуем смонтировать SD
          if (mmc_mount()) {
              // Пробуем открыть файл с именем FW_FILE_NAME
              timer_Stop(&LED_Blinky_Yellow);
              LED_Off(RED1_INT);
              LED_Off(GREEN_INT);
              if (f_open(&fil_obj, FW_FILE_NAME, FA_READ | FA_OPEN_EXISTING) == FR_OK) {  // открываем файл
                  LED_On(GREEN_INT);

                  if (startFlashing() < 0) {
                      Error_Handler();
                  }
                  f_close(&fil_obj);

                  // CRC посчитанная при сборке и записанная в последине 4 байта прошивки
                  // Должна сойтись с CRC посчитанной на контроллере
                  if (CRC_Read() == CRC_Calculate()) {
                      loadMode = 0;
                      bootTry = BOOT_TRY;
                      RTC_WriteBackupRegister(RTC_BKP_DR1, loadMode);
                      RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);
#ifdef SLAVEBRD_ENABLE
                      /* Reboot daughter board */
                      stmReboot();
#endif
                      /* Self reboot */
                      NVIC_SystemReset();
                  } else {
                      Error_Handler();
                  }

              } else { // Файл не найден
                  LED_On(RED1_INT);
              }
          }
#endif
      }

      //Если нажата DEF переходим в основную прошивку
      if (IO_BtnDefaultPressed() || UpdateTimeoutFlag) {
          if (!fUpload && ((*(__IO uint32_t *)USER_FLASH_FIRST_PAGE_ADDRESS) != 0xFFFFFFFF)) {
              printf("\r\nUpdate timeout... Return to main FW\r\n");
              loadMode = 0;
              bootTry = BOOT_TRY;
              RTC_WriteBackupRegister(RTC_BKP_DR1, loadMode);
              RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);
#ifdef SLAVEBRD_ENABLE
              /* Reboot daughter board */
              stmReboot();
#endif
              /* Self reboot */
              NVIC_SystemReset();
          }
      }
  }
}

/**
  * @brief  Updates the system local time
  * @param  None
  * @retval None
  */
void Time_Update(void)
{
  LocalTime += SYSTEMTICK_PERIOD_MS;
}

/**
  * @brief
  */
u32_t sys_now(void) {
    return LocalTime;
}

/**
  * @brief  Error handler
  * @param  None
  * @retval None
  */
void Error_Handler(void) {
  LED_Off(LED_INIT_ERR);
  LED_Off(LED_INIT_OK);
  timer_AddFunction(500, &LED_Blinky_Red);
  while (1)
  {
    timer_Main();
  }
}

/**
  * @brief
  */
void UpdateTimeout_Handler(void)
{
    if ((fUpload) || (fInvalidFw) || (fDoneReset)) {
        return;
    }

    if (update_timeout == 0) {
        UpdateTimeoutFlag = true;
    } else {
#ifdef LCD_ENABLE
        static char lcdbuf[32] = {0};
        sprintf(lcdbuf, "Ожидание (%d) ", update_timeout);
        LCD_ClearRow(7);
        LCD_PrintAligned(7, alignCENTER, lcdbuf);
#endif
        update_timeout--;
    }
}