/*  Driver for 8250/16550-type serial ports */


#include "serial.h"
#include "../io.h"
#include "../irq.h"
#include "../buffer.h"
#include "../kprintf.h"

#define COM1 0x3F8
#define COM1_IRQ 4

#define IER_OFFSET 1 // offset of interrupt enable register
#define IIR_OFFSET 2 // offset of interrupt identification register
#define FCR_OFFSET 2 // offset of FIFO control register
#define LCR_OFFSET 3 // offset of line control register
#define MCR_OFFSET 4 // offset of modem control register
#define LSR_OFFSET 5 // offset of line status register
#define MSR_OFFSET 6 // offset of modem status register

#define DIVISOR_LSB_OFFSET 0 // when DLAB is enabled, divisor LSB
#define DIVISOR_MSB_OFFSET 1 // when DLAB is enabled, divisor MSB

#define SERIAL_PORT COM1
#define SERIAL_PORT_IRQ COM1_IRQ

static ring_buffer_t serial_buffer;

static void serial_irq_handler();

int serial_init(void) {
    outb(SERIAL_PORT + IER_OFFSET, 0x01); // enable interrupts

    outb(SERIAL_PORT + LCR_OFFSET, 0x80); // set DLAB
    outb(SERIAL_PORT + DIVISOR_LSB_OFFSET, 0x01); // set LSB of divisor
    outb(SERIAL_PORT + DIVISOR_MSB_OFFSET, 0x00); // set MSB of divisor
    outb(SERIAL_PORT + LCR_OFFSET, 0x00); // clear DLAB

    //outb(SERIAL_PORT + FCR_OFFSET, 0xC7); // enable FIFO, clear buffers, 14-byte threshold
    outb(SERIAL_PORT + FCR_OFFSET, 0x00); // disable FIFO

    outb(SERIAL_PORT + LCR_OFFSET, 0x03); // 8-bit, no parity, 1 stop bit
    outb(SERIAL_PORT + MCR_OFFSET, 0x1E); // loopback mode, test the serial chip
    outb(SERIAL_PORT, 0xAB);

    if (inb(SERIAL_PORT) != 0xAB) {
        return 1;
    }

    // serial is not faulty, set in normal operation mode
    outb(SERIAL_PORT + MCR_OFFSET, 0x0F); // interrupt enabled, RTS and DSR set
    
    register_irq_handler(SERIAL_PORT_IRQ, serial_irq_handler);
    ring_buffer_init(&serial_buffer);

    return 0;
}

static void serial_handle_receive_byte() {
    uint8_t recb = inb(SERIAL_PORT);
    kprintf("%c", recb);
    //ring_buffer_write(&serial_buffer, recb);
}

static void serial_irq_handler() {
    // Determine if we are reading/writing UART
    uint8_t lsr = inb(SERIAL_PORT + LSR_OFFSET);
    uint8_t iir = inb(SERIAL_PORT + IIR_OFFSET);

    kprintf("%d", iir);
    // Check if data is ready to be extracted from the UART
    if (lsr & 1) {
        serial_handle_receive_byte();
    }
}

void serial_read(uint8_t *buf, size_t len) {
    for (size_t i = 0; i < len; ++i) {
        buf[i] = ring_buffer_read(&serial_buffer);
    }
}
