// Public serial API: a thin pass-through to the per-port HAL plus a gate that
// makes every call safe when no port is open (or the platform has no serial
// support). Game code can fire-and-forget regardless of whether serial is
// actually working at runtime -- a failed jlSerialOpen leaves every other call
// a no-op, exactly like the audio module.
//
// Single open port at a time (module-static gate), matching the add-on's
// stated contract. Config is normalized + range-checked here so every per-port
// HAL can assume a sane jlSerialConfigT.

#include <stddef.h>

#include "joey/serial.h"
#include "port.h"


// ----- Constants -----

#define SERIAL_DATA_BITS_MIN   5u
#define SERIAL_DATA_BITS_MAX   8u
#define SERIAL_DATA_BITS_DEF   8u
#define SERIAL_STOP_BITS_DEF   1u
#define SERIAL_BAUD_DEF        9600u


// ----- Module state -----

static bool gSerialOpen = false;


// ----- Public API (alphabetical) -----

uint16_t jlSerialAvailable(void) {
    if (!gSerialOpen) {
        return 0u;
    }
    return jlpSerialAvailable();
}


void jlSerialClose(void) {
    // No gate: the HAL self-guards teardown, and closing an already-closed
    // port must be harmless.
    jlpSerialClose();
    gSerialOpen = false;
}


void jlSerialFlush(void) {
    if (!gSerialOpen) {
        return;
    }
    jlpSerialFlush();
}


bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
    jlSerialConfigT  cfg;

    if (gSerialOpen || config == NULL) {
        return false;
    }

    // Normalize + range-check so the HAL sees a sane config. 0 fields take the
    // library default; out-of-range data/stop bits are rejected.
    cfg = *config;
    if (cfg.baud == 0u) {
        cfg.baud = SERIAL_BAUD_DEF;
    }
    if (cfg.dataBits == 0u) {
        cfg.dataBits = SERIAL_DATA_BITS_DEF;
    }
    if (cfg.stopBits == 0u) {
        cfg.stopBits = SERIAL_STOP_BITS_DEF;
    }
    if (cfg.dataBits < SERIAL_DATA_BITS_MIN || cfg.dataBits > SERIAL_DATA_BITS_MAX ||
        cfg.stopBits < 1u || cfg.stopBits > 2u) {
        return false;
    }

    gSerialOpen = jlpSerialOpen(device, &cfg);
    return gSerialOpen;
}


void jlSerialPoll(void) {
    if (!gSerialOpen) {
        return;
    }
    jlpSerialPoll();
}


uint16_t jlSerialRead(uint8_t *buf, uint16_t max) {
    if (!gSerialOpen || buf == NULL || max == 0u) {
        return 0u;
    }
    return jlpSerialRead(buf, max);
}


int16_t jlSerialReadByte(void) {
    uint8_t  b;

    if (jlSerialRead(&b, 1u) != 1u) {
        return -1;
    }
    return (int16_t)b;
}


uint16_t jlSerialWrite(const uint8_t *buf, uint16_t len) {
    if (!gSerialOpen || buf == NULL || len == 0u) {
        return 0u;
    }
    return jlpSerialWrite(buf, len);
}


bool jlSerialWriteByte(uint8_t b) {
    return jlSerialWrite(&b, 1u) == 1u;
}
