// components/evse_link/src/evse_link_framing.c
//
// EVSE-Link framing (RS-485 HALF DUPLEX via UART driver)
// - Usa UART_MODE_RS485_HALF_DUPLEX (driver controla RTS => DE//RE)
// - Configura RX timeout + RX full threshold para evitar “len=120”
// - Remove controlo manual de GPIO do RTS
//
// Requisitos:
// - MAX3485 com DE e /RE juntos ligados ao RTS_PIN
// - RTS_PIN definido em evse_link_framing.h (ex.: GPIO2; recomendado mudar no futuro)
//
// Notas:
// - Se o teu hardware inverter a lógica do RTS (raro), define EVSE_LINK_RTS_INVERT=1

#include "evse_link_framing.h"

#include "driver/uart.h"
#include "freertos/semphr.h"

#include "esp_log.h"
#include "esp_err.h"
#include "esp_timer.h"

#include <string.h>
#include <stdint.h>
#include <stdbool.h>

static const char *TAG = "evse_framing";

static SemaphoreHandle_t tx_mutex = NULL;
static uint8_t seq = 0;
static evse_link_frame_cb_t rx_cb = NULL;

static bool s_framing_inited = false;

// ---- Tunables (fallbacks) ----
#ifndef EVSE_LINK_INTERBYTE_TIMEOUT_US
// Timeout inter-byte: se um frame morrer a meio, reseta o parser
#define EVSE_LINK_INTERBYTE_TIMEOUT_US 5000
#endif

// Rate-limit para warnings (em microsegundos)
#define LOG_RATELIMIT_US 1000000 // 1s

// RX tuning (evita acumular ~120 bytes antes de "acordar")
#ifndef EVSE_LINK_RX_TIMEOUT
// Timeout do UART TOUT feature (em "character times"). 3..10 funciona bem.
#define EVSE_LINK_RX_TIMEOUT 3
#endif

#ifndef EVSE_LINK_RX_FULL_THRESH
// Gera interrupção quando FIFO tem pelo menos N bytes (1..120). 4 é um bom default.
#define EVSE_LINK_RX_FULL_THRESH 1
#endif

#ifndef EVSE_LINK_RTS_INVERT
// Se precisares inverter RTS (muito raro), define para 1 no build.
#define EVSE_LINK_RTS_INVERT 0
#endif

static inline bool log_ratelimit_ok(int64_t *last_us, int64_t interval_us)
{
    const int64_t now = esp_timer_get_time();
    if (*last_us == 0 || (now - *last_us) > interval_us)
    {
        *last_us = now;
        return true;
    }
    return false;
}

// CRC-8 (poly 0x07), MSB-first, init=0x00
static uint8_t crc8_update(uint8_t crc, uint8_t data)
{
    crc ^= data;
    for (uint8_t b = 0; b < 8; ++b)
    {
        if (crc & 0x80)
            crc = (uint8_t)((crc << 1) ^ 0x07);
        else
            crc <<= 1;
    }
    return crc;
}

static esp_err_t configure_uart(void)
{
    uart_config_t cfg = {
        .baud_rate = UART_BAUDRATE,
        .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,
    };

    esp_err_t err = uart_param_config(UART_PORT, &cfg);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
        return err;
    }

    // TX/RX/RTS na UART (RTS controla DE//RE em RS485 half-duplex)
    err = uart_set_pin(UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_PIN_NO_CHANGE);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
        return err;
    }

    // RS-485 HALF DUPLEX (driver controla RTS automaticamente)
    err = uart_set_mode(UART_PORT, UART_MODE_RS485_HALF_DUPLEX);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "uart_set_mode(RS485_HALF_DUPLEX) failed: %s", esp_err_to_name(err));
        return err;
    }

    // Ajustes para RX responsivo (evita "len=120")
    (void)uart_set_rx_full_threshold(UART_PORT, EVSE_LINK_RX_FULL_THRESH);
    (void)uart_set_rx_timeout(UART_PORT, EVSE_LINK_RX_TIMEOUT);

    // Opcional: inverter RTS se hardware exigir
    if (EVSE_LINK_RTS_INVERT)
    {
        (void)uart_set_line_inverse(UART_PORT, UART_SIGNAL_RTS_INV);
        ESP_LOGW(TAG, "RS485 driver: RTS inverted");
    }

    ESP_LOGW(TAG, "RS485 driver enabled: UART%d TX=%d RX=%d RTS=%d baud=%d rx_to=%d rx_thresh=%d",
             UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_BAUDRATE,
             EVSE_LINK_RX_TIMEOUT, EVSE_LINK_RX_FULL_THRESH);

    return ESP_OK;
}

void evse_link_framing_init(void)
{
    if (s_framing_inited)
    {
        ESP_LOGI(TAG, "Framing already initialized");
        return;
    }

    if (!tx_mutex)
    {
        tx_mutex = xSemaphoreCreateMutex();
        if (!tx_mutex)
        {
            ESP_LOGE(TAG, "Failed to create TX mutex");
            return;
        }
    }

    // Se o driver já estiver instalado, só reconfigura e aplica RX tuning.
    if (uart_is_driver_installed(UART_PORT))
    {
        esp_err_t err = configure_uart();
        if (err == ESP_OK)
        {
            s_framing_inited = true;
            ESP_LOGW(TAG, "UART%d driver already installed -> configured for RS485 HALF DUPLEX", UART_PORT);
            (void)uart_flush_input(UART_PORT);
        }
        else
        {
            ESP_LOGE(TAG, "Failed to configure already-installed UART%d", UART_PORT);
        }
        return;
    }

    // Instala driver UART
    esp_err_t err = uart_driver_install(UART_PORT,
                                        UART_RX_BUF_SIZE * 2, // RX buffer (ringbuffer)
                                        0,                    // TX buffer (não usado)
                                        0,                    // event queue size
                                        NULL,                 // event queue
                                        0);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
        return;
    }

    err = configure_uart();
    if (err != ESP_OK)
        return;

    (void)uart_flush_input(UART_PORT);

    s_framing_inited = true;

    ESP_LOGI(TAG, "Framing init (RS485 driver): UART%d TX=%d RX=%d RTS=%d baud=%d",
             UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_BAUDRATE);
}

bool evse_link_framing_send(uint8_t dest, uint8_t src,
                            const uint8_t *payload, uint8_t len)
{
    if (!s_framing_inited)
    {
        ESP_LOGE(TAG, "Framing not initialized");
        return false;
    }

    if (len > EVSE_LINK_MAX_PAYLOAD)
    {
        ESP_LOGW(TAG, "Payload too large: %u (max=%u)", len, EVSE_LINK_MAX_PAYLOAD);
        return false;
    }

    if (len > 0 && payload == NULL)
    {
        ESP_LOGW(TAG, "Invalid send: len=%u but payload=NULL", len);
        return false;
    }

    if (!tx_mutex)
    {
        ESP_LOGE(TAG, "TX mutex is NULL (framing_init not called?)");
        return false;
    }

    if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE)
    {
        ESP_LOGW(TAG, "Failed to take TX mutex");
        return false;
    }

    // Frame: START | DEST | SRC | LEN | SEQ | PAYLOAD | CRC | END
    // LEN on wire = SEQ + PAYLOAD (>=1)
    uint8_t frame[EVSE_LINK_MAX_PAYLOAD + 7];
    int idx = 0;

    frame[idx++] = MAGIC_START;
    frame[idx++] = dest;
    frame[idx++] = src;
    frame[idx++] = (uint8_t)(len + 1);
    frame[idx++] = seq;

    if (len > 0)
    {
        memcpy(&frame[idx], payload, len);
        idx += len;
    }

    // CRC: DEST + SRC + LEN + SEQ + PAYLOAD
    uint8_t crc = 0;
    crc = crc8_update(crc, dest);
    crc = crc8_update(crc, src);
    crc = crc8_update(crc, (uint8_t)(len + 1));
    crc = crc8_update(crc, seq);
    for (uint8_t i = 0; i < len; ++i)
        crc = crc8_update(crc, payload[i]);

    frame[idx++] = crc;
    frame[idx++] = MAGIC_END;

    int written = uart_write_bytes(UART_PORT, (const char *)frame, idx);
    if (written != idx)
    {
        ESP_LOGW(TAG, "uart_write_bytes wrote %d of %d", written, idx);
    }

    // Aguarda TX terminar; driver RS485 devolve RTS para RX automaticamente.
    (void)uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(700));

    xSemaphoreGive(tx_mutex);

    ESP_LOGD(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u",
             dest, src, len, seq);

    seq++;
    return true;
}

void evse_link_framing_recv_byte(uint8_t b)
{
    static enum {
        ST_WAIT_START = 0,
        ST_WAIT_DEST,
        ST_WAIT_SRC,
        ST_WAIT_LEN,
        ST_WAIT_SEQ,
        ST_READING,
        ST_WAIT_CRC,
        ST_WAIT_END
    } rx_state = ST_WAIT_START;

    static uint8_t rx_dest;
    static uint8_t rx_src;
    static uint8_t rx_len; // inclui SEQ + payload (>=1)
    static uint8_t rx_seq;
    static uint8_t rx_buf[EVSE_LINK_MAX_PAYLOAD];
    static uint8_t rx_pos;
    static uint8_t rx_crc;

    static int64_t s_last_byte_us = 0;
    static int64_t s_last_bad_len_log_us = 0;
    static int64_t s_last_bad_crc_log_us = 0;

#define RESET_PARSER()            \
    do                            \
    {                             \
        rx_state = ST_WAIT_START; \
        rx_dest = 0;              \
        rx_src = 0;               \
        rx_len = 0;               \
        rx_seq = 0;               \
        rx_pos = 0;               \
        rx_crc = 0;               \
    } while (0)

    const int64_t now_us = esp_timer_get_time();

    // Timeout inter-byte: frame morreu a meio -> reseta
    if (rx_state != ST_WAIT_START && s_last_byte_us != 0 &&
        (now_us - s_last_byte_us) > EVSE_LINK_INTERBYTE_TIMEOUT_US)
    {
        RESET_PARSER();
    }
    s_last_byte_us = now_us;

    switch (rx_state)
    {
    case ST_WAIT_START:
        if (b == MAGIC_START)
        {
            rx_pos = 0;
            rx_state = ST_WAIT_DEST;
        }
        break;

    case ST_WAIT_DEST:
        rx_dest = b;
        rx_state = ST_WAIT_SRC;
        break;

    case ST_WAIT_SRC:
        rx_src = b;
        rx_state = ST_WAIT_LEN;
        break;

    case ST_WAIT_LEN:
        rx_len = b;

        // rx_len = SEQ + payload => >=1 e <= MAX+1
        if (rx_len < 1 || rx_len > (uint8_t)(EVSE_LINK_MAX_PAYLOAD + 1))
        {
            if (log_ratelimit_ok(&s_last_bad_len_log_us, LOG_RATELIMIT_US))
            {
                ESP_LOGW(TAG, "Invalid LEN=%u (max=%u), dropping frame",
                         rx_len, (unsigned)(EVSE_LINK_MAX_PAYLOAD + 1));
            }
            RESET_PARSER();
            break;
        }

        rx_pos = 0;
        rx_state = ST_WAIT_SEQ;
        break;

    case ST_WAIT_SEQ:
        rx_seq = b;
        rx_state = (rx_len > 1) ? ST_READING : ST_WAIT_CRC;
        break;

    case ST_READING:
    {
        const uint8_t payload_len = (uint8_t)(rx_len - 1);

        if (payload_len > EVSE_LINK_MAX_PAYLOAD)
        {
            if (log_ratelimit_ok(&s_last_bad_len_log_us, LOG_RATELIMIT_US))
            {
                ESP_LOGW(TAG, "Payload len too big: %u", (unsigned)payload_len);
            }
            RESET_PARSER();
            break;
        }

        if (rx_pos < EVSE_LINK_MAX_PAYLOAD)
            rx_buf[rx_pos++] = b;

        if (rx_pos >= payload_len)
            rx_state = ST_WAIT_CRC;
        break;
    }

    case ST_WAIT_CRC:
        rx_crc = b;
        rx_state = ST_WAIT_END;
        break;

    case ST_WAIT_END:
        if (b == MAGIC_END)
        {
            uint8_t expected = 0;
            expected = crc8_update(expected, rx_dest);
            expected = crc8_update(expected, rx_src);
            expected = crc8_update(expected, rx_len);
            expected = crc8_update(expected, rx_seq);

            const uint8_t payload_len = (uint8_t)(rx_len - 1);
            for (uint8_t i = 0; i < payload_len; ++i)
                expected = crc8_update(expected, rx_buf[i]);

            if (expected == rx_crc)
            {
                if (rx_cb)
                    rx_cb(rx_src, rx_dest, rx_buf, payload_len);

                ESP_LOGD(TAG, "Frame OK src=0x%02X dest=0x%02X len=%u seq=%u",
                         rx_src, rx_dest, payload_len, rx_seq);
            }
            else
            {
                if (log_ratelimit_ok(&s_last_bad_crc_log_us, LOG_RATELIMIT_US))
                {
                    ESP_LOGW(TAG, "CRC mismatch: expected=0x%02X got=0x%02X",
                             expected, rx_crc);
                }
            }
        }
        RESET_PARSER();
        break;

    default:
        RESET_PARSER();
        break;
    }

#undef RESET_PARSER
}

void evse_link_framing_register_cb(evse_link_frame_cb_t cb)
{
    rx_cb = cb;
}
