#include "tcp_client.h"

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

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

#define TCP_CLIENT_CONNECT_TIMEOUT_MS 500
#define TCP_CLIENT_RECONNECT_INTERVAL_MS 3000u
#define TCP_CLIENT_STOP_POLL_MS       50u

static BaseType_t tcp_client_stop_requested(void)
{
    return (app_network_task_stop_requested() != pdFALSE) ? pdTRUE : pdFALSE;
}

static BaseType_t tcp_client_delay_with_stop(uint32_t delay_ms)
{
    uint32_t remaining_ms = delay_ms;

    while (remaining_ms > 0u) {
        uint32_t slice_ms = (remaining_ms > TCP_CLIENT_STOP_POLL_MS) ? TCP_CLIENT_STOP_POLL_MS : remaining_ms;

        if (tcp_client_stop_requested() != pdFALSE) {
            return pdFALSE;
        }

        vTaskDelay(pdMS_TO_TICKS(slice_ms));
        remaining_ms -= slice_ms;
    }

    return (tcp_client_stop_requested() == pdFALSE) ? pdTRUE : pdFALSE;
}

static void tcp_client_abort_and_delete(struct netconn *conn, uint8_t link_index)
{
    struct tcp_pcb *pcb;

    if (conn == NULL) {
        return;
    }

    pcb = conn->pcb.tcp;
    if (pcb != NULL) {
        LOCK_TCPIP_CORE();
        pcb = conn->pcb.tcp;
        if (pcb != NULL) {
            tcp_abort(pcb);
            conn->pcb.tcp = NULL;
            conn->state = NETCONN_NONE;
            debug_log_printf("[CLI] idx=%u abort-close\r\n", (unsigned int)link_index);
        }
        UNLOCK_TCPIP_CORE();
    }

    netconn_delete(conn);
}

static err_t tcp_client_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;
    route_send_result_t route_result;

    netconn_set_recvtimeout(conn, 10);

    for (;;) {
        if (tcp_client_stop_requested() != pdFALSE) {
            return ERR_CLSD;
        }

        err = netconn_recv(conn, &buf);
        if (err == ERR_OK) {
            do {
                void *data;
                uint16_t len;
                netbuf_data(buf, &data, &len);
                route_result = route_send(xTcpRxQueue,
                                          src_endpoint,
                                          uart_endpoint,
                                          (link_index == CONFIG_LINK_C1) ? ROUTE_CONN_C1 : ROUTE_CONN_C2,
                                          (const uint8_t *)data,
                                          len,
                                          pdMS_TO_TICKS(10));
                if (route_result != ROUTE_SEND_OK) {
                    debug_log_printf("[CLI] idx=%u rx-route-fail rc=%s len=%u\r\n",
                                     (unsigned int)link_index,
                                     route_send_result_to_str(route_result),
                                     (unsigned int)len);
                    netbuf_delete(buf);
                    return ERR_CLSD;
                }
            } while (netbuf_next(buf) >= 0);
            netbuf_delete(buf);
        } else if (err == ERR_TIMEOUT) {
            if (tcp_client_stop_requested() != pdFALSE) {
                return ERR_CLSD;
            }
        } else {
            return err;
        }

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

}

static void tcp_client_task(uint8_t link_index)
{
    const device_config_t *cfg;
    struct netconn *conn;
    ip_addr_t remote_ip;
    uint32_t delay_ms;
    err_t err;
    uint8_t first_connect_deferred;

    netconn_thread_init();
    first_connect_deferred = (link_index == CONFIG_LINK_C1) ? 1u : 0u;

    for (;;) {
        if (tcp_client_stop_requested() != pdFALSE) {
            break;
        }

        while ((g_netif_ready == pdFALSE) || (ethernetif_link_is_up() == 0u)) {
            if (tcp_client_stop_requested() != pdFALSE) {
                goto exit_task;
            }
            vTaskDelay(pdMS_TO_TICKS(100));
        }

        cfg = config_get();
        if (cfg->links[link_index].enabled == 0u) {
            if (tcp_client_stop_requested() != pdFALSE) {
                break;
            }
            if (tcp_client_delay_with_stop(500u) == pdFALSE) {
                break;
            }
            continue;
        }

        delay_ms = TCP_CLIENT_RECONNECT_INTERVAL_MS;

        if (first_connect_deferred != 0u) {
            first_connect_deferred = 0u;
            debug_log_write("[CLI] C1 first-connect defer\r\n");
            if (tcp_client_delay_with_stop(delay_ms) == pdFALSE) {
                break;
            }
            continue;
        }

        conn = netconn_new(NETCONN_TCP);
        if (conn == NULL) {
            if (tcp_client_delay_with_stop(delay_ms) == pdFALSE) {
                break;
            }
            continue;
        }

        if (cfg->links[link_index].local_port != 0u) {
            err = netconn_bind(conn, IP_ADDR_ANY, cfg->links[link_index].local_port);
            if (err != ERR_OK) {
                debug_log_printf("[CLI] idx=%u bind-fail err=%d lport=%u\r\n",
                                 (unsigned int)link_index,
                                 (int)err,
                                 (unsigned int)cfg->links[link_index].local_port);
                netconn_delete(conn);
                if (tcp_client_delay_with_stop(delay_ms) == pdFALSE) {
                    break;
                }
                continue;
            }
        }

        IP_ADDR4(&remote_ip,
                 cfg->links[link_index].remote_ip[0],
                 cfg->links[link_index].remote_ip[1],
                 cfg->links[link_index].remote_ip[2],
                 cfg->links[link_index].remote_ip[3]);

        netconn_set_recvtimeout(conn, TCP_CLIENT_CONNECT_TIMEOUT_MS);
        err = netconn_connect(conn, &remote_ip, cfg->links[link_index].remote_port);
        if (err == ERR_OK) {
            debug_log_printf("[CLI] idx=%u connect-ok\r\n", (unsigned int)link_index);
            (void)tcp_client_worker(conn, link_index);
        } else {
            if (err == ERR_TIMEOUT) {
                debug_log_printf("[CLI] idx=%u connect-timeout ms=%u rip=%u.%u.%u.%u rport=%u\r\n",
                                 (unsigned int)link_index,
                                 (unsigned int)TCP_CLIENT_CONNECT_TIMEOUT_MS,
                                 (unsigned int)cfg->links[link_index].remote_ip[0],
                                 (unsigned int)cfg->links[link_index].remote_ip[1],
                                 (unsigned int)cfg->links[link_index].remote_ip[2],
                                 (unsigned int)cfg->links[link_index].remote_ip[3],
                                 (unsigned int)cfg->links[link_index].remote_port);
            } else {
                debug_log_printf("[CLI] idx=%u connect-fail err=%d rip=%u.%u.%u.%u rport=%u\r\n",
                                 (unsigned int)link_index,
                                 (int)err,
                                 (unsigned int)cfg->links[link_index].remote_ip[0],
                                 (unsigned int)cfg->links[link_index].remote_ip[1],
                                 (unsigned int)cfg->links[link_index].remote_ip[2],
                                 (unsigned int)cfg->links[link_index].remote_ip[3],
                                 (unsigned int)cfg->links[link_index].remote_port);
            }
        }

        tcp_client_abort_and_delete(conn, link_index);
        if (tcp_client_stop_requested() != pdFALSE) {
            break;
        }
        if (tcp_client_delay_with_stop(delay_ms) == pdFALSE) {
            break;
        }
    }

exit_task:
    netconn_thread_cleanup();
    app_on_network_task_exit(xTaskGetCurrentTaskHandle());
    vTaskDelete(NULL);
}

void TcpCliTask_C1(void *argument)
{
    (void)argument;
    tcp_client_task(CONFIG_LINK_C1);
}

void TcpCliTask_C2(void *argument)
{
    (void)argument;
    tcp_client_task(CONFIG_LINK_C2);
}
