#include "stm32f4xx.h"
#include "common_config.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 "crc.h"
#include "wdg.h"
#include "tinystdio.h"
#include "time.h"
#include "string.h"

#include "stm32f4x7_eth.h"
#include "netconf.h"

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

bool IAPviaETH = false;
uint8_t fDoneReset = 0;
uint8_t fErrorReset = 0;
uint8_t fUpload = 0;
uint8_t fInvalidFw = 0;
uint8_t fBootFailed = 0;
uint32_t resetCounter = 0;
bool UpdateTimeoutFlag = false;

/* 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);

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

    WDG_Init();
    IO_Init();
    InitUSART();

  /* 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 и ждать обновления ПО */
  /* TODO remove if tested */
  //bootTry = sSettings.bootParams.bootTry;
  loadMode = RTC_ReadBackupRegister(RTC_BKP_DR1);
  bootTry = RTC_ReadBackupRegister(RTC_BKP_DR2);
  printf("loadMode: %d\r\nbootTry: %d\r\n", loadMode, bootTry);
  
  if (bootTry > 1)
  {
    bootTry--;
    RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);

    /* Check if valid stack address (RAM address) then jump to user application */
    if (((*(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
    {
        JumpAdd = *(__IO uint32_t*) (USER_FLASH_FIRST_PAGE_ADDRESS + 4);
        Jump_To_App = (pFunction) JumpAdd;
        __set_MSP(*(__IO uint32_t*) USER_FLASH_FIRST_PAGE_ADDRESS);
        Jump_To_App();
    }
    else {
        /* Флеш пустая, нечего загружать, висим в аварийном режиме */
        fInvalidFw = 1;
        PRINT_USART("\n\rFW empty. Started bootloader\n\r");
    }
  }
  else if (bootTry == 1)
  {
    fBootFailed = 1;
    PRINT_USART("\n\rFW boot failed. Started bootloader\n\r");

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

  /* Флаг не установлен прыгаем на основную программу */
  if (loadMode == 0)
  {
      printf("Run main FW\n\r");
      //printf("*(__IO uint32_t*)(USER_FLASH_FIRST_PAGE_ADDRESS) = 0x%X\n\r", *(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS);
      //printf("*(__IO uint32_t*)(USER_FLASH_FIRST_PAGE_ADDRESS + 4) = 0x%X\n\r", *(__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 (((*(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
    {
      /* 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(120000);
  LED_Init();


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

  LED_On(RED_STATUS);

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

  //Если нажата DEF начинаем обновление с sd
  if (IO_BtnDefaultPressed()) 
  {
//	IAPviaETH = false;
//    timer_AddFunction(500, &LED_Blinky_Yellow);
//    SD_NVIC_Init();
  } 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) {
      timer_AddFunction(1000, &UpdateTimeout_Handler);
  }
  else {
      /* Флеш пустая, нечего загружать, висим в аварийном режиме */
      fInvalidFw = 1;
  }


  while (1)
  {
    timer_Main();

    if (IAPviaETH) { // Обновление по ETH
      /* check if any packet received */
      if (ETH_CheckFrameReceived())
      { 
        /* process received ethernet packet */
        LwIP_Pkt_Handle();
      }
      /* handle periodic timers for LwIP */
      LwIP_Periodic_Handle(LocalTime);
	
	  if (fDoneReset)
	  {
		resetCounter++;
	    if (resetCounter > 100000)
		{  
	      loadMode = 0;
	      bootTry = BOOT_TRY;
	      RTC_WriteBackupRegister(RTC_BKP_DR1, loadMode);
	      RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);
	      /* Set FW update flag */
	      RTC_WriteBackupRegister(RTC_BKP_DR3, 1);

          NVIC_SystemReset();
		}  
	  }	
	  if (fErrorReset)
	  {
		resetCounter++;
	    if (resetCounter > 100000) {
          NVIC_SystemReset();
	    }
	  }
    }

    //Если нажата DEF переходим в основную прошивку
    if (IO_BtnDefaultPressed() || UpdateTimeoutFlag)
    {
      if (!fUpload && ((*(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS) != 0xFFFFFFFF)) {
          PRINT_USART("\n\rUpdate timeout... Return to main FW\n\r");
          loadMode = 0;
          bootTry = BOOT_TRY;
          RTC_WriteBackupRegister(RTC_BKP_DR1, loadMode);
          RTC_WriteBackupRegister(RTC_BKP_DR2, bootTry);
		  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(RED_STATUS);
  LED_Off(GREEN_STATUS);
  timer_AddFunction(500, &LED_Blinky_Red);
  while (1)
  {
    timer_Main();
  }
}

/**
  * @brief
  */
void UpdateTimeout_Handler(void)
{
    static char lcdbuf[32] = {0};
    static uint8_t time = UPDATE_TIMEOUT;

    if ((fUpload) || (fInvalidFw)) return;

    if (time == 0) {
        UpdateTimeoutFlag = true;
    }
    else {
        time--;
    }
}