/**
 * @file uart_trans.c
 * @brief Bare-metal UART DMA/IDLE transport and MUX helpers.
 */

#include "uart_trans.h"

#include "../Core/Inc/usart.h"

#include <string.h>

#define UART_MUX_SYNC  0x7Eu
#define UART_MUX_TAIL  0x7Fu

typedef struct {
    UART_HandleTypeDef *huart;
    uint8_t rx_dma_buffer[UART_RX_DMA_BUFFER_SIZE];
    uint8_t tx_dma_buffer[UART_TX_DMA_BUFFER_SIZE];
    uint8_t rx_ring[UART_RX_RING_BUFFER_SIZE];
    uint8_t tx_ring[UART_TX_RING_BUFFER_SIZE];
    volatile uint16_t rx_dma_read_index;
    volatile uint16_t rx_head;
    volatile uint16_t rx_tail;
    volatile uint16_t tx_head;
    volatile uint16_t tx_tail;
    volatile uint16_t tx_dma_len;
    volatile bool tx_busy;
    uart_config_t config;
    uart_stats_t stats;
    bool initialized;
    bool running;
} uart_channel_ctx_t;

static uart_channel_ctx_t g_channels[UART_CHANNEL_MAX];

static uint16_t ring_used(uint16_t head, uint16_t tail, uint16_t size)
{
    return (head >= tail) ? (head - tail) : (size - tail + head);
}

static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
{
    return (uint16_t)(size - ring_used(head, tail, size) - 1u);
}

static bool ring_peek_byte(const uart_channel_ctx_t *ctx, uint16_t offset, uint8_t *out)
{
    uint16_t head;
    uint16_t tail;

    if (ctx == NULL || out == NULL) {
        return false;
    }

    head = ctx->rx_head;
    tail = ctx->rx_tail;
    if (offset >= ring_used(head, tail, UART_RX_RING_BUFFER_SIZE)) {
        return false;
    }

    *out = ctx->rx_ring[(tail + offset) % UART_RX_RING_BUFFER_SIZE];
    return true;
}

static bool ring_peek_span(const uart_channel_ctx_t *ctx, uint16_t offset, uint8_t *data, uint16_t len)
{
    if (ctx == NULL || data == NULL) {
        return false;
    }

    for (uint16_t i = 0u; i < len; ++i) {
        if (!ring_peek_byte(ctx, (uint16_t)(offset + i), &data[i])) {
            return false;
        }
    }

    return true;
}

static void ring_drop_bytes(uart_channel_ctx_t *ctx, uint16_t len)
{
    if (ctx == NULL) {
        return;
    }

    while (len > 0u && ctx->rx_tail != ctx->rx_head) {
        ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % UART_RX_RING_BUFFER_SIZE);
        --len;
    }
}

static int apply_uart_config(uart_channel_t channel)
{
    uart_channel_ctx_t *ctx = &g_channels[channel];
    if (ctx->huart == NULL) {
        return -1;
    }

    if (ctx->running) {
        HAL_UART_DMAStop(ctx->huart);
        ctx->running = false;
    }

    ctx->huart->Init.BaudRate = ctx->config.baudrate;
    ctx->huart->Init.WordLength = UART_WORDLENGTH_8B;
    ctx->huart->Init.StopBits = UART_STOPBITS_1;
    ctx->huart->Init.Parity = UART_PARITY_NONE;
    return (HAL_UART_Init(ctx->huart) == HAL_OK) ? 0 : -1;
}

static void process_rx_snapshot(uart_channel_t channel, uint16_t dma_write_index)
{
    uart_channel_ctx_t *ctx = &g_channels[channel];

    while (ctx->rx_dma_read_index != dma_write_index) {
        uint16_t next_head = (uint16_t)((ctx->rx_head + 1u) % UART_RX_RING_BUFFER_SIZE);
        if (next_head == ctx->rx_tail) {
            ctx->stats.errors++;
            break;
        }

        ctx->rx_ring[ctx->rx_head] = ctx->rx_dma_buffer[ctx->rx_dma_read_index];
        ctx->rx_head = next_head;
        ctx->rx_dma_read_index = (uint16_t)((ctx->rx_dma_read_index + 1u) % UART_RX_DMA_BUFFER_SIZE);
        ctx->stats.rx_bytes++;
    }
}

static void kick_tx(uart_channel_t channel)
{
    uart_channel_ctx_t *ctx = &g_channels[channel];
    uint16_t available;
    uint16_t chunk;

    if (!ctx->running || ctx->tx_busy) {
        return;
    }

    available = ring_used(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE);
    if (available == 0u) {
        return;
    }

    chunk = available;
    if (chunk > UART_TX_DMA_BUFFER_SIZE) {
        chunk = UART_TX_DMA_BUFFER_SIZE;
    }

    for (uint16_t i = 0; i < chunk; ++i) {
        ctx->tx_dma_buffer[i] = ctx->tx_ring[ctx->tx_tail];
        ctx->tx_tail = (uint16_t)((ctx->tx_tail + 1u) % UART_TX_RING_BUFFER_SIZE);
    }

    ctx->tx_dma_len = chunk;
    ctx->tx_busy = true;
    ctx->stats.tx_packets++;

    if (HAL_UART_Transmit_DMA(ctx->huart, ctx->tx_dma_buffer, chunk) != HAL_OK) {
        ctx->tx_busy = false;
        ctx->stats.errors++;
    }
}

int uart_trans_init(void)
{
    memset(g_channels, 0, sizeof(g_channels));
    g_channels[UART_CHANNEL_U0].huart = &huart2;
    g_channels[UART_CHANNEL_U1].huart = &huart3;
    g_channels[UART_CHANNEL_U0].config.baudrate = UART_DEFAULT_BAUDRATE;
    g_channels[UART_CHANNEL_U1].config.baudrate = UART_DEFAULT_BAUDRATE;
    g_channels[UART_CHANNEL_U0].initialized = true;
    g_channels[UART_CHANNEL_U1].initialized = true;
    return 0;
}

int uart_trans_config(uart_channel_t channel, const uart_config_t *config)
{
    if (channel >= UART_CHANNEL_MAX || config == NULL) {
        return -1;
    }
    g_channels[channel].config = *config;
    return apply_uart_config(channel);
}

int uart_trans_start(uart_channel_t channel)
{
    uart_channel_ctx_t *ctx;

    if (channel >= UART_CHANNEL_MAX) {
        return -1;
    }

    ctx = &g_channels[channel];
    if (!ctx->initialized || ctx->huart == NULL) {
        return -1;
    }

    ctx->rx_dma_read_index = 0u;
    ctx->rx_head = 0u;
    ctx->rx_tail = 0u;
    ctx->tx_head = 0u;
    ctx->tx_tail = 0u;
    ctx->tx_dma_len = 0u;
    ctx->tx_busy = false;
    memset(&ctx->stats, 0, sizeof(ctx->stats));

    __HAL_UART_ENABLE_IT(ctx->huart, UART_IT_IDLE);
    if (HAL_UART_Receive_DMA(ctx->huart, ctx->rx_dma_buffer, UART_RX_DMA_BUFFER_SIZE) != HAL_OK) {
        return -1;
    }

    ctx->running = true;
    return 0;
}

int uart_trans_stop(uart_channel_t channel)
{
    if (channel >= UART_CHANNEL_MAX) {
        return -1;
    }
    HAL_UART_DMAStop(g_channels[channel].huart);
    g_channels[channel].running = false;
    g_channels[channel].tx_busy = false;
    return 0;
}

void uart_trans_poll(void)
{
    kick_tx(UART_CHANNEL_U0);
    kick_tx(UART_CHANNEL_U1);
}

uint16_t uart_trans_rx_available(uart_channel_t channel)
{
    if (channel >= UART_CHANNEL_MAX) {
        return 0u;
    }
    return ring_used(g_channels[channel].rx_head, g_channels[channel].rx_tail, UART_RX_RING_BUFFER_SIZE);
}

uint16_t uart_trans_read(uart_channel_t channel, uint8_t *data, uint16_t max_len)
{
    uart_channel_ctx_t *ctx;
    uint16_t copied = 0u;

    if (channel >= UART_CHANNEL_MAX || data == NULL || max_len == 0u) {
        return 0u;
    }

    ctx = &g_channels[channel];
    while (copied < max_len && ctx->rx_tail != ctx->rx_head) {
        data[copied++] = ctx->rx_ring[ctx->rx_tail];
        ctx->rx_tail = (uint16_t)((ctx->rx_tail + 1u) % UART_RX_RING_BUFFER_SIZE);
    }
    if (copied > 0u) {
        ctx->stats.rx_packets++;
    }
    return copied;
}

uint16_t uart_trans_write(uart_channel_t channel, const uint8_t *data, uint16_t len)
{
    uart_channel_ctx_t *ctx;
    uint16_t written = 0u;

    if (channel >= UART_CHANNEL_MAX || data == NULL || len == 0u) {
        return 0u;
    }

    ctx = &g_channels[channel];
    while (written < len && ring_free(ctx->tx_head, ctx->tx_tail, UART_TX_RING_BUFFER_SIZE) > 0u) {
        ctx->tx_ring[ctx->tx_head] = data[written++];
        ctx->tx_head = (uint16_t)((ctx->tx_head + 1u) % UART_TX_RING_BUFFER_SIZE);
    }

    if (written < len) {
        ctx->stats.errors++;
    }

    kick_tx(channel);
    return written;
}

uint16_t uart_trans_tx_free(uart_channel_t channel)
{
    if (channel >= UART_CHANNEL_MAX) {
        return 0u;
    }
    return ring_free(g_channels[channel].tx_head, g_channels[channel].tx_tail, UART_TX_RING_BUFFER_SIZE);
}

void uart_trans_get_stats(uart_channel_t channel, uart_stats_t *stats)
{
    if (channel < UART_CHANNEL_MAX && stats != NULL) {
        *stats = g_channels[channel].stats;
    }
}

void uart_trans_reset_stats(uart_channel_t channel)
{
    if (channel < UART_CHANNEL_MAX) {
        memset(&g_channels[channel].stats, 0, sizeof(g_channels[channel].stats));
    }
}

void uart_trans_idle_handler(uart_channel_t channel)
{
    UART_HandleTypeDef *huart;
    uint16_t dma_write_index;

    if (channel >= UART_CHANNEL_MAX) {
        return;
    }
    huart = g_channels[channel].huart;
    g_channels[channel].stats.idle_events++;
    dma_write_index = (uint16_t)(UART_RX_DMA_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx));
    if (dma_write_index >= UART_RX_DMA_BUFFER_SIZE) {
        dma_write_index = 0u;
    }
    process_rx_snapshot(channel, dma_write_index);
}

void uart_trans_rx_half_cplt_handler(uart_channel_t channel)
{
    if (channel >= UART_CHANNEL_MAX) {
        return;
    }
    g_channels[channel].stats.rx_half_events++;
    process_rx_snapshot(channel, UART_RX_DMA_BUFFER_SIZE / 2u);
}

void uart_trans_rx_cplt_handler(uart_channel_t channel)
{
    if (channel >= UART_CHANNEL_MAX) {
        return;
    }
    g_channels[channel].stats.rx_full_events++;
    process_rx_snapshot(channel, 0u);
}

void uart_trans_tx_cplt_handler(uart_channel_t channel)
{
    if (channel >= UART_CHANNEL_MAX) {
        return;
    }
    g_channels[channel].tx_busy = false;
    g_channels[channel].stats.tx_bytes += g_channels[channel].tx_dma_len;
    g_channels[channel].tx_dma_len = 0u;
    kick_tx(channel);
}

bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame)
{
    uart_channel_ctx_t *ctx;
    uint8_t header[4];
    uint8_t tail_byte;
    uint16_t available;
    uint16_t payload_len;
    uint16_t sync_offset;
    uint16_t total_len;

    if (channel >= UART_CHANNEL_MAX || frame == NULL) {
        return false;
    }

    ctx = &g_channels[channel];

    for (;;) {
        available = uart_trans_rx_available(channel);
        if (available < 6u) {
            return false;
        }

        sync_offset = available;
        for (uint16_t i = 0u; i < available; ++i) {
            uint8_t byte = 0u;
            if (!ring_peek_byte(ctx, i, &byte)) {
                return false;
            }
            if (byte == UART_MUX_SYNC) {
                sync_offset = i;
                break;
            }
        }

        if (sync_offset == available) {
            ring_drop_bytes(ctx, available);
            return false;
        }

        if (sync_offset > 0u) {
            ring_drop_bytes(ctx, sync_offset);
            available = (uint16_t)(available - sync_offset);
        }

        if (available < 6u) {
            return false;
        }

        if (!ring_peek_span(ctx, 1u, header, sizeof(header))) {
            return false;
        }

        payload_len = (uint16_t)(((uint16_t)header[0] << 8) | header[1]);
        if (payload_len > sizeof(frame->payload)) {
            ring_drop_bytes(ctx, 1u);
            continue;
        }

        total_len = (uint16_t)(payload_len + 6u);
        if (available < total_len) {
            return false;
        }

        if (!ring_peek_byte(ctx, (uint16_t)(total_len - 1u), &tail_byte)) {
            return false;
        }
        if (tail_byte != UART_MUX_TAIL) {
            ring_drop_bytes(ctx, 1u);
            continue;
        }

        frame->src_id = header[2];
        frame->dst_mask = header[3];
        frame->payload_len = payload_len;
        if (payload_len > 0u) {
            if (!ring_peek_span(ctx, 5u, frame->payload, payload_len)) {
                return false;
            }
        }

        ring_drop_bytes(ctx, total_len);
        return true;
    }
}

bool uart_mux_encode_frame(uint8_t src_id,
                           uint8_t dst_mask,
                           const uint8_t *payload,
                           uint16_t payload_len,
                           uint8_t *out,
                           uint16_t *out_len,
                           uint16_t out_capacity)
{
    uint16_t frame_len;

    if (out == NULL || out_len == NULL) {
        return false;
    }
    frame_len = (uint16_t)(payload_len + 6u);
    if (frame_len > out_capacity) {
        return false;
    }

    out[0] = UART_MUX_SYNC;
    out[1] = (uint8_t)(payload_len >> 8);
    out[2] = (uint8_t)(payload_len & 0xFFu);
    out[3] = src_id;
    out[4] = dst_mask;
    if (payload_len > 0u && payload != NULL) {
        memcpy(&out[5], payload, payload_len);
    }
    out[5 + payload_len] = UART_MUX_TAIL;
    *out_len = frame_len;
    return true;
}
