#include "servo_bus.h"
#include "config.h"
#include "stm32f7xx_hal.h"
#include <string.h>

/*
 * servo_bus.c — Feetech/ST3215 serial bus servo driver (Issue #547)
 *
 * Half-duplex on USART3 PB10 (USART3_TX, AF7) at 1 Mbps.
 * HDSEL bit (CR3.3) routes TX and RX to the TX pin.
 * TX direction: TX active, RX gated.
 * RX direction: TX idle, RX active — enabled after TC interrupt.
 *
 * All operations are blocking with short timeouts; the servo bus
 * is polled at 50 Hz from the main loop (not from an ISR).
 */

static UART_HandleTypeDef s_uart;

/* ---- Init ---- */

void servo_bus_init(void)
{
    /* GPIO: PB10 open-drain AF7 (USART3_TX) with external pull-up on bus */
    __HAL_RCC_GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin       = SERVO_BUS_PIN;
    gpio.Mode      = GPIO_MODE_AF_OD;      /* open-drain for half-duplex bus */
    gpio.Pull      = GPIO_PULLUP;
    gpio.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
    gpio.Alternate = GPIO_AF7_USART3;
    HAL_GPIO_Init(SERVO_BUS_PORT, &gpio);

    /* USART3 at SERVO_BUS_BAUD (1 Mbps), 8N1 */
    __HAL_RCC_USART3_CLK_ENABLE();
    s_uart.Instance          = USART3;
    s_uart.Init.BaudRate     = SERVO_BUS_BAUD;
    s_uart.Init.WordLength   = UART_WORDLENGTH_8B;
    s_uart.Init.StopBits     = UART_STOPBITS_1;
    s_uart.Init.Parity       = UART_PARITY_NONE;
    s_uart.Init.Mode         = UART_MODE_TX_RX;
    s_uart.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
    s_uart.Init.OverSampling = UART_OVERSAMPLING_8;  /* required for 1 Mbps @ APB1 54 MHz */
    HAL_UART_Init(&s_uart);

    /* Enable half-duplex: HDSEL bit in CR3 routes TX/RX to TX pin */
    SET_BIT(s_uart.Instance->CR3, USART_CR3_HDSEL);
}

/* ---- Checksum ---- */

static uint8_t _cksum(uint8_t id, uint8_t len, uint8_t instr,
                      const uint8_t *params, uint8_t nparams)
{
    uint16_t sum = (uint16_t)id + len + instr;
    for (uint8_t i = 0; i < nparams; i++) sum += params[i];
    return (uint8_t)(~sum & 0xFFu);
}

/* ---- Transmit a raw packet ---- */

static bool _tx(uint8_t id, uint8_t instr, const uint8_t *params, uint8_t nparams)
{
    uint8_t  len = nparams + 2u;  /* instr + params + cksum */
    uint8_t  buf[32];
    uint8_t  n = 0;

    buf[n++] = 0xFFu;
    buf[n++] = 0xFFu;
    buf[n++] = id;
    buf[n++] = len;
    buf[n++] = instr;
    for (uint8_t i = 0; i < nparams; i++) buf[n++] = params[i];
    buf[n++] = _cksum(id, len, instr, params, nparams);

    return HAL_UART_Transmit(&s_uart, buf, n, 10u) == HAL_OK;
}

/* ---- Receive response packet ---- */

/*
 * Read one byte with timeout. In half-duplex the TX line is still driving
 * after the last stop bit; we must wait ~1 bit-time before the servo begins
 * its response. HAL_UART_Receive handles the timing via the timeout.
 */
static bool _rx(uint8_t id, uint8_t *data, uint8_t ndata)
{
    uint8_t hdr[4];   /* 0xFF 0xFF ID LEN */
    uint8_t tail[2];  /* ERROR CKSUM (or just CKSUM if ndata==0) */

    /* Read header */
    if (HAL_UART_Receive(&s_uart, hdr, 4u, SB_RX_TIMEOUT_MS) != HAL_OK)
        return false;

    /* Validate header */
    if (hdr[0] != 0xFFu || hdr[1] != 0xFFu || hdr[2] != id)
        return false;

    uint8_t pkt_len = hdr[3];              /* ERROR + DATA... + CKSUM */
    if (pkt_len < 2u || pkt_len > 20u)     /* sanity */
        return false;

    /* Read payload: ERROR + DATA bytes + CKSUM */
    uint8_t  payload[20];
    uint8_t  payload_len = pkt_len;        /* includes error byte and cksum */
    if (HAL_UART_Receive(&s_uart, payload, payload_len, SB_RX_TIMEOUT_MS) != HAL_OK)
        return false;

    /* Verify checksum: ~(ID + LEN + ERROR + DATA) */
    uint16_t sum = (uint16_t)id + pkt_len;
    for (uint8_t i = 0; i < (uint8_t)(pkt_len - 1u); i++) sum += payload[i];
    uint8_t expected_cksum = (uint8_t)(~sum & 0xFFu);
    if (payload[pkt_len - 1u] != expected_cksum)
        return false;

    /* payload[0] = ERROR; payload[1..] = data */
    if (payload[0] != 0u) return false;    /* servo reported error */

    uint8_t n_data_bytes = pkt_len - 2u;   /* minus ERROR and CKSUM */
    if (n_data_bytes < ndata) return false;

    for (uint8_t i = 0; i < ndata; i++) data[i] = payload[1u + i];
    return true;
}

/* ---- Public API ---- */

bool servo_bus_write_pos(uint8_t id, uint16_t raw_pos, uint16_t speed)
{
    if (raw_pos > SB_POS_MAX) raw_pos = SB_POS_MAX;
    if (speed   > SB_SPEED_MAX) speed = SB_SPEED_MAX;

    /* Write 4 bytes starting at SB_REG_GOAL_POS_L:
     * [addr][POS_L][POS_H][SPD_L][SPD_H] */
    uint8_t params[5] = {
        SB_REG_GOAL_POS_L,
        (uint8_t)(raw_pos & 0xFFu),
        (uint8_t)(raw_pos >> 8),
        (uint8_t)(speed   & 0xFFu),
        (uint8_t)(speed   >> 8),
    };
    return _tx(id, SB_INSTR_WRITE, params, 5u);
    /* WRITE does not generate a status packet by default */
}

bool servo_bus_write_torque(uint8_t id, bool enable)
{
    uint8_t params[2] = { SB_REG_TORQUE_EN, enable ? 1u : 0u };
    return _tx(id, SB_INSTR_WRITE, params, 2u);
}

bool servo_bus_read_pos(uint8_t id, uint16_t *raw_pos)
{
    /* READ instruction: [addr][count] */
    uint8_t params[2] = { SB_REG_PRES_POS_L, 2u };
    if (!_tx(id, SB_INSTR_READ, params, 2u)) return false;

    uint8_t data[2];
    if (!_rx(id, data, 2u)) return false;

    *raw_pos = (uint16_t)data[0] | ((uint16_t)data[1] << 8);
    return true;
}

bool servo_bus_read_speed(uint8_t id, uint16_t *speed)
{
    uint8_t params[2] = { SB_REG_PRES_SPD_L, 2u };
    if (!_tx(id, SB_INSTR_READ, params, 2u)) return false;

    uint8_t data[2];
    if (!_rx(id, data, 2u)) return false;

    *speed = (uint16_t)data[0] | ((uint16_t)data[1] << 8);
    return true;
}

uint16_t servo_bus_deg_to_raw(float deg)
{
    /* Center = 0 deg = 2048 raw; 1 deg = 4096/360 raw */
    int32_t raw = (int32_t)(SB_POS_CENTER + (int32_t)(deg * (4096.0f / 360.0f)));
    if (raw < 0)             raw = 0;
    if (raw > (int32_t)SB_POS_MAX) raw = (int32_t)SB_POS_MAX;
    return (uint16_t)raw;
}

float servo_bus_raw_to_deg(uint16_t raw)
{
    return ((float)raw - (float)SB_POS_CENTER) * (360.0f / 4096.0f);
}
