/* Serial Port Driver for CubeCactusOS
 * COM1 (0x3F8) - Primary serial port
 * Provides reliable input/output via QEMU serial
 */

#include <stdint.h>

#define SERIAL_COM1 0x3F8

/* Serial port registers (offset from COM1) */
#define SERIAL_DATA         0   /* Data register (read/write) */
#define SERIAL_INT_ENABLE   1   /* Interrupt enable */
#define SERIAL_FIFO_CTRL    2   /* FIFO control */
#define SERIAL_LINE_CTRL    3   /* Line control */
#define SERIAL_MODEM_CTRL   4   /* Modem control */
#define SERIAL_LINE_STATUS  5   /* Line status */

extern void outb(uint16_t port, uint8_t value);
extern uint8_t inb(uint16_t port);

/* Initialize serial port */
void serial_init() {
    outb(SERIAL_COM1 + SERIAL_INT_ENABLE, 0x00);    /* Disable interrupts */
    outb(SERIAL_COM1 + SERIAL_LINE_CTRL, 0x80);     /* Enable DLAB (set baud rate divisor) */
    outb(SERIAL_COM1 + 0, 0x03);                    /* Set divisor to 3 (lo byte) 38400 baud */
    outb(SERIAL_COM1 + 1, 0x00);                    /*                  (hi byte) */
    outb(SERIAL_COM1 + SERIAL_LINE_CTRL, 0x03);     /* 8 bits, no parity, one stop bit */
    outb(SERIAL_COM1 + SERIAL_FIFO_CTRL, 0xC7);     /* Enable FIFO, clear them, 14-byte threshold */
    outb(SERIAL_COM1 + SERIAL_MODEM_CTRL, 0x0B);    /* IRQs enabled, RTS/DSR set */
}

/* Check if serial port can transmit */
static int serial_is_transmit_empty() {
    return inb(SERIAL_COM1 + SERIAL_LINE_STATUS) & 0x20;
}

/* Send a character to serial port */
void serial_putchar(char c) {
    /* Wait for transmit buffer to be empty */
    while (!serial_is_transmit_empty());
    
    outb(SERIAL_COM1 + SERIAL_DATA, c);
}

/* Send a string to serial port */
void serial_puts(const char* str) {
    while (*str) {
        serial_putchar(*str++);
    }
}

/* Check if serial port has received data */
static int serial_received() {
    return inb(SERIAL_COM1 + SERIAL_LINE_STATUS) & 0x01;
}

/* Read a character from serial port (blocking) */
char serial_getchar() {
    /* Wait for data to be received */
    while (!serial_received());
    
    return inb(SERIAL_COM1 + SERIAL_DATA);
}

/* Check if character is available (non-blocking) */
int serial_available() {
    return serial_received();
}
