/*
 * bt_uart.c — DX-BT24 Bluetooth module UART driver for KL630
 *
 * Normal operation (bt_at_probe = 0 in INI):
 *   Open directly at 115200 blocking (VMIN=1).  No AT commands sent.
 *   The module's baud rate is persistent across power cycles once set.
 *
 * First-time / factory-reset setup (bt_at_probe = 1 in INI):
 *   Call bt_uart_probe_and_upgrade() which handles the 9600→115200 upgrade:
 *     1. Try AT+BAUD at 115200 — if response, already configured.
 *     2. If no response: try AT+BAUD at 9600 — if response, send AT+BAUD7
 *        then AT+RESET to lock in the new baud rate.
 *   After this one-time step, set bt_at_probe = 0 in INI so AT commands are
 *   never sent again (avoids forwarding AT strings to connected BLE clients).
 *
 * bt_uart_send_json() is fully non-blocking: it enqueues the message into an
 * internal FIFO and returns immediately.  A dedicated writer thread drains the
 * queue and performs the actual UART write, so callers are never stalled by IO.
 */

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

#include "bt_uart.h"

/* ── UART fd ─────────────────────────────────────────────────────────────── */
static int s_bt_fd = -1;

/* ── Message queue ───────────────────────────────────────────────────────── */
typedef struct BtMsg {
    char          *json;
    struct BtMsg  *next;
} BtMsg;

static BtMsg           *s_q_head    = NULL;
static BtMsg           *s_q_tail    = NULL;
static pthread_mutex_t  s_q_mtx     = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t   s_q_cond    = PTHREAD_COND_INITIALIZER;
static pthread_t        s_writer_tid;
static volatile int     s_running   = 0;

/* ── Internal: write all bytes to fd ────────────────────────────────────── */
static void uart_write_all(const char *data, size_t len)
{
    size_t off = 0;
    while (off < len) {
        ssize_t n = write(s_bt_fd, data + off, len - off);
        if (n <= 0) { perror("[BT] write"); return; }
        off += (size_t)n;
    }
}

/* ── Writer thread: drains queue, does actual IO ─────────────────────────── */
static void *bt_writer_thread(void *arg)
{
    (void)arg;
    while (1) {
        pthread_mutex_lock(&s_q_mtx);
        while (!s_q_head && s_running)
            pthread_cond_wait(&s_q_cond, &s_q_mtx);

        if (!s_running && !s_q_head) {
            pthread_mutex_unlock(&s_q_mtx);
            break;
        }

        BtMsg *msg = s_q_head;
        if (msg) {
            s_q_head = msg->next;
            if (!s_q_head) s_q_tail = NULL;
        }
        pthread_mutex_unlock(&s_q_mtx);

        if (msg) {
            if (s_bt_fd >= 0)
                uart_write_all(msg->json, strlen(msg->json));
            free(msg->json);
            free(msg);
        }
    }
    return NULL;
}

/* ── Internal: open + configure serial port ──────────────────────────────── */
static int open_uart(const char *dev, speed_t baud, int vmin, int vtime)
{
    int fd = open(dev, O_RDWR | O_NOCTTY);
    if (fd < 0) { perror("[BT] open uart"); return -1; }

    struct termios opts;
    if (tcgetattr(fd, &opts) != 0) {
        perror("[BT] tcgetattr");
        close(fd);
        return -1;
    }

    cfmakeraw(&opts);
    cfsetispeed(&opts, baud);
    cfsetospeed(&opts, baud);

    opts.c_cflag |= (CLOCAL | CREAD);
    opts.c_cflag &= ~PARENB;
    opts.c_cflag &= ~CSTOPB;
    opts.c_cflag &= ~CSIZE;
    opts.c_cflag |= CS8;
    opts.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    opts.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL | INLCR);
    opts.c_oflag &= ~OPOST;
    opts.c_cc[VMIN]  = (cc_t)vmin;
    opts.c_cc[VTIME] = (cc_t)vtime;

    if (tcsetattr(fd, TCSANOW, &opts) != 0) {
        perror("[BT] tcsetattr");
        close(fd);
        return -1;
    }
    return fd;
}

/* ── One-time AT baud setup (call only when bt_at_probe = 1 in INI) ─────── */
static void bt_uart_probe_and_upgrade(const char *dev)
{
    char buf[128] = {0};
    int  n;

    /* Try AT+BAUD at 115200 first */
    int fd = open_uart(dev, B115200, 0, 10);   /* VTIME=10 → 1s timeout */
    if (fd < 0) return;
    write(fd, "AT+BAUD\r\n", 9);
    n = read(fd, buf, sizeof(buf) - 1);
    buf[n > 0 ? n : 0] = '\0';
    printf("[BT] AT probe @ 115200 => %d byte(s): %s\n", n, n > 0 ? buf : "(none)");
    close(fd);

    if (n > 0) {
        printf("[BT] module already at 115200 — no upgrade needed\n");
        return;
    }

    /* No response at 115200 — try 9600 */
    fd = open_uart(dev, B9600, 0, 10);
    if (fd < 0) return;
    memset(buf, 0, sizeof(buf));
    write(fd, "AT+BAUD\r\n", 9);
    n = read(fd, buf, sizeof(buf) - 1);
    printf("[BT] AT probe @ 9600 => %d byte(s): %s\n", n, n > 0 ? buf : "(none)");

    if (n == 0) {
        /* No response at either baud — cannot upgrade; proceed anyway */
        printf("[BT] WARNING: no AT response at 9600 or 115200\n");
        close(fd);
        return;
    }

    /* Module is at 9600 — send AT+BAUD7 to switch to 115200.
     * The module switches baud immediately after responding; close this fd
     * before sending any further commands. */
    printf("[BT] upgrading module from 9600 to 115200\n");
    memset(buf, 0, sizeof(buf));
    write(fd, "AT+BAUD7\r\n", 10);
    usleep(100000);   /* 100 ms: wait for ack + baud switch */
    n = read(fd, buf, sizeof(buf) - 1);
    printf("[BT] AT+BAUD7 => %d byte(s): %s\n", n, n > 0 ? buf : "(none)");
    close(fd);
    usleep(300000);   /* 300 ms: let module stabilise at 115200 */

    /* Reopen at 115200 and reset so the new baud persists across power cycles */
    fd = open_uart(dev, B115200, 0, 10);
    if (fd < 0) return;
    write(fd, "AT+RESET\r\n", 10);
    usleep(100000);
    read(fd, buf, sizeof(buf) - 1);
    close(fd);
    usleep(500000);   /* 500 ms: wait for module to reboot */
    printf("[BT] upgrade complete — set bt_at_probe = 0 in INI to skip AT on next boot\n");
}

/* ── Public API ───────────────────────────────────────────────────────────── */

int bt_uart_init(const char *dev, int do_at_probe)
{
    if (!dev || !*dev) {
        printf("[BT] bt_uart_init: no device configured — BT disabled\n");
        return -1;
    }

    if (do_at_probe) {
        /* One-time factory setup: negotiate baud rate via AT commands */
        bt_uart_probe_and_upgrade(dev);
    }

    /* Open at 115200, blocking (VMIN=1) for normal operation */
    int fd = open_uart(dev, B115200, 1, 0);
    if (fd < 0) {
        printf("[BT] bt_uart_init: open at 115200 failed\n");
        return -1;
    }

    /* Flush any residual bytes */
    tcflush(fd, TCIOFLUSH);

    s_bt_fd = fd;

    /* Start dedicated writer thread */
    s_running = 1;
    if (pthread_create(&s_writer_tid, NULL, bt_writer_thread, NULL) != 0) {
        perror("[BT] pthread_create writer");
        close(fd);
        s_bt_fd  = -1;
        s_running = 0;
        return -1;
    }
    pthread_setname_np(s_writer_tid, "bt_writer");

    printf("[BT] bt_uart_init OK: %s @ 115200 (at_probe=%d)\n", dev, do_at_probe);

    /* Diagnostic ping — confirms UART→BLE channel is alive.
     * No \r\n: BLE is packet-based; delimiter not needed and causes an extra
     * empty notification in nRF Connect. */
    bt_uart_send_json("{\"class\":\"boot\",\"level\":0}");
    printf("[BT] boot ping queued\n");

    return 0;
}

void bt_uart_send_json(const char *json)
{
    if (s_bt_fd < 0 || !s_running || !json || !*json) return;

    BtMsg *msg = (BtMsg *)malloc(sizeof(BtMsg));
    if (!msg) return;
    msg->json = strdup(json);
    msg->next = NULL;
    if (!msg->json) { free(msg); return; }

    pthread_mutex_lock(&s_q_mtx);
    if (s_q_tail) {
        s_q_tail->next = msg;
        s_q_tail = msg;
    } else {
        s_q_head = s_q_tail = msg;
    }
    pthread_cond_signal(&s_q_cond);
    pthread_mutex_unlock(&s_q_mtx);
}

void bt_uart_close(void)
{
    if (!s_running) return;

    /* Signal writer thread to exit after draining remaining messages */
    pthread_mutex_lock(&s_q_mtx);
    s_running = 0;
    pthread_cond_signal(&s_q_cond);
    pthread_mutex_unlock(&s_q_mtx);

    pthread_join(s_writer_tid, NULL);

    if (s_bt_fd >= 0) {
        close(s_bt_fd);
        s_bt_fd = -1;
        printf("[BT] uart closed\n");
    }

    /* Drain any leftover queue entries */
    BtMsg *m = s_q_head;
    while (m) {
        BtMsg *next = m->next;
        free(m->json);
        free(m);
        m = next;
    }
    s_q_head = s_q_tail = NULL;
}
