#include "uart_transport.h"

#include <limits.h>
#include <string.h>

#include "driver/uart.h"
#include "esp_log.h"

#define UART_PORT_NUM UART_NUM_0
#define UART_BAUD_RATE 115200
#define UART_RX_BUF_SIZE 4096
#define UART_TX_BUF_SIZE 4096

#define UART_MAX_FRAME 4096
#define UART_RX_RING_CAP (UART_MAX_FRAME + 32)

typedef struct {
    uint8_t buf[UART_RX_RING_CAP];
    size_t have;
} rx_ring_t;

static const char *TAG = "uart_transport";

static bool s_inited = false;
static rx_ring_t s_uart_rx;

static int parse_frame_from_ring(rx_ring_t *ring, uint8_t *payload, size_t payload_max, size_t *out_len)
{
    if (ring == NULL || payload == NULL || out_len == NULL) {
        return -1;
    }

    if (ring->have < 4) {
        return 1;
    }

    for (;;) {
        uint32_t len = ((uint32_t)ring->buf[0] << 24) |
                       ((uint32_t)ring->buf[1] << 16) |
                       ((uint32_t)ring->buf[2] << 8) |
                       (uint32_t)ring->buf[3];

        if (len == 0 || len > payload_max) {
            memmove(ring->buf, ring->buf + 1, ring->have - 1);
            ring->have -= 1;
            if (ring->have < 4) {
                return 1;
            }
            continue;
        }

        if (ring->have < (size_t)len + 4U) {
            return 1;
        }

        memcpy(payload, ring->buf + 4, len);
        *out_len = (size_t)len;

        size_t rem = ring->have - ((size_t)len + 4U);
        if (rem > 0) {
            memmove(ring->buf, ring->buf + 4U + len, rem);
        }
        ring->have = rem;

        return 0;
    }
}

static void read_into_ring(rx_ring_t *ring, const uint8_t *data, size_t n)
{
    if (ring == NULL || data == NULL || n == 0) {
        return;
    }

    if (ring->have + n > sizeof(ring->buf)) {
        ring->have = 0;
    }

    memcpy(ring->buf + ring->have, data, n);
    ring->have += n;
}

int uart_transport_init(void)
{
    if (s_inited) {
        return 0;
    }

    const uart_config_t uart_cfg = {
        .baud_rate = UART_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };

    if (uart_driver_install(UART_PORT_NUM, UART_RX_BUF_SIZE, UART_TX_BUF_SIZE, 0, NULL, 0) != ESP_OK) {
        ESP_LOGE(TAG, "uart_driver_install failed");
        return -1;
    }

    if (uart_param_config(UART_PORT_NUM, &uart_cfg) != ESP_OK) {
        ESP_LOGE(TAG, "uart_param_config failed");
        return -1;
    }

    if (uart_set_pin(UART_PORT_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE) != ESP_OK) {
        ESP_LOGE(TAG, "uart_set_pin failed");
        return -1;
    }

    memset(&s_uart_rx, 0, sizeof(s_uart_rx));
    s_inited = true;

    ESP_LOGI(TAG, "UART transport initialized on UART0 @ %d", UART_BAUD_RATE);
    return 0;
}

int usb_transport_init(void)
{
    return uart_transport_init();
}

int cdc_recv_frame(uint8_t *payload, size_t payload_max, size_t *out_len)
{
    uint8_t tmp[128];

    if (payload == NULL || out_len == NULL) {
        return -1;
    }

    if (!s_inited) {
        return 1;
    }

    for (;;) {
        int n = uart_read_bytes(UART_PORT_NUM, tmp, sizeof(tmp), 0);
        if (n <= 0) {
            break;
        }
        read_into_ring(&s_uart_rx, tmp, (size_t)n);
    }

    return parse_frame_from_ring(&s_uart_rx, payload, payload_max, out_len);
}

int cdc_send_frame(const uint8_t *payload, size_t len)
{
    uint8_t hdr[4];

    if (!s_inited || payload == NULL || len == 0 || len > UINT_MAX) {
        return -1;
    }

    hdr[0] = (uint8_t)((len >> 24) & 0xFFU);
    hdr[1] = (uint8_t)((len >> 16) & 0xFFU);
    hdr[2] = (uint8_t)((len >> 8) & 0xFFU);
    hdr[3] = (uint8_t)(len & 0xFFU);

    int wrote = uart_write_bytes(UART_PORT_NUM, (const char *)hdr, sizeof(hdr));
    if (wrote != (int)sizeof(hdr)) {
        return -1;
    }

    wrote = uart_write_bytes(UART_PORT_NUM, (const char *)payload, len);
    if (wrote != (int)len) {
        return -1;
    }

    if (uart_wait_tx_done(UART_PORT_NUM, pdMS_TO_TICKS(2000)) != ESP_OK) {
        return -1;
    }

    return 0;
}

int webusb_recv_frame(uint8_t *payload, size_t payload_max, size_t *out_len)
{
    (void)payload;
    (void)payload_max;
    (void)out_len;
    return 1;
}

int webusb_send_frame(const uint8_t *payload, size_t len)
{
    (void)payload;
    (void)len;
    return -1;
}
