#include "lip.h"
#include "lip_sample_tcp_cntr_cfg_defs.h"
#include <string.h>


#if LIP_EXAMPLE_TCP_ENABLE
TCP_SEND_BUF_MEM(static uint8_t tcp_snd_buf[TCP_SEND_PART_CNT][TCP_SEND_PART_SIZE]);
TCP_RECV_BUF_MEM(static uint8_t tcp_rcv_buf[TCP_RECV_PART_CNT][TCP_RECV_PART_SIZE]);

/* счетчик, используемый при передачу */
static uint32_t f_snd_cntr, f_rcv_cntr, f_rcv_blocks, f_rcv_errs;

static int f_sock; /* идентификатор сокета */
static int f_snd_part, f_rcv_part; /* часть буфера, которая будет следующая
                                      использована для задания на передачу и прием
                                      соответственно */
static int f_port; /* номер порта, который будет прослушиваться */
static int f_con_established; /* установлено ли соединение */

static void  f_start_listen(void) {
    f_snd_cntr = f_rcv_cntr = 0;
    f_rcv_blocks = f_rcv_errs = 0;
    f_snd_part = f_rcv_part = 0;
    f_con_established = 0;

    lsock_listen(f_sock, f_port);
}

void lip_sample_tcp_cntr_init(const uint8_t *mac, const uint8_t *ip,
                              const uint8_t *mask, const uint8_t *gate,
                              uint16_t port) {
    /* инициализация стека */
    lip_init();

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



    /* Установка статических параметров IPv4. Можно изменять и во время
     * работы стека */
    lip_ipv4_set_addr(ip);
    lip_ipv4_set_mask(mask);
    lip_ipv4_set_gate(gate);

    f_sock = lsock_create();

#ifdef TCP_SEND_DATA_COPY_FUNC
    lsock_set_send_data_copy_func(f_sock, TCP_SEND_DATA_COPY_FUNC, TCP_SEND_DATA_COPY_FUNC_CTX);
#endif
#ifdef TCP_RECV_DATA_COPY_FUNC
    lsock_set_recv_data_copy_func(f_sock, TCP_RECV_DATA_COPY_FUNC, TCP_RECV_DATA_COPY_FUNC_CTX);
#endif

    f_port = port;
    f_start_listen();
}

static void f_tcp_rx_done_cb(int sock_id, const t_lip_tcp_dd *pdd, t_lip_tcp_dd_status status) {
    if (status == LIP_TCP_DD_STATUS_DONE) {
        const uint32_t *data = (const uint32_t*)pdd->buf;
        unsigned len = pdd->trans_cnt/sizeof(data[0]);
        unsigned i;
        for (i=0; i < len; i++) {
            if (data[i]!=f_rcv_cntr) {
                lip_printf(LIP_LOG_MODULE_EXAMPLE, LIP_LOG_LVL_ERROR_HIGH, 0,
                           "lip tcp test: !! recv cntr error: expect 0x%08X, recv 0x%08X, i = %d, len = %d!!\n", f_rcv_cntr, data[i], i, len);
                f_rcv_cntr = data[i];
                f_rcv_errs++;
            }
            f_rcv_cntr++;
            if ((f_rcv_cntr % 0x100000)==0) {
                f_rcv_blocks++;
                lip_printf(LIP_LOG_MODULE_EXAMPLE, LIP_LOG_LVL_PROGRESS, 0,
                           "lip tcp test: recvd %5d blocks, errs = %5d\n", f_rcv_blocks, f_rcv_errs);
            }
        }
    }
}

void lip_sample_tcp_cntr_pull(void) {
    /* продвижение стека */
    lip_pull();
    /* при установленном соединении пробуем передать данные */
    if ((lsock_state(f_sock) == LIP_TCP_SOCK_STATE_ESTABLISHED)) {
        int dd;

        if (!f_con_established) {
            t_lip_ip_addr ip;
            uint16_t port;

            f_con_established = 1;

            lsock_get_remote_ip(f_sock, &ip);
            lsock_get_remote_port(f_sock, &port);

            /* вывод сделан только для IPv4 (который сейчас только и поддерживается) */
            if (ip.ip_type == LIP_IPTYPE_IPv4) {
                lip_printf(LIP_LOG_MODULE_EXAMPLE, LIP_LOG_LVL_PROGRESS, 0,
                           "lip tcp test: established new connection. rem host = %d.%d.%d.%d, %d\n",
                           ip.addr[0], ip.addr[1], ip.addr[2], ip.addr[3], port);
            }
        }


        dd = lsock_recv_dd_in_progress(f_sock);
        while ((dd >= 0) && (dd < TCP_RECV_PART_CNT) && (dd < LIP_TCP_SOCK_RX_DD_CNT)) {
            if (lsock_recv_start(f_sock, tcp_rcv_buf[f_rcv_part], TCP_RECV_PART_SIZE, f_tcp_rx_done_cb) == LIP_ERR_SUCCESS) {
                if (++f_rcv_part == TCP_RECV_PART_CNT)
                    f_rcv_part = 0;
                dd++;
            } else {
                break;
            }

        }

        dd = lsock_send_dd_in_progress(f_sock);
        /* если есть свободное задание на передачу и свободный буфер - добавляем новое */
        if ((dd >= 0) && (dd < TCP_SEND_PART_CNT) && (dd < LIP_TCP_SOCK_TX_DD_CNT)) {
            uint32_t* put_buf = (uint32_t*)tcp_snd_buf[f_snd_part];
            uint32_t cntr = f_snd_cntr;
            for (int i = 0; i < TCP_SEND_PART_SIZE/4; i++)
                put_buf[i] = cntr++;

            if (lsock_send_start(f_sock, tcp_snd_buf[f_snd_part], TCP_SEND_PART_SIZE, NULL) == LIP_ERR_SUCCESS) {
                if (++f_snd_part==TCP_SEND_PART_CNT)
                    f_snd_part = 0;
                f_snd_cntr = cntr;
            }
        }


    }

    /* если другая сторона закрыла соединение - закрываем и сами */
    if (lsock_state(f_sock) == LIP_TCP_SOCK_STATE_CLOSE_WAIT) {
        lip_printf(LIP_LOG_MODULE_EXAMPLE, LIP_LOG_LVL_PROGRESS, 0,
                   "lip tcp test: another side closed connection\n");
        lsock_shutdown(f_sock);
    }


    if (lsock_state(f_sock) == LIP_TCP_SOCK_STATE_CLOSED) {

        lip_printf(LIP_LOG_MODULE_EXAMPLE, LIP_LOG_LVL_PROGRESS, 0,
                   "lip tcp test: connection closed\n");
        f_start_listen();
    }
}
#endif
