/**
 * @file socket_inet_server_shell.c
 * @author LokLiang
 * @brief
 * @version 0.1
 * @date 2023-11-28
 *
 * @copyright Copyright (c) 2024
 *
 * 用于测试 socket 数据接口。
 * 注意事项：
 * 需要配合 wifi_shell 模块使用。
 * 操作：
 * 通过命令行，输入 inet open 打开连接，输入 wifi oa 或 wifi os 选择 wifi 模式；
 * 在 wifi_shell.c::wifi_shell_register() 部分包含了连接信息，可根据这些信息连接或修改必要参数；
 * 无线连接成功后，可通过工具测试发送和接收的速度等。
 */

#include "socket_inet_server_shell.h"
#include "socket_inet.h"
#include "shell/sh.h"

#include <string.h>
#include "os/os.h"

#include "drivers/data_port/sb_data_port.h"

#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF
#define SYS_LOG_DOMAIN "SH"
#include "sys_log.h"

static void _server_tcp_connect_cb(bool is_connect, sb_data_port_t *port);
static void _bind_and_open_tcp(uint8_t hdl_id);
static void _bind_and_open_udp(uint8_t hdl_id, uint16_t listen_port);
static void _unbind_and_close(uint8_t hdl_id);
static void _work_speed_handler(void *arg);
static void _work_read_handler(void *arg);

static struct
{
    socket_listen_tcp_t tcp_listen;

    struct
    {
        sb_data_port_t *data_port;
        os_work_t work_hdl;
        int stype; // 0: udp, 1: tcp
    } hdl[4];

    os_work_t s_speed_work_hdl;
    int recv_size;
    int err_size;
    int s_recv_total_tcp;
    int recv_total;
} s_cm;

SH_CMD_FN(_open);
SH_CMD_FN(_close);

SH_DEF_SUB_CMD(
    sub_wifi,
    SH_SETUP_CMD("open", "打开接口", _open, NULL),   //
    SH_SETUP_CMD("close", "关闭接口", _close, NULL), //
);

SH_DEF_CMD(
    _register_tcp_wifi,
    SH_SETUP_CMD("inet", "操作 WIFI 数据接口", NULL, sub_wifi), //
);

void socket_server_shell_register(void)
{
    sh_register_cmd(&_register_tcp_wifi);

    socket_inet_init();

    os_work_create(&s_cm.s_speed_work_hdl, "work-speed", _work_speed_handler, NULL, OS_PRIORITY_LOWEST);
    os_work_submit(default_os_work_q_hdl, &s_cm.s_speed_work_hdl, 0);

    s_cm.tcp_listen = socket_inet_server_listen_tcp(4278, 2, _server_tcp_connect_cb);
}

void socket_server_shell_unregister(void)
{
    sh_unregister_cmd(&_register_tcp_wifi);
}

static void _server_tcp_connect_cb(bool is_connect, sb_data_port_t *port)
{
    SYS_LOG_INF("%s", is_connect ? "connected" : "disconnected");
    if (is_connect)
    {
        os_thread_sleep(10);
        sb_data_port_write(port, "hello\r\n", 7, 0);
    }
}

static void _bind_and_open_tcp(uint8_t hdl_id)
{
    SYS_ASSERT(hdl_id < __ARRAY_SIZE(s_cm.hdl), "");

    if (s_cm.hdl[hdl_id].data_port == NULL)
    {
        socket_server_bind_t bind_param = {
            .rx_buf_size = 0x1000,
            .rx_event = NULL,
            .rx_resume_work = &s_cm.hdl[hdl_id].work_hdl,
        };
        s_cm.hdl[hdl_id].data_port = socket_inet_server_bind_tcp(s_cm.tcp_listen, &bind_param);
        s_cm.hdl[hdl_id].stype = 1;
    }

    if (s_cm.hdl[hdl_id].data_port)
    {
        sb_data_port_start(s_cm.hdl[hdl_id].data_port);

        os_work_create(&s_cm.hdl[hdl_id].work_hdl, "work-read", _work_read_handler, s_cm.hdl[hdl_id].data_port, OS_PRIORITY_LOWEST);
        os_work_submit(default_os_work_q_hdl, &s_cm.hdl[hdl_id].work_hdl, 0);
    }
}

static void _bind_and_open_udp(uint8_t hdl_id, uint16_t listen_port)
{
    SYS_ASSERT(hdl_id < __ARRAY_SIZE(s_cm.hdl), "");

    if (s_cm.hdl[hdl_id].data_port)
    {
        return;
    }

    socket_server_bind_t bind_param = {
        .rx_buf_size = 0x1000,
        .rx_event = NULL,
        .rx_resume_work = &s_cm.hdl[hdl_id].work_hdl,
    };
    s_cm.hdl[hdl_id].data_port = socket_inet_server_bind_udp(listen_port, &bind_param);
    s_cm.hdl[hdl_id].stype = 0;

    if (s_cm.hdl[hdl_id].data_port)
    {
        sb_data_port_start(s_cm.hdl[hdl_id].data_port);

        os_work_create(&s_cm.hdl[hdl_id].work_hdl, "work-read", _work_read_handler, s_cm.hdl[hdl_id].data_port, OS_PRIORITY_LOWEST);
        os_work_submit(default_os_work_q_hdl, &s_cm.hdl[hdl_id].work_hdl, 0);
    }
}

static void _unbind_and_close(uint8_t hdl_id)
{
    SYS_ASSERT(hdl_id < __ARRAY_SIZE(s_cm.hdl), "");

    if (os_work_is_valid(&s_cm.hdl[hdl_id].work_hdl))
        os_work_delete(&s_cm.hdl[hdl_id].work_hdl);

    if (s_cm.hdl[hdl_id].data_port)
    {
        if (s_cm.hdl[hdl_id].stype == 0)
        {
            socket_inet_server_unbind_udp(s_cm.hdl[hdl_id].data_port);
            s_cm.hdl[hdl_id].data_port = NULL;
        }
        else
        {
            sb_data_port_stop(s_cm.hdl[hdl_id].data_port);
        }
    }
}

static void _work_speed_handler(void *arg)
{
    static os_time_t last_time;
    os_time_t curr_time = os_get_sys_time();
    int time_cost = curr_time - last_time;
    bool recv_flag = false;

    os_work_later(1000);

    time_cost += !time_cost;

    for (int i = 0; i < __ARRAY_SIZE(s_cm.hdl); i++)
    {
        sb_data_port_t *port = s_cm.hdl[i].data_port;
        if (port && sb_data_port_is_started(port))
        {
            if (s_cm.recv_size)
            {
                int result = s_cm.recv_size * 1000 / time_cost;
                s_cm.recv_total += s_cm.recv_size;
                s_cm.recv_size = 0;
                SYS_LOG_INF("val trans speed: %2u.%02u KiB/S, recv total: %d bytes", result / 0x400, result % 0x400 * 100 / 0x400, s_cm.recv_total);
                if (s_cm.err_size)
                {
                    SYS_LOG_DBG("err total: %d times", s_cm.err_size);
                    s_cm.err_size = 0;
                }
                recv_flag = true;
            }
        }
    }

    if (recv_flag == false)
    {
        s_cm.recv_total = 0;
    }

    last_time = curr_time;
}

static void _work_read_handler(void *arg)
{
    sb_data_port_t *port = arg;
    static uint8_t val_log = '0';

    while (sb_data_port_is_started(port))
    {
        uint8_t buf[1500];
        int r_size = sb_data_port_read(port, buf, sizeof(buf), 0);
        if (r_size <= 0)
        {
            break;
        }
        else
        {
            sb_data_port_write(port, buf, r_size, 0);

            s_cm.recv_size += r_size;
            SYS_LOG_DUMP(buf, r_size, 0, 0);
            for (int i = 0; i < r_size; i++)
            {
                if (buf[i] != val_log)
                {
                    s_cm.err_size++;
                    val_log = buf[i];
                }
                val_log = '0' + (val_log - '0' + 1) % 10;
            }

            os_work_later(0);
        }
    }
}

SH_CMD_FN(_open)
{
    _bind_and_open_udp(0, 14550);
    _bind_and_open_udp(1, 14550 + 1);
    _bind_and_open_tcp(2);
    _bind_and_open_tcp(3);
    return 0;
}

SH_CMD_FN(_close)
{
    _unbind_and_close(0);
    _unbind_and_close(1);
    _unbind_and_close(2);
    _unbind_and_close(3);
    return 0;
}

/*
soc base free
wifi open
wifi close
*/
