/*
 * MCU version probe - extended listening
 * Send init probes, then listen for 10s to collect all async frames
 */

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/select.h>
#include <errno.h>

#define PORT "/dev/ttyS7"
#define BAUD 460800

static int serial_open(const char *port, int baud)
{
    int fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd < 0) { perror("open"); return -1; }
    struct termios tty;
    if (tcgetattr(fd, &tty) != 0) { perror("tcgetattr"); close(fd); return -1; }
    speed_t speed = B0;
    switch (baud) {
        case 115200: speed = B115200; break;
        case 460800: speed = B460800; break;
        default: fprintf(stderr, "unsupported baud: %d\n", baud); close(fd); return -1;
    }
    cfsetispeed(&tty, speed);
    cfsetospeed(&tty, speed);
    tty.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS);
    tty.c_cflag |= CS8 | CREAD | CLOCAL;
    tty.c_iflag &= ~(IXON | IXOFF | IXANY | IGNBRK | BRKINT | PARMRK | ISTRIP
                    | INLCR | IGNCR | ICRNL);
    tty.c_oflag &= ~(OPOST | ONLCR);
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);
    tty.c_cc[VMIN]  = 0;
    tty.c_cc[VTIME] = 0;
    if (tcsetattr(fd, TCSANOW, &tty) != 0) { perror("tcsetattr"); close(fd); return -1; }
    int flags = fcntl(fd, F_GETFL);
    if (flags >= 0) fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
    tcflush(fd, TCIOFLUSH);
    return fd;
}

static int serial_read(int fd, uint8_t *buf, size_t cap, int timeout_ms)
{
    fd_set rfds;
    struct timeval tv;
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    tv.tv_sec  = timeout_ms / 1000;
    tv.tv_usec = (timeout_ms % 1000) * 1000;
    int ret = select(fd + 1, &rfds, NULL, NULL, &tv);
    if (ret <= 0) return ret;
    return (int)read(fd, buf, cap);
}

static int serial_write(int fd, const uint8_t *data, size_t len)
{
    size_t written = 0;
    while (written < len) {
        ssize_t ret = write(fd, data + written, len - written);
        if (ret < 0) {
            if (errno == EINTR || errno == EAGAIN) continue;
            return -1;
        }
        if (ret == 0) break;
        written += (size_t)ret;
    }
    tcdrain(fd);
    return (int)written;
}

static uint8_t calc_checksum(const uint8_t *data, size_t len)
{
    uint16_t sum = 0;
    for (size_t i = 0; i < len; ++i) sum += data[i];
    return (uint8_t)(~(sum & 0xFF));
}

static size_t build_frame(uint8_t *out, uint8_t seq, uint8_t ft,
                          const uint8_t *payload, size_t payload_len)
{
    size_t total = payload_len + 7;
    out[0] = 0xFF; out[1] = 0xAA;
    out[2] = (uint8_t)((total >> 8) & 0xFF);
    out[3] = (uint8_t)(total & 0xFF);
    out[4] = seq; out[5] = ft;
    if (payload_len > 0) memcpy(out + 6, payload, payload_len);
    out[6 + payload_len] = calc_checksum(out + 2, payload_len + 4);
    return total;
}

static void print_hex(const char *label, const uint8_t *data, size_t len)
{
    printf("%s [len=%zu] ", label, len);
    for (size_t i = 0; i < len; ++i) printf("%02X ", data[i]);
    printf("\n");
}

static const char* ft_name(uint8_t ft) {
    switch(ft) {
        case 0x01: return "SETUP";
        case 0x02: return "ACK";
        case 0x03: return "SETUPACK";
        case 0x04: return "CLOSE";
        case 0x05: return "HB";
        case 0x06: return "DATA";
        case 0x07: return "TRANSFER";
        case 0x08: return "BOOT";
        default: return "?";
    }
}

static const char* cmd_name(uint8_t cmd) {
    switch(cmd) {
        case 0x01: return "M2A_LINE_DETECT";
        case 0x02: return "M2A_MCU_VERSION";
        case 0x03: return "M2A_TIME_INFO";
        case 0x04: return "M2A_REQ_PWR_OFF";
        case 0x05: return "M2A_SYS_STATUS";
        case 0x06: return "M2A_KEY_EVENT";
        case 0x07: return "M2A_BP_ABNORMAL";
        case 0x08: return "M2A_TFT_DUTY";
        case 0x09: return "M2A_BATT_INFO";
        case 0x0A: return "M2A_CAR_SPEED";
        case 0x0B: return "M2A_MILEAGE";
        case 0x0C: return "M2A_SWITCH_STATUS";
        case 0x0D: return "M2A_VOLTAGE";
        case 0x0E: return "M2A_FUEL_CAL";
        case 0x0F: return "M2A_ENCRYPT_STATUS";
        case 0x10: return "M2A_VEHICLE_CFG";
        case 0x11: return "M2A_INS_FUEL_CONSUME";
        case 0x12: return "M2A_MCU_HARD_VERSION";
        case 0x13: return "M2A_SW_VERSION";
        case 0x40: return "M2A_FACTORY_CMD";
        case 0x41: return "M2A_FACTORY_CMD_CAN";
        case 0x42: return "M2A_COM_TEST";
        case 0x43: return "M2A_TEST_INFO";
        case 0x9C: return "M2A_COM_TEST_RESP";
        case 0x9D: return "M2A_STOP_CHARGE";
        case 0x9E: return "M2A_RUN_NORMALLY";
        default: return "UNKNOWN";
    }
}

int main(void)
{
    int fd = serial_open(PORT, BAUD);
    if (fd < 0) return 1;
    printf("Opened %s @ %d\n\n", PORT, BAUD);

    uint8_t drain[256];
    while (serial_read(fd, drain, sizeof(drain), 100) > 0) {}

    /* SETUP */
    uint8_t setup[16];
    size_t setup_len = build_frame(setup, 1, 0x01, NULL, 0);
    print_hex("TX SETUP", setup, setup_len);
    serial_write(fd, setup, setup_len);

    /* Wait SETUPACK */
    uint8_t rx[4096];
    int n = serial_read(fd, rx, sizeof(rx), 3000);
    if (n > 0) print_hex("RX", rx, n);

    /* ACK */
    uint8_t ack[16]; size_t alen = build_frame(ack, 1, 0x02, NULL, 0);
    print_hex("TX ACK", ack, alen);
    serial_write(fd, ack, alen);

    /* Send all probe commands */
    uint8_t seq = 2;
    struct { uint8_t payload[8]; size_t len; const char* name; } cmds[] = {
        {{0x01, 0x81}, 2, "REQ_INIT(0x81)"},
        {{0x01, 0x86, 0x02}, 3, "REQ_TYPE(0x86)"},
        {{0x01, 0x9A}, 2, "ARM_HW_VERSION(0x9A)"},
        {{0x01, 0x99}, 2, "ARM_SW_VERSION(0x99)"},
        {{0x01, 0x98}, 2, "REQ_VEHICLE_CFG(0x98)"},
    };

    for (int i = 0; i < 5; i++) {
        uint8_t tx[32];
        size_t txlen = build_frame(tx, seq++, 0x06, cmds[i].payload, cmds[i].len);
        printf("\n--- %s --- seq=0x%02X\n", cmds[i].name, seq-1);
        print_hex("TX", tx, txlen);
        serial_write(fd, tx, txlen);
        usleep(50000); /* 50ms gap */
    }

    /* Now listen for 12 seconds, collecting ALL async frames */
    printf("\n========================================\n");
    printf("Listening for async MCU frames (12s)...\n");
    printf("========================================\n\n");

    uint8_t parser[8192];
    size_t parser_len = 0;
    int frame_count = 0;
    uint64_t start_ms = 0;
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    start_ms = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;

    while (1) {
        clock_gettime(CLOCK_MONOTONIC, &ts);
        uint64_t now_ms = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
        if ((int)(now_ms - start_ms) >= 12000) break;

        int n = serial_read(fd, rx, sizeof(rx), 100);
        if (n <= 0) continue;

        /* Append to parser buffer */
        if (parser_len + (size_t)n > sizeof(parser)) {
            size_t keep = parser_len > 200 ? 200 : parser_len;
            memmove(parser, parser + parser_len - keep, keep);
            parser_len = keep;
        }
        memcpy(parser + parser_len, rx, n);
        parser_len += n;

        /* Scan for valid frames */
        size_t offset = 0;
        while (offset + 7 <= parser_len) {
            if (parser[offset] != 0xFF || parser[offset+1] != 0xAA) { offset++; continue; }
            uint16_t flen = ((uint16_t)parser[offset+2] << 8) | parser[offset+3];
            if (flen < 7 || flen > 2048) { offset++; continue; }
            if (offset + flen > parser_len) break;

            uint8_t cs = calc_checksum(parser + offset + 2, flen - 3);
            if (cs == parser[offset + flen - 1]) {
                uint8_t seq_rx = parser[offset + 4];
                uint8_t ft_rx  = parser[offset + 5];
                size_t plen = flen - 7;

                printf("[FRAME-%d] ft=0x%02X(%s) seq=0x%02X plen=%zu | ",
                       ++frame_count, ft_rx, ft_name(ft_rx), seq_rx, plen);
                for (size_t j = 0; j < flen; j++) printf("%02X ", parser[offset+j]);
                printf("\n");

                if (plen >= 2 && (ft_rx == 0x06 || ft_rx == 0x08)) {
                    uint8_t mt = parser[offset+6];
                    uint8_t cmd = parser[offset+7];
                    printf("          -> mt=0x%02X cmd=0x%02X(%s)\n", mt, cmd, cmd_name(cmd));
                    if (plen > 2) {
                        printf("          -> body: ");
                        for (size_t j = 2; j < plen; j++) {
                            uint8_t b = parser[offset+6+j];
                            printf("%02X ", b);
                        }
                        printf(" | ascii: ");
                        for (size_t j = 2; j < plen; j++) {
                            uint8_t b = parser[offset+6+j];
                            printf("%c", b >= 0x20 && b < 0x7F ? b : '.');
                        }
                        printf("\n");
                    }
                }

                /* Auto-ACK for DATA/BOOT/HB */
                if (ft_rx == 0x06 || ft_rx == 0x08 || ft_rx == 0x05) {
                    uint8_t ack_tx[16];
                    size_t ack_tx_len = build_frame(ack_tx, seq_rx, 0x02, NULL, 0);
                    serial_write(fd, ack_tx, ack_tx_len);
                }

                offset += flen;
            } else {
                offset++;
            }
        }

        /* Shift remaining */
        if (offset > 0) {
            if (offset < parser_len) {
                memmove(parser, parser + offset, parser_len - offset);
                parser_len -= offset;
            } else {
                parser_len = 0;
            }
        }
    }

    printf("\n========================================\n");
    printf("Total frames received: %d\n", frame_count);
    printf("========================================\n");

    /* CLOSE */
    uint8_t closef[16]; size_t clen = build_frame(closef, seq, 0x04, NULL, 0);
    print_hex("\nTX CLOSE", closef, clen);
    serial_write(fd, closef, clen);

    close(fd);
    return 0;
}
