use core::fmt::{Error, Write};

/// Initialize the UART driver by setting
/// the word length, FIFOs, and interrupts
pub fn uart_init(base_addr: usize) {
    let ptr = base_addr as *mut u8;
    unsafe {
        // First, set the word length, which
        // are bits 0, and 1 of the line control register (LCR)
        // which is at base_address + 3
        // We can easily write the value 3 here or 0b11, but I'm
        // extending it so that it is clear we're setting two individual
        // fields
        //         Word 0     Word 1
        //         ~~~~~~     ~~~~~~
        let lcr = (1 << 0) | (1 << 1);
        ptr.add(3).write_volatile(lcr);

        // Now, enable the FIFO, which is bit index 0 of the FIFO
        // control register (FCR at offset 2).
        // Again, we can just write 1 here, but when we use left shift,
        // it's easier to see that we're trying to write bit index #0.
        ptr.add(2).write_volatile(1 << 0);

        // Enable receiver buffer interrupts, which is at bit index
        // 0 of the interrupt enable register (IER at offset 1).
        ptr.add(1).write_volatile(1 << 0);

        // If we cared about the divisor, the code below would set the divisor
        // from a global clock rate of 22.729 MHz (22,729,000 cycles per second)
        // to a signaling rate of 2400 (BAUD). We usually have much faster signalling
        // rates nowadays, but this demonstrates what the divisor actually does.
        // The formula given in the NS16500A specification for calculating the divisor
        // is:
        // divisor = ceil( (clock_hz) / (baud_sps x 16) )
        // So, we substitute our values and get:
        // divisor = ceil( 22_729_000 / (2400 x 16) )
        // divisor = ceil( 22_729_000 / 38_400 )
        // divisor = ceil( 591.901 ) = 592

        // The divisor register is two bytes (16 bits), so we need to split the value
        // 592 into two bytes. Typically, we would calculate this based on measuring
        // the clock rate, but again, for our purposes [qemu], this doesn't really do
        // anything.
        let divisor: u16 = 592;
        let divisor_least: u8 = (divisor & 0xff).try_into().unwrap();
        let divisor_most: u8 = (divisor >> 8).try_into().unwrap();

        // Notice that the divisor register DLL (divisor latch least) and DLM (divisor
        // latch most) have the same base address as the receiver/transmitter and the
        // interrupt enable register. To change what the base address points to, we
        // open the "divisor latch" by writing 1 into the Divisor Latch Access Bit
        // (DLAB), which is bit index 7 of the Line Control Register (LCR) which
        // is at base_address + 3.
        ptr.add(3).write_volatile(lcr | 1 << 7);

        // Now, base addresses 0 and 1 point to DLL and DLM, respectively.
        // Put the lower 8 bits of the divisor into DLL
        ptr.add(0).write_volatile(divisor_least);
        ptr.add(1).write_volatile(divisor_most);

        // Now that we've written the divisor, we never have to touch this again. In
        // hardware, this will divide the global clock (22.729 MHz) into one suitable
        // for 2,400 signals per second. So, to once again get access to the
        // RBR/THR/IER registers, we need to close the DLAB bit by clearing it to 0.
        ptr.add(3).write_volatile(lcr);
    }
}

fn uart_get(base_addr: usize) -> Option<u8> {
    let ptr = base_addr as *mut u8;
    unsafe {
        // Bit index #5 is the Line Control Register.
        if ptr.add(5).read_volatile() & 1 == 0 {
            // The DR bit is 0, meaning no data
            None
        } else {
            // The DR bit is 1, meaning data!
            Some(ptr.add(0).read_volatile())
        }
    }
}

fn uart_put(base_addr: usize, c: u8) {
    let ptr = base_addr as *mut u8;
    unsafe {
        // If we get here, the transmitter is empty, so transmit
        // our stuff!
        ptr.add(0).write_volatile(c);
    }
}

pub struct Uart {
    base_address: usize,
}

impl Uart {
    pub fn new(base_address: usize) -> Self {
        Uart { base_address }
    }

    pub fn get(&self) -> Option<u8> {
        uart_get(self.base_address)
    }

    pub fn put(&self, c: u8) {
        uart_put(self.base_address, c);
    }

    pub fn init(&self) {
        uart_init(self.base_address);
    }
}

// This is a slightly different syntax. Write is this "trait", meaning it is much like
// an interface where we're just guaranteeing a certain function signature. In the Write
// trait, one is absolutely required to be implemented, which is write_str. There are other
// functions, but they all rely on write_str(), so their default implementation is OK for now.
impl Write for Uart {
    // The trait Write expects us to write the function write_str
    // which looks like:
    fn write_str(&mut self, s: &str) -> Result<(), Error> {
        for c in s.bytes() {
            self.put(c);
        }
        // Return that we succeeded.
        Ok(())
    }
}
