#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

#ifdef __APPLE__
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
#define BAUD_RATE_VAL 460800
#else
#define BAUD_RATE B460800
#endif

#define SERIAL_PORT "/dev/ttyS7"

// 协议常量
#define HEAD1 0xFF
#define HEAD2 0xAA
#define FT_ACK 0x02
#define FT_HB  0x05
#define FT_DATA 0x06

// 全局帧缓冲：解决拆包/粘包
static unsigned char g_buf[2048];
static int g_len = 0;

int init_serial(const char *port) {
    int fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) { perror("open"); return -1; }
    fcntl(fd, F_SETFL, 0); // 阻塞模式，read会等待数据

    struct termios tty;
    tcgetattr(fd, &tty);

#ifdef __APPLE__
    cfsetispeed(&tty, B9600);
    cfsetospeed(&tty, B9600);
#else
    cfsetispeed(&tty, BAUD_RATE);
    cfsetospeed(&tty, BAUD_RATE);
#endif

    tty.c_cflag |= (CLOCAL | CREAD);
    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tty.c_oflag &= ~OPOST;
    tty.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL);

    tcsetattr(fd, TCSANOW, &tty);
#ifdef __APPLE__
    speed_t speed = BAUD_RATE_VAL;
    ioctl(fd, IOSSIOSPEED, &speed);
#endif
    return fd;
}

// 计算校验和：对 LenH,LenL,SerialNO,FrameType,Data... 求和取反
unsigned char calc_checksum(unsigned char *buf, int total_len) {
    unsigned char sum = 0;
    // 校验范围：索引2 到 total_len-2（不含Head1/2和最后的CheckSum）
    for (int i = 2; i < total_len - 1; i++) sum += buf[i];
    return (~sum) & 0xFF;
}

// 构造ACK帧：7字节无数据帧
void build_ack(unsigned char seq, unsigned char *out) {
    out[0] = HEAD1;
    out[1] = HEAD2;
    out[2] = 0x00;      // LenH
    out[3] = 0x07;      // LenL = 7 (Head2+Len2+Serial1+Type1+CS1)
    out[4] = seq;       // SerialNO
    out[5] = FT_ACK;    // FrameType
    out[6] = calc_checksum(out, 7);
}

// 处理缓冲区，尝试解析并消费完整帧
void process_frames(int fd) {
    int i = 0;
    while (i <= g_len - 7) { // 最小帧7字节
        // 找帧头
        if (g_buf[i] != HEAD1 || g_buf[i+1] != HEAD2) {
            i++;
            continue;
        }

        // 读取Len（高字节在前）
        int frame_len = (g_buf[i+2] << 8) | g_buf[i+3];
        if (frame_len < 7 || frame_len > 2048) {
            // 非法长度，跳过这个假帧头
            i++;
            continue;
        }

        // 数据是否足够？
        if (i + frame_len > g_len) break; // 等下次read补数据

        // 校验CheckSum（最后一个字节）
        unsigned char cs = calc_checksum(&g_buf[i], frame_len);
        if (cs != g_buf[i + frame_len - 1]) {
            printf("[ERR] CheckSum fail! calc=0x%02X, got=0x%02X\n", cs, g_buf[i+frame_len-1]);
            i++; // 跳过坏帧头，继续搜索
            continue;
        }

        // ===== 成功解析一帧 =====
        unsigned char seq   = g_buf[i+4];
        unsigned char type  = g_buf[i+5];

        printf("[RX] Type:0x%02X Seq:%d Len:%d\n", type, seq, frame_len);

        // 心跳或数据帧：回ACK
        if (type == FT_HB || type == FT_DATA) {
            unsigned char ack[7];
            build_ack(seq, ack);
            write(fd, ack, 7);
            printf("  -> TX ACK (Seq:%d)\n", seq);
        }

        // 消费这帧：从缓冲区移除
        int remain = g_len - (i + frame_len);
        if (remain > 0) {
            memmove(&g_buf[0], &g_buf[i + frame_len], remain);
        }
        g_len = remain;
        i = 0; // 重新从头部开始解析（因为数据前移了）
    }

    // 如果前面有垃圾数据（一直找不到帧头），保留最后6字节可能属于帧头的一部分
    if (i > 0 && g_len > 6) {
        int keep = g_len - i;
        if (keep > 6) keep = 6; // 最多保留6字节（<最小帧头搜索需求）
        memmove(&g_buf[0], &g_buf[i], keep);
        g_len = keep;
    }
}

int main() {
    int fd = init_serial(SERIAL_PORT);
    if (fd < 0) return 1;

    printf("开始监听 %s...（按Ctrl+C退出）\n", SERIAL_PORT);

    while (1) {
        // 把新读到的数据追加到全局缓冲区
        int space = sizeof(g_buf) - g_len;
        if (space > 0) {
            int n = read(fd, &g_buf[g_len], space);
            if (n > 0) {
                g_len += n;
                process_frames(fd); // 立即尝试解析
            }
        }
        usleep(5000); // 5ms轮询，降低CPU占用
    }

    close(fd);
    return 0;
}