#include "tcp_server.h"

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "lwip/api.h"
#include "lwip/ip_addr.h"

#include "app_runtime.h"
#include "config.h"
#include "debug_log.h"
#include "route_msg.h"

static void tcp_server_worker(struct netconn *conn, uint8_t link_index)
{
    struct netbuf *buf;
    const device_config_t *cfg = config_get();
    uint8_t uart_endpoint = config_uart_index_to_endpoint(cfg->links[link_index].uart);
    uint8_t src_endpoint = config_link_index_to_endpoint(link_index);
    err_t err;
    route_msg_t *tx_msg;

    netconn_set_recvtimeout(conn, 10);

    for (;;) {
        err = netconn_recv(conn, &buf);
        if (err == ERR_OK) {
            do {
                void *data;
                uint16_t len;
                netbuf_data(buf, &data, &len);
                (void)route_send(xTcpRxQueue,
                                 src_endpoint,
                                 uart_endpoint,
                                 (link_index == CONFIG_LINK_S1) ? ROUTE_CONN_S1 : ROUTE_CONN_S2,
                                 (const uint8_t *)data,
                                 len,
                                 pdMS_TO_TICKS(10));
            } while (netbuf_next(buf) >= 0);
            netbuf_delete(buf);
        } else if (err != ERR_TIMEOUT) {
            break;
        }

        while (xQueueReceive(xLinkTxQueues[link_index], &tx_msg, 0) == pdPASS) {
            err = netconn_write(conn, tx_msg->data, tx_msg->len, NETCONN_COPY);
            route_msg_free(tx_msg);
            if (err != ERR_OK) {
                return;
            }
        }
    }
}

static void tcp_server_task(uint8_t link_index)
{
    const device_config_t *cfg;
    struct netconn *listener;
    struct netconn *newconn;

    netconn_thread_init();

    for (;;) {
        while (g_netif_ready == pdFALSE) {
            vTaskDelay(pdMS_TO_TICKS(100));
        }

        cfg = config_get();
        if (cfg->links[link_index].enabled == 0u) {
            vTaskDelay(pdMS_TO_TICKS(500));
            continue;
        }

        listener = netconn_new(NETCONN_TCP);
        if (listener == NULL) {
            vTaskDelay(pdMS_TO_TICKS(500));
            continue;
        }

        if (netconn_bind(listener, IP_ADDR_ANY, cfg->links[link_index].local_port) != ERR_OK ||
            netconn_listen(listener) != ERR_OK) {
            netconn_delete(listener);
            vTaskDelay(pdMS_TO_TICKS(500));
            continue;
        }

        for (;;) {
            if (cfg->links[link_index].enabled == 0u) {
                break;
            }
            if (netconn_accept(listener, &newconn) == ERR_OK) {
                tcp_server_worker(newconn, link_index);
                netconn_close(newconn);
                netconn_delete(newconn);
            }
        }

        netconn_close(listener);
        netconn_delete(listener);
    }
}

void TcpSrvTask_S1(void *argument)
{
    (void)argument;
    tcp_server_task(CONFIG_LINK_S1);
}

void TcpSrvTask_S2(void *argument)
{
    (void)argument;
    tcp_server_task(CONFIG_LINK_S2);
}
