#include "at32f403a_407.h"
#include "iap.h"
#include "common_config.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "fr_timers.h"
#include "event_groups.h"
#include "mb.h"
#include "mbport.h"
#include "mbrtu.h"
#include "model_cfg.h"
#include <stdio.h>
#include <string.h>


EventGroupHandle_t event;
SemaphoreHandle_t buf_sem;
SemaphoreHandle_t flash_sem;
TimerHandle_t led_timer_handle;
TimerHandle_t iap_timeout_handle;

uint32_t fw_size;   // Размер FW (передается в первом пакете)
static uint16_t i_big_package;
static uint16_t i_short_package;
uint8_t fw_buf_1[FW_BUF_SIZE];
uint8_t fw_buf_2[FW_BUF_SIZE];
bool flash_err_flag = false;

void led_timer(TimerHandle_t timer);
void iap_timeout(TimerHandle_t timer);
void iap_task(void *params);


//
void iap_init(void)
{
    flash_sem = xSemaphoreCreateBinary();
    
    led_timer_handle = xTimerCreate("led_timer", 500, pdTRUE, (void *)0, led_timer);
    xTimerStart(led_timer_handle, 0);
    
    iap_timeout_handle = xTimerCreate("iap_timer", 100000, pdTRUE, (void *)0, iap_timeout);
        
    xTaskCreate(iap_task, "iap_task", 4*configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
}

//
void iap_task(void *params)
{
    (void)(params);
    unsigned int ev;

    event = xEventGroupCreate();
    buf_sem = xSemaphoreCreateBinary();
    xSemaphoreGive(buf_sem);
    
    if (ev = xEventGroupWaitBits(event, IAP_START | IAP_RETURN, pdTRUE, pdFALSE, 60000) != IAP_START)
    {
        printf("IAP: switch to FW!\r\n");
        bpr_data_write(BACKUP_IAP_LOAD_MODE, 0);
        bpr_data_write(BACKUP_UPDATE_STATUS, IAP_UPDATE_ERROR);
        NVIC_SystemReset();
    }
    
    if (ev == IAP_RETURN)
    {
        printf("IAP: return to FW\r\n");
        bpr_data_write(BACKUP_IAP_LOAD_MODE, 0);
        bpr_data_write(BACKUP_UPDATE_STATUS, IAP_UPDATE_ERROR);
        vTaskDelay(200);
        NVIC_SystemReset();
    }
    
    for (;;)
    {
        ev = xEventGroupWaitBits(event, 
                                 IAP_BLOCK | IAP_FINISH | IAP_RETURN | 
                                 IAP_FLASH_ERROR | IAP_RESET,
                                 pdTRUE, pdFALSE, portMAX_DELAY);

        switch (ev)
        {
            case IAP_BLOCK :
              
                write_buf();
                
            break;
            
            // Запись завершена. Проверки пройдены успешно.
            case IAP_FINISH :
              
                flash_lock();
                DBG printf("IAP: finish\r\n");
                bpr_data_write(BACKUP_IAP_LOAD_MODE, 0);
                bpr_data_write(BACKUP_UPDATE_STATUS, IAP_UPDATE_OK);
                vTaskDelay(100);
                NVIC_SystemReset();
                
            break;
            
            // Возврат в основное ПО
            case IAP_RETURN :
              
                DBG printf("IAP: return to FW\r\n");
                bpr_data_write(BACKUP_IAP_LOAD_MODE, 0);
                vTaskDelay(100);
                NVIC_SystemReset();
            
            break;
            
            // Ошибка работы с flash
            case IAP_FLASH_ERROR :
              
                DBG printf("IAP: flash error");
                vTaskDelay(100);
                NVIC_SystemReset();
              
            break;
            
            // Общая ошибка (что-то пошло не так)
            case IAP_RESET :
            
                DBG printf("IAP: common error");
                vTaskDelay(100);
                NVIC_SystemReset();
              
            break;
              
            default : break;
        }  
	}
}

// Обработчик приема первого пакета с ключом и размером
mb_err_code_t iap_start(uint8_t *data, uint8_t len)
{
    char model[MODEL_LEN] = {0};
    char new_model[MODEL_LEN] = {0};
    
    // Проверка контрольного слова
	if (data[0] != 0xEF || data[1] != 0xBE || data[2] != 0xAD || data[3] != 0xDE)
		return MB_BOOT_ERR_WRONG_CONTENT;

    // Проверка модели выполняется если флеш не пустая.
    // Если флеш пуста, то можно залить прошивку от любой модели
    memcpy(model, (void const*)MODEL_ADDR, MODEL_LEN);
    
    if ((model[0] != 0xFF) && (model[1] != 0xFF) && (model[2] != 0xFF) && (model[3] != 0xFF)) 
    {
        memcpy(new_model, &data[8], MODEL_LEN);
        printf("Current model: %s\r\n", model);
        printf("New model: %s\r\n", new_model);
        printf("Model size: %u\r\n", strlen(new_model));
        if (memcmp(new_model, model, strlen(new_model)) != 0)
        {
            DBG printf("Model error!\r\n");
            return MB_BOOT_ERR_WRONG_CONTENT;
        }
    }
    
	swap((uint8_t*)&data[4], (uint8_t*)&fw_size, 4);
    
    DBG printf("FW size: %u\r\n", fw_size);
    
    erase_flash();

	i_big_package = 0;
	i_short_package = 0;

    // Запуск таймера для отсчета timeout
    xTimerStart(iap_timeout_handle, 0);
    
	xEventGroupSetBits(event, IAP_START);

	return MB_BOOT_ERR_NO;
}

// Обработчик приема блока прошивки
mb_err_code_t iap_block(uint8_t *data, uint8_t len)
{
	uint16_t pack_num;
    static uint16_t pack_index = 0;
    
    // Проверка номера пакета
	swap(data, (uint8_t*)&pack_num, 2);
    if (pack_num != pack_index) {
        xEventGroupSetBits(event, IAP_RESET);
        return MB_BOOT_WRONG_PACK_INDEX;
    }
    pack_index++;
	
    memcpy(&fw_buf_1[BLOCK_SIZE*i_short_package++], &data[2], len - 4);
    
	// Когда буфер заполняется нужно писать во флеш.
    // Файл может быть не кратен размеру блока!
    if  ((i_short_package == (FLASH_PAGE_SIZE / BLOCK_SIZE)) || (len != (BLOCK_SIZE + 4))) 
	{
		i_short_package = 0;
		i_big_package++;
		
		if (i_big_package != 1) 
		{
			memcpy(fw_buf_2, fw_buf_1, FLASH_PAGE_SIZE);
			xEventGroupSetBits(event, IAP_BLOCK);
            return MB_BOOT_ERR_NO;
		}
	}

	// Собрали данные для первой станицы. Нужно проверить ключ прошивки.
	if ((i_big_package == 1) && (i_short_package == 0))
	{
		memcpy(fw_buf_2, fw_buf_1, FLASH_PAGE_SIZE);
        xEventGroupSetBits(event, IAP_BLOCK);
		return MB_BOOT_ERR_NO;			
	}

    xSemaphoreGive(flash_sem);
	return MB_BOOT_ERR_NO;
}

//
bool write_buf(void)
{
	uint32_t *ptr = (uint32_t*)fw_buf_2;
	uint32_t addr = FW_BEGIN_ADDRESS + FLASH_PAGE_SIZE*(i_big_package - 1);
    
	// Когда принят и проверен первый блок данных (2кБт) нужно очистить память
	if (i_big_package == 1)
	{
/*      
        if (!erase_flash())
        {
            flash_err_flag = true;  // ошибка flash
            xSemaphoreGive(flash_sem);
            return false;
        }
*/
    }  
    
    flash_unlock();
    
    for (uint32_t i = 0; i < 512; i++)
    {
        if (flash_word_program(addr, *ptr) != FLASH_OPERATE_DONE)
        {
            flash_lock();
            flash_err_flag = true;  // ошибка flash
        }
        ptr++;
		addr += 4;
    }        
    
    xSemaphoreGive(flash_sem);
	
	return true;
}

// Нужно определить количество секторов для записи FW
bool erase_flash(void)
{
    uint32_t sector = FW_BEGIN_ADDRESS;
    int sector_number = fw_size/FLASH_PAGE_SIZE + 1;
      
    //DBG printf("Need to erase %u sectros\r\n", sector_number);
    
    flash_unlock();
    
    for (int i = 0; i < sector_number; i++)
    {
        if (flash_sector_erase(sector) != FLASH_OPERATE_DONE) {
            return false;
        }
        //DBG printf("Sector %X erased\r\n", sector);
        sector += FLASH_PAGE_SIZE;
    }
    
    flash_lock();
    
    return true;
}

// 
mb_err_code_t check_crc(void)
{
    uint32_t calc_crc;
    uint32_t read_crc;
    
    
    read_crc = *(uint32_t*)(FW_BEGIN_ADDRESS + fw_size - 4);
    DBG printf("Read CRC: %X\r\n", read_crc);
    
    crm_periph_clock_enable(CRM_CRC_PERIPH_CLOCK, TRUE);
    crc_data_reset();
      
    for (uint32_t* ptr = (uint32_t*)FW_BEGIN_ADDRESS; ptr != (uint32_t*)(FW_BEGIN_ADDRESS + fw_size - 4); ptr++)
        crc_one_word_calculate(*ptr); // добавляем всю прошивку в CRC
        
    calc_crc = crc_data_get();
    
    DBG printf("Calc CRC: %X\r\n", calc_crc);
    
    if (read_crc != calc_crc)
        return MB_BOOT_ERR_WRONG_FW_CRC;
    
    return MB_BOOT_ERR_NO;
}

//
void iap_finish(void)
{
	xEventGroupSetBits(event, IAP_BLOCK | IAP_FINISH);
}

//
void led_timer(TimerHandle_t timer)
{
    
}

//
// 65 (0x41) Read Input Registers	 
eMBErrorCode
eMBUpdateCB( UCHAR * pucFrame, USHORT * usLen)
{
	int res;
				
	switch (pucFrame[1]) 
	{
		case 1:

			pucFrame[1] = iap_start(&pucFrame[2], *usLen);
			*usLen = 2;
			return MB_ENOERR;

        break;

		case 2:
			
            res = iap_block(&pucFrame[2], *usLen);
            
            xSemaphoreTake(flash_sem, 500);
            
            if (flash_err_flag) {
                pucFrame[1] = MB_BOOT_FLASH_ERR;
                xEventGroupSetBits(event, IAP_FLASH_ERROR);
            }
            else if (res == MB_BOOT_ERR_NO)
                pucFrame[1] = WRITE_BLOCK_COM;
            else if (res == MB_BOOT_ERR_WRONG_KEY)
                pucFrame[1] = MB_BOOT_ERR_WRONG_KEY;
            else if (res == MB_BOOT_WRONG_PACK_INDEX)
                pucFrame[1] = MB_BOOT_WRONG_PACK_INDEX;
            
			*usLen = 2;

			return MB_ENOERR;

        break;

		case 3:

            res = check_crc();
          
            if (res == MB_BOOT_ERR_WRONG_FW_CRC)
                pucFrame[1] = WRITE_BLOCK_COM;
            else 
                iap_finish();
            
			*usLen = 2;

			return MB_ENOERR;
			
		break;

		default:
			return MB_EPORTERR;
	}
}


//
void swap(uint8_t *in_buf, uint8_t *out_buf,  uint8_t size)
{
	for (uint8_t i = 0; i < size; i++) 
		out_buf[size - i - 1] = in_buf[i];
}

//
void iap_timeout(TimerHandle_t timer)
{
    DBG printf("IAP timeout callback\r\nReset...");
    vTaskDelay(1000);
    flash_lock();
    NVIC_SystemReset();
}
