/**
 * @file qdx_port.c
 * @brief FreeRTOS + WCHNET implementation of the QDX HAL (qdx_port.h)
 *
 * Bridges the WCHNET proprietary TCP/IP stack with the blocking/thread-based
 * interface expected by qdx_tcp_logic.  Uses binary semaphores and ring buffers
 * to convert WCHNET async callbacks into blocking recv semantics.
 */

#include "qdx_port.h"
#include "wchnet.h"
#include "eth_driver.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#include <string.h>
#include <stdio.h>

/* ============================================================
 * Ring-Buffer for receive data
 * ============================================================ */

#define RX_RING_SIZE  2920  /* == WCHNET_TCP_MSS * 2 */

typedef struct {
    uint8_t  buf[RX_RING_SIZE];
    uint16_t head;   /* write index */
    uint16_t tail;   /* read  index */
    uint16_t count;  /* bytes stored */
} RxRingBuf_t;

static void ring_init(RxRingBuf_t *r)
{
    r->head  = 0;
    r->tail  = 0;
    r->count = 0;
}

static uint16_t ring_available(const RxRingBuf_t *r)
{
    return r->count;
}

static uint16_t ring_write(RxRingBuf_t *r, const uint8_t *data, uint16_t len)
{
    uint16_t free = RX_RING_SIZE - r->count;
    if (len > free) len = free;

    for (uint16_t i = 0; i < len; i++) {
        r->buf[r->head] = data[i];
        r->head = (r->head + 1) % RX_RING_SIZE;
    }
    r->count += len;
    return len;
}

static uint16_t ring_read(RxRingBuf_t *r, uint8_t *data, uint16_t max_len)
{
    uint16_t to_read = (r->count < max_len) ? r->count : max_len;

    for (uint16_t i = 0; i < to_read; i++) {
        data[i] = r->buf[r->tail];
        r->tail = (r->tail + 1) % RX_RING_SIZE;
    }
    r->count -= to_read;
    return to_read;
}

/* ============================================================
 * Socket Context — maps WCHNET socket ID to qdx_socket_t
 * ============================================================ */

#define MAX_SOCKETS  2

typedef struct {
    uint8_t             in_use;
    uint8_t             wchnet_sock_id;
    volatile uint8_t    connected;
    RxRingBuf_t         rx_ring;
    SemaphoreHandle_t   rx_sem;         /* binary sem: data available */
    SemaphoreHandle_t   connect_sem;    /* binary sem: connect complete */
} SocketCtx_t;

static SocketCtx_t g_sock_ctx[MAX_SOCKETS];

/* WCHNET per-socket receive buffers (required by WCHNET_ModifyRecvBuf) */
extern u8 SocketRecvBuf[WCHNET_MAX_SOCKET_NUM][RECE_BUF_LEN];

/* WCHNET mutex for thread-safe access to WCHNET APIs */
static SemaphoreHandle_t g_wchnet_mutex;

/* ============================================================
 * Internal helpers
 * ============================================================ */

static SocketCtx_t *alloc_sock_ctx(void)
{
    for (int i = 0; i < MAX_SOCKETS; i++) {
        if (!g_sock_ctx[i].in_use) {
            memset(&g_sock_ctx[i], 0, sizeof(SocketCtx_t));
            g_sock_ctx[i].in_use = 1;
            ring_init(&g_sock_ctx[i].rx_ring);
            g_sock_ctx[i].rx_sem      = xSemaphoreCreateBinary();
            g_sock_ctx[i].connect_sem  = xSemaphoreCreateBinary();
            return &g_sock_ctx[i];
        }
    }
    return NULL;
}

static void free_sock_ctx(SocketCtx_t *ctx)
{
    if (!ctx) return;
    if (ctx->rx_sem)      { vSemaphoreDelete(ctx->rx_sem);      ctx->rx_sem      = NULL; }
    if (ctx->connect_sem) { vSemaphoreDelete(ctx->connect_sem); ctx->connect_sem = NULL; }
    ctx->in_use    = 0;
    ctx->connected = 0;
}

static SocketCtx_t *find_ctx_by_wchnet_id(uint8_t sockid)
{
    for (int i = 0; i < MAX_SOCKETS; i++) {
        if (g_sock_ctx[i].in_use && g_sock_ctx[i].wchnet_sock_id == sockid)
            return &g_sock_ctx[i];
    }
    return NULL;
}

/** Parse dotted-decimal IP string to 4-byte array */
static int parse_ip(const char *ip_str, uint8_t ip[4])
{
    unsigned int a, b, c, d;
    int n = 0, idx = 0;
    const char *p = ip_str;

    a = b = c = d = 0;
    uint32_t vals[4] = {0};
    while (*p && idx < 4) {
        if (*p >= '0' && *p <= '9') {
            vals[idx] = vals[idx] * 10 + (*p - '0');
        } else if (*p == '.') {
            idx++;
        } else {
            return -1;
        }
        p++;
    }
    if (idx != 3) return -1;
    for (int i = 0; i < 4; i++) {
        if (vals[i] > 255) return -1;
        ip[i] = (uint8_t)vals[i];
    }
    return 0;
}

/* ============================================================
 * Public: called from WCHNET_HandleSockInt (task context)
 * ============================================================ */

void qdx_port_sock_recv_notify(uint8_t sockid)
{
    SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid);
    if (!ctx) return;

    /* Read data from WCHNET into ring buffer */
    uint8_t tmp[512];
    uint32_t len;
    uint32_t total = 0;
    while (1) {
        len = sizeof(tmp);
        uint8_t err = WCHNET_SocketRecv(sockid, tmp, &len);
        if (len == 0) break;
        ring_write(&ctx->rx_ring, tmp, (uint16_t)len);
        total += len;
        if (err != WCHNET_ERR_SUCCESS) break;
    }
    DBG_HB("recv_notify sock%d: %u bytes, ring=%u\r\n",
             sockid, (unsigned)total, (unsigned)ring_available(&ctx->rx_ring));
    /* Wake blocking recv thread */
    xSemaphoreGive(ctx->rx_sem);
}

void qdx_port_sock_connect_notify(uint8_t sockid)
{
    SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid);
    if (!ctx) return;
    ctx->connected = 1;
    DBG_NET("connect_notify sock%d\r\n", sockid);
    xSemaphoreGive(ctx->connect_sem);
}

void qdx_port_sock_disconnect_notify(uint8_t sockid)
{
    SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid);
    if (!ctx) return;
    ctx->connected = 0;
    DBG_NET("disconnect_notify sock%d\r\n", sockid);
    /* Wake recv thread so it can detect disconnect */
    xSemaphoreGive(ctx->rx_sem);
}

/* ============================================================
 * Init — call once before scheduler starts
 * ============================================================ */

void qdx_port_init(void)
{
    memset(g_sock_ctx, 0, sizeof(g_sock_ctx));
    g_wchnet_mutex = xSemaphoreCreateMutex();
    DBG_INIT("qdx_port init done, mutex=%p\r\n", g_wchnet_mutex);
}

/* ============================================================
 * Time & Delay
 * ============================================================ */

uint32_t qdx_port_get_tick_ms(void)
{
    return xTaskGetTickCount() * portTICK_PERIOD_MS;
}

void qdx_port_delay_ms(uint32_t ms)
{
    vTaskDelay(pdMS_TO_TICKS(ms));
}

/* ============================================================
 * Mutex
 * ============================================================ */

qdx_mutex_t qdx_port_mutex_create(void)
{
    return (qdx_mutex_t)xSemaphoreCreateMutex();
}

void qdx_port_mutex_lock(qdx_mutex_t mutex)
{
    if (mutex)
        xSemaphoreTake((SemaphoreHandle_t)mutex, portMAX_DELAY);
}

void qdx_port_mutex_unlock(qdx_mutex_t mutex)
{
    if (mutex)
        xSemaphoreGive((SemaphoreHandle_t)mutex);
}

void qdx_port_mutex_delete(qdx_mutex_t mutex)
{
    if (mutex)
        vSemaphoreDelete((SemaphoreHandle_t)mutex);
}

/* ============================================================
 * Thread
 * ============================================================ */

int8_t qdx_port_thread_create(const char *name, qdx_thread_entry_t entry,
                              void *arg, uint32_t stack_size, uint8_t priority)
{
    /* stack_size from caller is in bytes; xTaskCreate expects words */
    uint32_t stack_words = stack_size / sizeof(StackType_t);
    if (stack_words < configMINIMAL_STACK_SIZE)
        stack_words = configMINIMAL_STACK_SIZE;

    BaseType_t ret = xTaskCreate((TaskFunction_t)entry, name,
                                 (uint16_t)stack_words, arg,
                                 (UBaseType_t)priority, NULL);
    DBG_INIT("thread_create \"%s\" stack=%d pri=%d -> %s\r\n",
             name, (int)stack_words, (int)priority, (ret == pdPASS) ? "OK" : "FAIL");
    return (ret == pdPASS) ? 0 : -1;
}

/* ============================================================
 * TCP Socket — connect / send / recv / close
 * ============================================================ */

qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port)
{
    uint8_t dest_ip[4];
    if (parse_ip(ip, dest_ip) != 0) {
        DBG_ERR("bad IP \"%s\"\r\n", ip);
        return NULL;
    }

    DBG_NET("connecting to %s:%d\r\n", ip, port);

    SocketCtx_t *ctx = alloc_sock_ctx();
    if (!ctx) {
        DBG_ERR("no free SocketCtx\r\n");
        return NULL;
    }

    SOCK_INF sock_inf;
    memset(&sock_inf, 0, sizeof(sock_inf));
    sock_inf.ProtoType = PROTO_TYPE_TCP;
    sock_inf.SourPort  = 0;  /* let WCHNET assign ephemeral port */
    sock_inf.DesPort   = port;
    memcpy(sock_inf.IPAddr, dest_ip, 4);

    DBG_NET("SOCK_INF: proto=%d dst=%d.%d.%d.%d:%d\r\n",
             sock_inf.ProtoType,
             sock_inf.IPAddr[0], sock_inf.IPAddr[1],
             sock_inf.IPAddr[2], sock_inf.IPAddr[3],
             sock_inf.DesPort);

    uint8_t wchnet_id = 0;

    xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
    uint8_t err = WCHNET_SocketCreat(&wchnet_id, &sock_inf);
    xSemaphoreGive(g_wchnet_mutex);

    if (err != WCHNET_ERR_SUCCESS) {
        DBG_ERR("SocketCreat fail %02X\r\n", err);
        free_sock_ctx(ctx);
        return NULL;
    }
    DBG_NET("SocketCreat OK, wchnet_id=%d\r\n", wchnet_id);

    ctx->wchnet_sock_id = wchnet_id;

    /* Assign WCHNET receive buffer */
    WCHNET_ModifyRecvBuf(wchnet_id,
                         (uint32_t)SocketRecvBuf[wchnet_id], RECE_BUF_LEN);

    xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
    err = WCHNET_SocketConnect(wchnet_id);
    xSemaphoreGive(g_wchnet_mutex);

    if (err != WCHNET_ERR_SUCCESS) {
        DBG_ERR("SocketConnect fail %02X\r\n", err);
        WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST);
        free_sock_ctx(ctx);
        return NULL;
    }
    DBG_NET("SocketConnect OK (err=0x%02X), waiting connect_sem (5s)...\r\n", err);

    /* Block until SINT_STAT_CONNECT or 5s timeout */
    uint32_t t0 = xTaskGetTickCount();
    BaseType_t sem_ret = xSemaphoreTake(ctx->connect_sem, pdMS_TO_TICKS(5000));
    uint32_t elapsed = (xTaskGetTickCount() - t0) * portTICK_PERIOD_MS;

    if (sem_ret != pdTRUE) {
        DBG_ERR("connect_sem TIMEOUT after %d ms -> %d.%d.%d.%d:%d\r\n",
                 (int)elapsed, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], port);
        DBG_ERR("  ctx->connected=%d wchnet_id=%d\r\n", ctx->connected, wchnet_id);
        WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST);
        free_sock_ctx(ctx);
        return NULL;
    }
    DBG_NET("connect_sem got after %d ms, connected=%d\r\n", (int)elapsed, ctx->connected);

    if (!ctx->connected) {
        WCHNET_SocketClose(wchnet_id, TCP_CLOSE_RST);
        free_sock_ctx(ctx);
        return NULL;
    }

#if KEEPALIVE_ENABLE
    WCHNET_SocketSetKeepLive(wchnet_id, ENABLE);
#endif

    DBG_NET("connected sock %d -> %d.%d.%d.%d:%d\r\n",
           wchnet_id, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], port);
    return (qdx_socket_t)ctx;
}

int32_t qdx_port_tcp_send(qdx_socket_t sock, const uint8_t *data, uint32_t len)
{
    SocketCtx_t *ctx = (SocketCtx_t *)sock;
    if (!ctx || !ctx->connected)
        return -1;

    uint32_t total_sent = 0;
    const uint8_t *ptr = data;
    uint32_t remaining = len;
    uint8_t retries = 0;

    while (remaining > 0) {
        uint32_t send_len = remaining;

        xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
        uint8_t err = WCHNET_SocketSend(ctx->wchnet_sock_id, (uint8_t *)ptr, &send_len);
        xSemaphoreGive(g_wchnet_mutex);

        if (err != WCHNET_ERR_SUCCESS && send_len == 0) {
            /* WCHNET send buffer full — yield so wchnet task can flush */
            if (++retries > 50) {
                DBG_ERR("send fail after retries, err=0x%02X\r\n", err);
                return -1;
            }
            vTaskDelay(pdMS_TO_TICKS(2));
            continue;
        }
        retries = 0;
        ptr += send_len;
        remaining -= send_len;
        total_sent += send_len;

        if (remaining > 0) {
            /* Partial send — yield to let wchnet task drain the buffer */
            vTaskDelay(pdMS_TO_TICKS(2));
        }
    }

    return (int32_t)total_sent;
}

int32_t qdx_port_tcp_recv(qdx_socket_t sock, uint8_t *buf, uint32_t max_len)
{
    SocketCtx_t *ctx = (SocketCtx_t *)sock;
    if (!ctx)
        return -1;

    /* Check ring buffer first */
    uint16_t got = ring_read(&ctx->rx_ring, buf, (uint16_t)max_len);
    if (got > 0)
        return (int32_t)got;

    /* No data — check if disconnected */
    if (!ctx->connected)
        return -1;

    /* Block on semaphore up to 100ms */
    xSemaphoreTake(ctx->rx_sem, pdMS_TO_TICKS(100));

    /* Try again after wake */
    got = ring_read(&ctx->rx_ring, buf, (uint16_t)max_len);
    if (got > 0)
        return (int32_t)got;

    /* Still no data — disconnected? */
    if (!ctx->connected)
        return -1;

    return 0;  /* timeout, no data */
}

void qdx_port_tcp_close(qdx_socket_t sock)
{
    SocketCtx_t *ctx = (SocketCtx_t *)sock;
    if (!ctx) return;

    if (ctx->in_use) {
        xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
        /* Use TCP_CLOSE_RST to immediately release the WCHNET socket slot.
         * TCP_CLOSE_NORMAL (0) causes TIME_WAIT which holds the slot for
         * an extended period, preventing reconnection with only 2 sockets. */
        WCHNET_SocketClose(ctx->wchnet_sock_id, TCP_CLOSE_RST);
        xSemaphoreGive(g_wchnet_mutex);
    }
    ring_init(&ctx->rx_ring);
    free_sock_ctx(ctx);
}

/* ============================================================
 * WCHNET global lock — exposed for task_wchnet_entry in main.c
 * All WCHNET API calls must be serialized through this mutex.
 * ============================================================ */

void qdx_port_net_lock(void)
{
    if (g_wchnet_mutex)
        xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
}

void qdx_port_net_unlock(void)
{
    if (g_wchnet_mutex)
        xSemaphoreGive(g_wchnet_mutex);
}
