#include "lip/lip_cfg_defs.h"

#ifdef LIP_USE_MB_TCP_SERVER
    #include "lip_mb_tcp_defs.h"
    #include "lip/app/modbus/core/lip_mb_server_private.h"
    #include "lip/transport/tcp/lip_tcp.h"

    #ifndef LIP_MB_TCP_SERVER_BUF_MEM
        #define LIP_MB_TCP_SERVER_BUF_MEM(var) LIP_DEFAULT_STATIC_BUF_MEM(var)
    #endif


    #ifndef LIP_MB_TCP_SERVER_RECV_BUF_SIZE
        #define LIP_MB_TCP_SERVER_RECV_BUF_SIZE LIP_TCP_RX_BUF_MIN(LIP_MB_TCP_ADU_SIZE_MAX)
    #endif
    #ifndef LIP_MB_TCP_SERVER_SEND_BUF_SIZE
        #define LIP_MB_TCP_SERVER_SEND_BUF_SIZE (LIP_MB_TCP_ADU_SIZE_MAX)
    #endif

    typedef struct {
        t_lip_mb_con_info info;
        uint8_t adu[LIP_MB_TCP_ADU_SIZE_MAX];
        uint16_t rx_pos;
        uint16_t rx_exp_pos;
    } t_mb_srv_connection;

    #define LIP_MB_TCP_ADU_GET_DLEN(adu) ((uint16_t)((((uint16_t)adu[LIP_MB_TCP_HDR_POS_LEN_H] << 8) | (adu[LIP_MB_TCP_HDR_POS_LEN_L])) - 1))


    LIP_MB_TCP_SERVER_BUF_MEM(static t_mb_srv_connection f_con_list[LIP_MB_TCP_SERVER_SOCKET_CNT]);
    static uint16_t f_mb_port = LIP_MB_TCP_DEFAULT_PORT;


    LIP_MB_TCP_SERVER_BUF_MEM(static uint8_t f_rx_buf[LIP_MB_TCP_SERVER_SOCKET_CNT][LIP_MB_TCP_SERVER_RECV_BUF_SIZE]);
    LIP_MB_TCP_SERVER_BUF_MEM(static uint8_t f_tx_buf[LIP_MB_TCP_SERVER_SOCKET_CNT][LIP_MB_TCP_SERVER_SEND_BUF_SIZE]);

    static void f_start_wait_req(int con_idx) {
        f_con_list[con_idx].rx_pos = 0;
        f_con_list[con_idx].rx_exp_pos = LIP_MB_TCP_HDR_SIZE;
    }

    static void f_start_listen(int con_idx) {
        lsock_listen(f_con_list[con_idx].info.tcp.socket, f_mb_port);
        f_start_wait_req(con_idx);
    }


    void lip_mb_tcp_server_init(void) {
        for (int con_idx = 0; con_idx < LIP_MB_TCP_SERVER_SOCKET_CNT; ++con_idx) {
            f_con_list[con_idx].info.proto = LIP_MB_PROTO_TCP;
            f_con_list[con_idx].info.tcp.socket = lsock_create();
            if (f_con_list[con_idx].info.tcp.socket >= 0) {
#ifdef LIP_TCP_USE_KEEPALIVE
                int val = 1;
                lsock_set_opt(f_con_list[con_idx].info.tcp.socket, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val));
#endif
                lsock_set_recv_fifo(f_con_list[con_idx].info.tcp.socket, f_rx_buf[con_idx], LIP_MB_TCP_SERVER_RECV_BUF_SIZE);
                lsock_set_send_fifo(f_con_list[con_idx].info.tcp.socket, f_tx_buf[con_idx], LIP_MB_TCP_SERVER_SEND_BUF_SIZE);
                f_start_listen(con_idx);
            }
        }
    }

    static void f_mb_try_recv(t_mb_srv_connection *con) {
        int exp_size = con->rx_exp_pos - con->rx_pos;
        if (lsock_recv_rdy_size(con->info.tcp.socket) >= exp_size) {
            lsock_recv(con->info.tcp.socket, &con->adu[con->rx_pos], exp_size);
            con->rx_pos += exp_size;
        }
    }

    void lip_mb_tcp_server_pull(void) {
        int con_idx;
        for (con_idx = 0; con_idx < LIP_MB_TCP_SERVER_SOCKET_CNT; ++con_idx) {
            t_mb_srv_connection *con = &f_con_list[con_idx];
            t_lip_tcp_sock_state state = lsock_state(con->info.tcp.socket);
            if (state == LIP_TCP_SOCK_STATE_ESTABLISHED) {
                f_mb_try_recv(con);


                if ((con->rx_exp_pos == LIP_MB_TCP_HDR_SIZE)  &&
                     (con->rx_pos == con->rx_exp_pos)) {
                    /* в длину входит помимо данных один байт unit_id */
                    uint16_t dlen = LIP_MB_TCP_ADU_GET_DLEN(con->adu);
                    if (dlen > LIP_MB_TCP_PDU_SIZE_MAX) {
                        lsock_close(con->info.tcp.socket, LIP_TCP_DEFAULT_CLOSE_TOUT);
                    } else {
                        con->rx_exp_pos += dlen;
                        f_mb_try_recv(con);
                    }
                }

                if ((con->rx_exp_pos > LIP_MB_TCP_HDR_SIZE)  &&
                     (con->rx_pos == con->rx_exp_pos)) {
                    if (lsock_send_rdy_size(con->info.tcp.socket) >= LIP_MB_TCP_ADU_SIZE_MAX) {
                        /* поддерживаем только нулевой код протокола, остальные игнорируем */
                        if ((con->adu[LIP_MB_TCP_HDR_POS_PROTO_L] == 0) &&
                             (con->adu[LIP_MB_TCP_HDR_POS_PROTO_H] == 0)) {
                            uint16_t dlen = LIP_MB_TCP_ADU_GET_DLEN(con->adu);

                            lip_mb_exec_func(&con->info, con->adu[LIP_MB_TCP_HDR_POS_UNIT_ID], &con->adu[LIP_MB_TCP_HDR_SIZE], dlen, &dlen);
                            if ((dlen > 0) && (dlen <= LIP_MB_TCP_PDU_SIZE_MAX)) {
                                uint16_t resp_len = dlen + 1;

                                con->adu[LIP_MB_TCP_HDR_POS_LEN_H] = (resp_len >> 8) & 0xFF;
                                con->adu[LIP_MB_TCP_HDR_POS_LEN_L] = (resp_len) & 0xFF;
                                lsock_send(con->info.tcp.socket, con->adu, LIP_MB_TCP_HDR_SIZE + dlen);
                            }
                        }
                        /* ожидание нового пакета */
                        f_start_wait_req(con_idx);

                    }
                }
            } else if (state == LIP_TCP_SOCK_STATE_CLOSE_WAIT) {
                lsock_close(con->info.tcp.socket, LIP_TCP_DEFAULT_CLOSE_TOUT);
            } else if (state == LIP_TCP_SOCK_STATE_CLOSED) {
                f_start_listen(con_idx);
            }
        }
    }

    void lip_mb_server_tcp_port_set(uint16_t port) {
        int con_idx;
        f_mb_port = port;
        /* для сокетов, которые ждут соединение, перезапускаем ожидание для изменившегося
           порта. уже установленные соединения оставляем */
        for (con_idx = 0; con_idx < LIP_MB_TCP_SERVER_SOCKET_CNT; ++con_idx) {
            t_mb_srv_connection *con = &f_con_list[con_idx];
            t_lip_tcp_sock_state state = lsock_state(con->info.tcp.socket);
            if (state == LIP_TCP_SOCK_STATE_LISTEN) {
                lsock_abort(con->info.tcp.socket);
                f_start_listen(con_idx);
            }
        }
    }

    void lip_mb_server_tcp_con_close(t_lip_mb_check_con_cb con_sel_cb, void *cb_ctx, uint32_t tout) {
        int con_idx;
        for (con_idx = 0; con_idx < LIP_MB_TCP_SERVER_SOCKET_CNT; ++con_idx) {
            t_mb_srv_connection *con = &f_con_list[con_idx];
            if (!con_sel_cb || con_sel_cb(&con->info, cb_ctx)) {
                lsock_close(con->info.tcp.socket, tout);
            }
        }
    }
#endif











