/**
 * @file    comm_uart.cpp
 * @brief   通讯抽象层 — UART 串口通讯
 * @details 串口设备打开、波特率/数据位/停止位/校验位配置、
 *          非阻塞读写、epoll 集成。
 */

#include "comm_internal.h"

#include <termios.h>

/**
 * @brief 波特率常量 → termios 速度标志
 */
LOCAL speed_t uart_get_baudrate(int baudrate)
{
    switch (baudrate)
    {
    case 9600:    return B9600;
    case 19200:   return B19200;
    case 38400:   return B38400;
    case 57600:   return B57600;
    case 115200:  return B115200;
    case 230400:  return B230400;
    case 460800:  return B460800;
    case 921600:  return B921600;
    default:      return B115200;
    }
}

/**
 * @brief 数据位数 → termios CS 标志
 */
LOCAL int uart_get_databits(int bits)
{
    switch (bits)
    {
    case 5:  return CS5;
    case 6:  return CS6;
    case 7:  return CS7;
    case 8:
    default: return CS8;
    }
}

/**
 * @brief 配置串口参数
 */
LOCAL int uart_configure(int fd, stru_comm_instance *inst)
{
    struct termios tty;

    memset(&tty, 0, sizeof(tty));

    if (tcgetattr(fd, &tty) != 0)
    {
        return -1;
    }

    /* 输入标志：关闭软件流控 */
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    tty.c_iflag |= IGNPAR;

    /* 输出标志：原始模式 */
    tty.c_oflag &= ~OPOST;

    /* 控制标志 */
    tty.c_cflag &= ~(CSIZE | PARENB | CSTOPB | CRTSCTS);
    tty.c_cflag |= CREAD | CLOCAL;

    /* 数据位 */
    tty.c_cflag |= uart_get_databits(inst->uart_data_bits);

    /* 停止位 */
    if (inst->uart_stop_bits == 2)
    {
        tty.c_cflag |= CSTOPB;
    }

    /* 校验位 */
    switch (inst->uart_parity)
    {
    case 'E':
    case 'e':
        tty.c_cflag |= PARENB;
        tty.c_iflag |= INPCK;
        break;

    case 'O':
    case 'o':
        tty.c_cflag |= (PARENB | PARODD);
        tty.c_iflag |= INPCK;
        break;

    case 'N':
    case 'n':
    default:
        /* 无校验 */
        break;
    }

    /* 本地模式：原始模式 */
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG | IEXTEN);

    /* 超时：0.1 秒 */
    tty.c_cc[VMIN]  = 0;
    tty.c_cc[VTIME] = 1;

    /* 波特率 */
    speed_t speed = uart_get_baudrate(inst->uart_baudrate);

    cfsetispeed(&tty, speed);
    cfsetospeed(&tty, speed);

    if (tcsetattr(fd, TCSANOW, &tty) != 0)
    {
        return -1;
    }

    /* 清空缓冲区 */
    tcflush(fd, TCIOFLUSH);

    return 0;
}

/**
 * @brief 打开串口设备
 */
int uart_open(stru_comm_instance *inst)
{
    int fd = open(inst->uart_device, O_RDWR | O_NOCTTY | O_NONBLOCK);

    if (fd == -1)
    {
        if (inst->debug)
        {
            LOG_E("comm[%d] UART open %s failed: %s",
                     inst->id, inst->uart_device, strerror(errno));
        }

        return COMM_ERR_SOCKET;
    }

    if (uart_configure(fd, inst) != 0)
    {
        if (inst->debug)
        {
            LOG_E("comm[%d] UART configure failed: %s",
                     inst->id, strerror(errno));
        }
        close(fd);

        return COMM_ERR_INTERNAL;
    }

    if (epoll_add_fd(fd, inst->id, 0) != 0)
    {
        close(fd);

        return COMM_ERR_INTERNAL;
    }

    inst->local_fd = fd;

    if (inst->debug)
    {
        LOG_I("comm[%d] UART opened %s baud=%d databits=%d stopbits=%d parity=%c",
                 inst->id, inst->uart_device, inst->uart_baudrate,
                 inst->uart_data_bits, inst->uart_stop_bits, inst->uart_parity);
    }

    return COMM_OK;
}

/**
 * @brief 关闭串口
 */
void uart_close(stru_comm_instance *inst)
{
    if (inst->local_fd != -1)
    {
        epoll_del_fd(inst->local_fd);
        close(inst->local_fd);
        inst->local_fd = -1;
    }
}

/**
 * @brief 处理 UART 可读事件
 */
void handle_uart_read(stru_comm_instance *inst, int fd)
{
    unsigned char buf[COMM_RECV_BUF_SIZE];

    while (1)
    {
        ssize_t n = read(fd, buf, sizeof(buf));

        if (n > 0)
        {
            if (inst->recv_cb)
            {
                inst->recv_cb(inst->id, fd, buf, (int)n);
            }
        }
        else if (n == 0)
        {
            break;
        }
        else
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                break;
            }

            if (errno != EINTR)
            {
                if (inst->debug)
                {
                    LOG_E("comm[%d] UART fd=%d read error: %s",
                             inst->id, fd, strerror(errno));
                }
            }
            break;
        }
    }
}
