#include "at32f403a_407.h"
#include "common_config.h"
#include "modbus.h"
#include "update.h"
#include "FreeRTOS.h"
#include "task.h"
#include "fr_timers.h"
#include "mb.h"
#include "mbport.h"
#include "mbrtu.h"
#include "tim_delay.h"
#include "modbus_params.h"
#include "common_gpio.h"
#include "io_utils.h"
#include "log.h"
#include "log_api.h"
#include "settings_api.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define REG_HOLDING_START		( 1 )
#define REG_HOLDING_NREGS		( 5 )


#undef DBG
#define DBG if(1)

//UCHAR
uint8_t mb_addr = 0; 
static USHORT	usRegHoldingStart = REG_HOLDING_START;
extern UCHAR    rcvAddress;
extern bool psw_ok;
extern uint8_t arch_channel;

//osThreadId modbus_task_handle;
static void modbus_task(void *params);
//osThreadId modbus_params_handle;
static void modbus_params(void *params);

TimerHandle_t settings_timer_handle;
//osTimerId reset_timer_handle;
//osTimerId settings_timer_handle;
//osTimerId modbus_timer_handle;

void cb_settings_timer(TimerHandle_t timer);

mb_delay_action_t mb_action = MB_NO_ACTION;
uint8_t new_slave_addr;
bool mb_package_flag = false;



//
void mb_init(void)
{
    uint32_t baud = 115200;
	const UCHAR ucSlaveID[] = {0xAA, 0xBB, 0xCC};
    
    gpio_mbaddr_init();
    
    mb_addr = gpio_get_mbaddr();
    
	mb_init_params();

    mb_helper_tim_init(baud);
    settings_init_mb_port(mb_addr);
	eMBSetSlaveID(0x34, TRUE, ucSlaveID, 3);
	eMBEnable();
    
    
    xTaskCreate(modbus_task, "modbus_task", 2*configMINIMAL_STACK_SIZE, NULL, 4/*tskIDLE_PRIORITY*/, NULL);
    
    xTaskCreate(modbus_params, "modbus_params", 4*configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
    
    // Таймер для перехода в IAP
    update_create_timer();
    
    settings_timer_handle = xTimerCreate("reset_timer", 10000, pdFALSE, (void *)0, cb_settings_timer);
    
#if 0    
	osTimerDef(vResetTimer, modbus_reset);
	reset_timer_handle = osTimerCreate(osTimer(vResetTimer), osTimerOnce, NULL);
    
    osTimerDef(vSettingsTimer, settings_timer);
    settings_timer_handle = osTimerCreate(osTimer(vSettingsTimer), osTimerOnce, NULL);
    
    osTimerDef(vModbusTimer, modbus_port_timer);
    modbus_timer_handle = osTimerCreate(osTimer(vModbusTimer), osTimerOnce, NULL);
#endif    
}

//
void modbus_task(void *params)
{
	(void)params;
    uint8_t new_addr = 0;
        
	for (;;)
	{
        if (gpio_check_mbaddr(mb_addr, &new_addr)) {
            eMBSetSlaveAddr((UCHAR)new_addr);
        }
                
		eMBPoll();
	}

}

//
void modbus_params(void *params)
{
    for (;;)
    {
        if (mb_action != MB_NO_ACTION)
        {
            switch (mb_action)
            {
                // Установка нового адреса
                case MB_CHANGE_ADDR :
    
                    //settings.com_settings.mb_addr = new_slave_addr;  
                    eMBSetSlaveAddr(new_slave_addr);
                      
                break;
                
                case MB_PAS_OK :
                  
                    //set_sys_settings_flag = true;
                    //osTimerStart(settings_timer_handle, 30000);
                    xTimerStart(settings_timer_handle, 0);
                  
                break;
                
                case MB_CHANGE_PORT_SETTINGS :
#if 0                  
                    osDelay(200);
                    eMBRTUStop();
                    settings_set_modbus_params(temp_modbus_port);
                    settings_init_mb_port(settings.com_settings.mb_addr);
                    MBDBG settings_print_modbus();
                    eMBRTUStart();
                    eMBEnable();
                    mb_package_flag = false;
                    osTimerStart(modbus_timer_handle, 9800);
#endif                  
                break;
                
                // Полное восстановление дефолтных настроек
                case MB_DEF_SETTINGS :
#if 0
                    settings_set_default(true);
                    settings_save();
#endif                  
                break;  
                
                // Частичное восстановление дефолтных настроек
                case MB_PART_DEF_SETTINGS :
#if 0
                    settings_set_default(false);
                    settings_save();
#endif                  
                break;
                
                // Сохранить настройки и добавить запись в журнал
                case MB_SAVE_SETTINGS :
                    
                    settings_save_with_log();

                break;
                
                case MB_SAVE_SYS_SETTINGS :

                    if (save_sys_cmd) {
                        save_sys_cmd = 0;
                        memcpy(&sys_settings, &temp_sys_settings, sizeof(sys_settings));
                        sys_save(&sys_settings);
                    }

                break;
                
                case MB_LOG_CLEAR :
                    
                    log_format(0, 0);
                    
                    // -------------------------------------------------------------------------- //
                    // EVENT. Очиста журнала.    
                    log_add_entry(LOG_CLEAR, (log_event_state_t)LOG_EVENT_STATE_OK, 0, 0.0);

                break;
                
                case MB_ARCHIVE_CLEAR :
                  
                    log_format(1, arch_channel);
                    
                    // -------------------------------------------------------------------------- //
                    // EVENT. Очиста архива.    
                    log_add_entry(LOG_CLEAR, (log_event_state_t)LOG_EVENT_STATE_OK, 0, 1.0);
                    
                break;
                  
                case MB_RESET :
                  
                    vTaskDelay(100);
                    NVIC_SystemReset();
                  
                break;
                
                default : break;
            }
            
            mb_action = MB_NO_ACTION;
        }
        vTaskDelay(10);
    }
}


// Отложенная перезагрузка в Bootloader
void modbus_reset(void const * params)
{
#if 0  
	MBDBG printf("Switch to bootloader!\r\n");
	rtc_set_load_mode(1);
	HAL_NVIC_SystemReset();
#endif    
}



// Запуск таймера для изменение настроек modbus
void modbus_port_timer(void const * params)
{
#if 0
    eMBRTUStop();
  
    // Были транзакции по modbus с новыми настройками. 
    // Нужно сохранить новые настройки.
    if (mb_package_flag)
    {
        settings.com_settings.mb_port = temp_modbus_port;
        settings_set_modbus_params(settings.com_settings.mb_port);
        settings_init_mb_port(settings.com_settings.mb_addr);
        settings_save();
        
    }
    // Нужно вернуть старые настройки
    else
    {
        temp_modbus_port = settings.com_settings.mb_port;
        settings_set_modbus_params(settings.com_settings.mb_port);
        settings_init_mb_port(settings.com_settings.mb_addr);
    }
  
    
    MBDBG settings_print_modbus();
    eMBRTUStart();
    eMBEnable();
#endif    
}

// ------------------------------------------------------------------- //
//						Modbus callback's
// ------------------------------------------------------------------- //

//
// 65 (0x41) Read Input Registers	 
eMBErrorCode
eMBUpdateCB( UCHAR * pucFrame, USHORT * usLen)
{
	eMBErrorCode    eStatus = MB_ENOERR;

    mb_package_flag = true;
    
	// Команда перехода в bootloader.
	if (pucFrame[1] == 0x01) {
        update_tim_start();
	}

    return eStatus;
}

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
	return MB_ENOREG;
}


// 03 (0x03) Read Holding Registers
// чтение N регистров управления и уставок
// 16 (0x10) Write Multiple registers
// запись N регистров управления и уставок (0x10)
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
	eMBErrorCode eStatus = MB_ENOERR;

	int			 iRegIndex;
    
    mb_delay_action_t mb_act = MB_NO_ACTION;
	uint16_t regs_cnt = usNRegs;
	uint16_t index_param;
	uint8_t *ptr_buf = pucRegBuffer;
	uint16_t size;

/*    
	printf("usAddress: 0x%X\r\n", usAddress);
	printf("usNRegs: %u\r\n", usNRegs);
	printf("eMode: %u\r\n", eMode);
    printf("pucRegBuffer :%X\r\n", pucRegBuffer[0]);
*/  
    
    save_mode_reset_cnt();
      
    mb_package_flag = true;
    
	iRegIndex = (int)(usAddress - usRegHoldingStart);

    
    
#if 1    
	switch (eMode)
	{
		case MB_REG_READ :

			while (regs_cnt)
			{
				// Поиск регистра
				if (!mb_find_param(iRegIndex, &index_param, &size))
				{
					return MB_ENOREG; // нет запрашиваемого регистра
				}
				else
				{
					MBDBG printf("reg: %X, index: %u, size: %u\r\n", iRegIndex, index_param, size);
                    MBDBG printf("reg: %X\r\n", iRegIndex);
					mb_get_param(ptr_buf,  index_param); // Вызов функции записи
					iRegIndex += size;
					ptr_buf += 2*size;
					regs_cnt -= size;
				}
			}


		break;

		case MB_REG_WRITE :

			while (regs_cnt)
			{
				// Поиск регистра
				if (!mb_find_param(iRegIndex, &index_param, &size))
				{
					return MB_ENOREG; // нет запрашиваемого регистра
				}
				else
				{
					MBDBG printf("reg: %X, index: %u, size: %u\r\n", iRegIndex, index_param, size);
					mb_act = mb_set_param(ptr_buf, index_param); // Вызов функции записи

                    if (mb_act != MB_NO_ACTION)
                        mb_action = mb_act;
                    
					iRegIndex += size;
					ptr_buf += 2*size;
					regs_cnt -= size;
                    
                    if (mb_act == MB_PAS_ERR)
                        return MB_EINVAL;
				}
			}

		break;
	}
#endif
	return eStatus;
}

// 17 (0x11) Report Slave ID (Serial Line only) 
// чтение информации об устройстве (0x11)
eMBException
eMBFuncReportSlaveID( UCHAR * pucFrame, USHORT * usLen )
{
#if 1
    mb_package_flag = true;
    
	//*usLen = mb_get_info(&pucFrame[1]) + 3;
#endif
	return MB_EX_NONE;
}


// чтение/запись значений из нескольких регистров флагов (Coil Status).
// чтения N регистров параметров
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}


// 0x42
// Запрос на изменение адреса
// Если запрос широковещательный, то нужно проверить присланный ID.
eMBException    
eMBSetAddrIdCB( UCHAR * pucFrame, USHORT * usLen )
{
#if 0  
    mb_package_flag = true;
    
    if (rcvAddress == MB_ADDRESS_BROADCAST)
    {
        if (mb_set_addr_id(&pucFrame[1], &new_slave_addr))
            mb_action = MB_CHANGE_ADDR;
    }
    // Адресный запрос. ID не проверяем
    else
    {
        if (mb_set_addr(&pucFrame[1], &new_slave_addr))
        {
            pucFrame[1] = new_slave_addr;
            mb_action = MB_CHANGE_ADDR;
        }
        else 
            pucFrame[1] = MB_BOOT_ERR_WRONG_CONTENT;
    }
    
    *usLen = 2;
#endif    
    return MB_EX_NONE;
}


// 0x43
eMBException
eMBSetAddrSerialCB( UCHAR * pucFrame, USHORT * usLen )
{
#if 0  
    mb_package_flag = true;
      
    if (rcvAddress == MB_ADDRESS_BROADCAST)
    {
        if (mb_set_addr_serial(&pucFrame[1], &new_slave_addr))
            mb_action = MB_CHANGE_ADDR;
    }
    // Адресный запрос. ID не проверяем
    else
    {
        if (mb_set_addr(&pucFrame[1], &new_slave_addr))
        {
            pucFrame[1] = new_slave_addr;
            mb_action = MB_CHANGE_ADDR;
        }
        else 
            pucFrame[1] = MB_BOOT_ERR_WRONG_CONTENT;
    }
    
    *usLen = 2;
#endif    
    return MB_EX_NONE;
}

eMBException    
eMBFuncReadFileRecordCB( UCHAR * pucFrame, USHORT * usLen )
{
    uint8_t byte_count;
    uint8_t reference_type;
    uint8_t archive_channel;
    uint16_t record_index = 0;
    uint16_t record_num = 0;
  
#if 0    
    DBG printf("Recived: ");
    
    for (int i = 0; i < *usLen; i++)
    {
        DBG printf("%X ", pucFrame[i]);
    }
    DBG printf("\r\n");
#endif    
    byte_count = pucFrame[1];
    
    reference_type = pucFrame[2];
    
    record_index = pucFrame[3] << 8;
    record_index |= pucFrame[4];
    
    archive_channel = pucFrame[5] << 8;
    archive_channel |= pucFrame[6];
    
    record_num = pucFrame[7] << 8;
    record_num |= pucFrame[8];
    
#if 0
    DBG printf("Byte count: %u, reference_type: %u, \r\n",
                byte_count, reference_type);
    
    DBG printf("Record index: %u, channel: %u, record num: %u\r\n", 
                record_index, archive_channel, record_num);
   
#endif    
    
#if 1    
    if (reference_type == MB_ARCHIVE_ENTRY) {
        *usLen = 1 + mb_archive_get_entry((uint8_t*)&pucFrame[1], 
                                          archive_channel, record_index);
    }
    else if (reference_type == MB_LOG_ENTRY) {
        *usLen = 1 + mb_log_get_entry((uint8_t*)&pucFrame[1], record_index);
    }
#endif        
    
    return MB_EX_NONE;
}




//
void cb_settings_timer(TimerHandle_t timer)
{
    DBG printf("Settings timer callback\r\n");
    psw_ok = false;
}
