//******************************************** Включаемые файлы *********************************************

#include "tcp.h"
// #include "tcp_cmd.h"
// #include "tcp_data.h"
// #include "tcp_settings.h"

#include "ltimer.h"
#include "dbg_printf.h"
#include "tcp_protocol.h"
#include "flash_mem.h"


// #include "lmcal.h"
// #include "lta_private.h"




// #include "lboot_Port.h"
// #include "dev_pins.h"
// #include "lip_config.h"
// #include "lip.h"
// #include "user_settings.h"
// #include "factory_mem.h"
// #include "lboot_req.h"
// #include "fpga_main.h"

// #include "streams.h"

// #include <stdio.h>

#ifdef LIP_USE_DNSSD
// размер имени экземпляра сервиса dns-sd
#define TCP_INSTANCE_NAME_SIZE          (64U)

#endif


#define TCP_CLOSED       ((lip_state() == LIP_STATE_OFF) ? (true) : (false))



//******************************************** Определения типов ********************************************





//******************************************* Локальные константы *******************************************



//************************************ Глобальные (публичные) переменные ************************************

// Структура с параметрами модуля 
extern tcp_info_t tsp_dev_info;


//************************************* Локальные (приватные) переменные ************************************

// признак, что нужно изменить настройки ethernet
static bool eth_restart_req = false;

#ifdef LIP_USE_DNSSD
static t_lip_dns_txt_keyval st_srv_txt_vals[] = 
{
    {"txtvers", "1"},
    {"devname",      tsp_dev_info.name},
    {"serial",       tsp_dev_info.serial_dev},
    {"serial_crate", tsp_dev_info.serial_crate},
    {"slot",         tsp_dev_info.pos_num}
};

static char s_host_name[TCP_INSTANCE_NAME_SIZE];
static char s_inst_name[TCP_INSTANCE_NAME_SIZE];
#endif



// 




// // Фукнции обратного вызова по передаче и приему данных
// static t_lip_tcp_dd_cb f_send_done_callback = NULL;
// static t_lip_tcp_dd_cb f_recv_done_callback = NULL;

//**************************************** Объявления глобальных функций ************************************

// Установка запроса на переинициализацию модуля
void tcp_restart(void);

// Инициализация модуля 
extern fun_res_t tcp_cmd_init(void);
// Деинициализация модуля 
extern void tcp_cmd_deinit(void);
// Периодический опрос сокетов команд
extern void tcp_cmd_loop(void);

// Инициализация модуля 
extern fun_res_t tcp_data_init(void);
// Деинициализация модуля 
extern void tcp_data_deinit(void);
// Периодический опрос сокетов данных
extern void tcp_data_loop(void);

// // Создание и начало прослушивания сокета данных
// // num - номер сокета (от 0 до TCP_DATA_SOCK_CNT - 1)
// extern fun_res_t tcp_data_create_socket(u8 num);
// // Закрытие и удаление сокета данных
// extern fun_res_t tcp_data_delete_socket(u8 num);

// Чтение настроек модуля в глобальную структуру
extern void tcp_read_settings(void);

// todo - cmd:
// todo - процедура записи настроек (с перезагрузкой сети)
// получение настроек и отправка наверх

//***********************************************************************************************************
//******************************** Определения локальных (приватных) функций ********************************
//***********************************************************************************************************

#if 0



/**
 * Обработка сокетов данных
 */
static void tcp_poll_data_soctets(void)
{
    
} // tcp_poll_data_soctets


#endif




#ifdef LIP_USE_DNSSD

static void tcp_itoa(unsigned val, char *str) 
{
    unsigned div, digts;
    const unsigned max_div = (1UL << (sizeof(int)*8-1))/10;

    /* определяем кол-во цифр */
    for (digts=1, div=10; (val >= div) && (div < max_div); digts++, div*=10)
    {}

    if (val >= div) {
        digts++;
    } else {
        div/=10;
    }

    while (div!=0) {
        char c;
        c = val/div;
        val %= div;
        *str++ = '0' + c;
        div/=10;
    }
    *str++ = '\0';
}



// Заполнить массив с именем модуля
// nConflictCnt - счетчик количества конфликтов имен
static void tcp_fill_host_name(unsigned int nConflictCnt) 
{
    strcpy(s_host_name, tsp_dev_info.name);
    strcat(s_host_name, "_\0");
    strcat(s_host_name, tsp_dev_info.serial_dev);
    strcat(s_host_name, "_\0");
    strcat(s_host_name, tsp_dev_info.serial_crate);
    strcat(s_host_name, "_\0");
    strcat(s_host_name, tsp_dev_info.pos_num);

    // При наличии конфликта имен добавляем _x в конец доменного имени
    if (nConflictCnt != 0) 
    {
        int nLen = strlen(s_host_name);

        s_host_name[nLen++] = '_';
        tcp_itoa(nConflictCnt + 1, 
                &s_host_name[nLen]);
    }
}


// Заполнить массив с именем экземпляра сервиса dnssd
// nConflictCnt - счетчик количества конфликтов имен 
static void tcp_fill_srv_name(unsigned int nConflictCnt) 
{
    strcpy(s_inst_name, s_host_name);

    if (nConflictCnt != 0U) 
    {
        int len = strlen(s_inst_name);
        s_inst_name[len++] = '_';
        tcp_itoa(nConflictCnt + 1, &s_inst_name[len]);
    }
}


// Колбек при обнаружении конфликта имен
static void tcp_mdns_nameconflict_cb(const t_lip_dns_name* name, 
                                    t_lip_dns_name* new_name, 
                                    unsigned int nConflictCnt) 
{
    if (name->name == s_host_name) 
    {
        tcp_fill_host_name(nConflictCnt);
        new_name->name = s_host_name;
        new_name->parent = lip_mdns_get_parent_domain();
    } 
    else if (name->name == s_inst_name) 
    {
        tcp_fill_srv_name(nConflictCnt);
        new_name->name = s_inst_name;
        new_name->parent = name->parent;
    }
}
#endif


#ifdef LIP_DHCPC_CONFIGURED_CB
/**
 * Вызывается при успешном получении адреса по DHCP, если в  
 * lip_conf.h определено LIP_DHCPC_CONFIGURED_CB
*/
void lip_dhcpc_configured_cb(t_lip_dhcp_info *info) {
    // dbg_printf(LOG_LVL_DEBUG, "%d: dhcpc_configured %d.%d.%d.%d\n",  lclock_get_ticks(), info->ip_addr[0], info->ip_addr[1], info->ip_addr[2], info->ip_addr[3]);

    memcpy(tsp_dev_info.cur_settings.ipv4_addr, info->ip_addr, sizeof(info->ip_addr));
    memcpy(tsp_dev_info.cur_settings.ipv4_mask, info->msk,     sizeof(info->msk));
    memcpy(tsp_dev_info.cur_settings.ipv4_gate, info->gate,    sizeof(info->gate));
    tsp_dev_info.dev_flags |= DEV_FLAGS_ADDR_ESTABLISHED;
}
#endif



// Примечание - При смене сетевых настроек необходимо выполнять полную переинициализацию стека
static void tcp_stack_init (void)
{
    // Получение сетевых настроек
    tcp_read_settings();

    // --------------------
    lip_init();

    /* MAC-адрес всегда устанавливается сразу после lip_init().
        Для изменения нужно вызвать lip_close(), lip_init() и снова lip_eth_set_mac_addr()*/
    lip_eth_set_mac_addr(tsp_dev_info.cur_settings.mac);

    if (tsp_dev_info.cur_settings.flags & FM_US_FLAGS_AUTO_IP) 
    {
        lip_ip_link_local_enable(1);
        lip_dhcpc_enable(1);
    } 
    else 
    {
        /* Установка статических параметров IPv4. Можно изменять и во время
            * работы стека */
        lip_ipv4_set_addr(tsp_dev_info.cur_settings.ipv4_addr);
        lip_ipv4_set_mask(tsp_dev_info.cur_settings.ipv4_mask);
        lip_ipv4_set_gate(tsp_dev_info.cur_settings.ipv4_gate);
    }


    #ifdef LIP_USE_DNSSD
    // MDNS
    tcp_fill_host_name(0);
    tcp_fill_srv_name(0);

    lip_mdns_set_hostname(s_host_name, tcp_mdns_nameconflict_cb);
    lip_dnssd_register_local(s_inst_name, 
                            TCP_DNSSD_SERVICE_NAME,
                            tsp_dev_info.cur_settings.tcp_cmd_port, 
                            tcp_mdns_nameconflict_cb,
                            st_srv_txt_vals, 
                            sizeof(st_srv_txt_vals)/sizeof(st_srv_txt_vals[0]));
    #endif

    // todo - ptp пока не поддерживается
    // #if LIP_CFG_PTP_ENABLE
    //         lip_ptp_set_system_priority2(LIP_PTP_SYSTEM_PRIO2_END_INST_DEFAULT);
    //         lip_ptp_port_enable(0, 0);
    //         /** @todo LAN PHY */
    //         lip_ptp_port_set_latency(0, 0, 0);
    //         for (int port_idx = 1; port_idx < LIP_PORT_MAX_CNT; ++port_idx) {
    //             lip_ptp_port_disable(port_idx);
    //         }
    //         lip_ll_tstmp_enable();
    // #endif
}


// Проверка, требуется ли выполнять опрос сокета
static bool tcp_is_poll_required(void)
{
    bool bRes = false;

    // Получен запрос на останов стека
    if (lip_state() == LIP_STATE_CLOSE_REQUEST) 
    {
       bRes = true;
    } 
    // Признак смены настроек
    else if (eth_restart_req == true) 
    {
        if ((TCP_CLOSED != true) && 
            (lip_state() != LIP_STATE_CLOSE_REQUEST)) 
        {
            lip_close_request(TCP_LIP_CLOSE_TIMEOUT);
        }

        if (TCP_CLOSED == true) 
        {
            eth_restart_req = false;
            tcp_stack_init();
        }
    } 
    // Если стек не закрыт, выполняем опрос
    else if (TCP_CLOSED != true) 
    {
        bRes = true;
    }

    return bRes;
}



//***********************************************************************************************************
//******************************* Определения глобальных (публичных) функций ********************************
//***********************************************************************************************************

//-----------------------------------------------------------------------------------------------------------
fun_res_t tcp_init(void)
{
    fun_res_t err = ERR_OK;
    
    // Обновление сетевых настроек и инициализация стека
    tcp_stack_init();

    // Инициализация сокетов команд и данных
    tcp_cmd_init();
    tcp_data_init();
    
    DBG_LOGI("TCP has initialized!\n");

    return err;
}


//-----------------------------------------------------------------------------------------------------------
void tcp_deinit(void)
{
    tcp_cmd_deinit();
    tcp_data_deinit();
    lip_close();
}


//-----------------------------------------------------------------------------------------------------------
void tcp_loop(void)
{
    // Опрос стека
    if (true == tcp_is_poll_required())
    {
        if (lip_pull() != LIP_ERR_SUCCESS) 
        {
            DBG_LOGI("lip close!\n");
            lip_close();
        }
    }

    // Опрос сокетов
    if(lip_state() != LIP_STATE_OFF)
    {
        tcp_cmd_loop();
        tcp_data_loop();
    }
}



//-----------------------------------------------------------------------------------------------------------
void tcp_restart(void)
{
    eth_restart_req = true;
}





#if 0
//-----------------------------------------------------------------------------------------------------------
BOOLEAN TCP_IsCmdSocketEstablished(void)
{
    BOOLEAN bFunRes = false;

    // Если хотя бы 1 сокет команд подключен
    for (U8 i=0; i < TCP_CMD_SOCK_CNT; i++) 
    {
        int cstate = lsock_state(ast_cmd_proc[i].sock);

        if (cstate == LIP_TCP_SOCK_STATE_ESTABLISHED)
        {
            bFunRes = true;
        }
    }

    return bFunRes;
}


//-----------------------------------------------------------------------------------------------------------
BOOLEAN TCP_IsDataSocketEstablished(void)
{
    return (n_cur_data_sock != LIP_TCP_INVALID_SOCKET_ID) ? (true) : (false);
}


//-----------------------------------------------------------------------------------------------------------
void TCP_SetSendDataCallback(t_lip_tcp_dd_cb cb)
{
    if (cb != NULL) {
        f_send_done_callback = cb;
    }
}


//-----------------------------------------------------------------------------------------------------------
void TCP_SetRecvDataCallback(t_lip_tcp_dd_cb cb)
{
    if (cb != NULL) {
        f_recv_done_callback = cb;
    }
}



//-----------------------------------------------------------------------------------------------------------
void TCP_SetRxCmdCallback(tcp_rx_cmd_t cb)
{
    if (cb != NULL) {
        for (U8 i = 0; i < TCP_CMD_CALLBACK_QTY; i++) {
            if (f_user_rx_cmd_callback[i] == NULL) {
                f_user_rx_cmd_callback[i] = cb;
                break;
            }
        }
    }
}


//-----------------------------------------------------------------------------------------------------------
void TCP_SetTxCmdCallback(tcp_tx_cmd_t cb)
{
    if (cb != NULL) {
        for (U8 i = 0; i < TCP_CMD_CALLBACK_QTY; i++) {
            if (f_user_tx_cmd_callback[i] == NULL) {
                f_user_tx_cmd_callback[i] = cb;
                break;
            }
        }
    }
}




//-----------------------------------------------------------------------------------------------------------
int32_t TCP_SendData(uint32_t *data, uint32_t len)
{
    int32_t ret = -1;

    if (true == TCP_IsDataSocketEstablished())
    {
        /* ограничение на размер идет с тем, чтобы мы не могли поставить один дескриптор
       на весь буфер, т.к. тогда мы его сможем заполнять только после полного завершения
       передачи. при ограничении же всегда используется несколько заданий и мы можем
       подкачивать его постепенно по мере их выполнений */
        if (len > TCP_SEND_DATA_SIZE_MAX) {
            len = TCP_SEND_DATA_SIZE_MAX;
        }

        ret = lsock_send_start(n_cur_data_sock, 
                                (uint8_t*)data, len*sizeof(data[0]), 
                                f_send_done_callback);

        /*if(ret != len*sizeof(data[0])) {
            lprintf("lsock_send(%d) ret = %d\n", len*sizeof(data[0]), ret);
        }*/
    }
     
    return (ret < 0) ? ret : (int)len;
}



//-----------------------------------------------------------------------------------------------------------
int32_t TCP_RecvData(uint32_t *data, uint32_t len)
{
    int32_t ret = -1;

    if (true == TCP_IsDataSocketEstablished())
    {
        if (len > TCP_SEND_DATA_SIZE_MAX) {
            len = TCP_SEND_DATA_SIZE_MAX;
        }   
            
        ret = lsock_recv_start(n_cur_data_sock, 
                                (uint8_t*)data, len*sizeof(data[0]), 
                                f_recv_done_callback);
    }
    return (ret < 0) ? ret : (int)len;
}


#endif








//*********************************************** КОНЕЦ ФАЙЛА ***********************************************


#if 0

        // Какой MAC использовать - заводской или из структуры сетевых настроек:
        // 1) заводского нет, флаг = -        -> сетевой (1е включение после прошивки)
        // 2) заводского нет, флаг = юзер_мак -> сетевой (есть сетевая настройка)
        // 3) заводской есть, флаг = -        -> заводской (была записана заводская информация)
        // 2) заводской есть, флаг = юзер_мак -> сетевой (есть сетевая настройка)
        if (true == (TCP_stEthConfig.flags & ETH_FLAGS_USER_MAC))
        {
            // Используем MAC из структуры сетевых настроек
            memcpy(TCP_pCurrentMac, TCP_stEthConfig.mac, ETH_CFG_MAC_ADDR_SIZE);
        }
        else
        {
            if (true == bFactoryMacValid)
            {
                // Используем MAC из структуры заводских настроек
                memcpy(TCP_pCurrentMac, FM_stFactInfo.mac, ETH_CFG_MAC_ADDR_SIZE);
            }
            else
            {
                // Используем MAC из структуры сетевых настроек (дефолтный mac)
                memcpy(TCP_pCurrentMac, TCP_stEthConfig.mac, ETH_CFG_MAC_ADDR_SIZE);
            }
        }

#endif