//! Driver for the UART.

use core::arch::asm;
use core::fmt;

use crate::traits::console::{Console, Read, Write};
use crate::traits::synchronization::{DummyMutex, Mutex};

use super::memory_map::uart as mm;

pub struct Uart<'a> {
    inner: DummyMutex<UartInner<'a>>,
}

pub struct UartInner<'a> {
    initialized: bool,
    memory_map: &'a mm::UartMemoryMap,
}

impl<'a> UartInner<'a> {
    /// Constructor for [`Uart`].
    const fn new(memory_map: &'a mm::UartMemoryMap) -> Self {
        Self { initialized: false, memory_map }
    }

    /// Initialise the UART.
    fn init(&mut self) {
        // TODO: Recover from possible previous test.
        
        self.flush();
        
        // Stop UART, see doc of CR register for the config process (P 185, doc BCM2835)
        self.memory_map.cr_uarten.read_and_write(0);

        // Flush the FIFOs
        self.memory_map.lcrh_fen.read_and_write(0);

        // Clear all interrupt
        self.memory_map.icr.write_without_read(0);

        // Config UART
        // 8N1 115_200 bauds.

        // divbaud = freq/16/baudrate = 48_000_000 / 16 / 115_200 = 26.041666666666668
        // => IBRD = 26
        // => FBRD = round(0.041666666666668 * 64) = 3 // TODO: why 64?
        self.memory_map.ibrd.write_without_read(26);
        self.memory_map.fbrd.write_without_read(3);

        // Set word len to 8
        let lcrh_val = self.memory_map.lcrh_wlen.read_and_write_to_u32(0b11, 0);
        // Reenable the FIFOs
        let lcrh_val = self.memory_map.lcrh_fen.read_and_write_to_u32(1, lcrh_val);
        self.memory_map.lcrh.write_without_read(lcrh_val);

        let cr_val = self.memory_map.cr_txe.read_and_write_to_u32(1, 0);
        let cr_val =  self.memory_map.cr_rxe.read_and_write_to_u32(1, cr_val);
        // Again, see doc of CR register for the config process (P 185, doc BCM2835)
        self.memory_map.cr.write_without_read(cr_val);
        
        // Start the UART
        self.memory_map.cr_uarten.read_and_write(1);
        self.initialized = true;
    }

    /// Test if the UART is busy.
    fn is_busy(&self) -> bool {
        self.memory_map.fr_busy.read() != 0
    }

    /// Test if the TX FIFO is full.
    fn is_tx_full(&self) -> bool {
        self.memory_map.fr_txff.read() != 0
    }

    /// Test if the RX FIFO is empty
    fn is_rx_empty(&self) -> bool {
        self.memory_map.fr_rxfe.read() != 0
    }

    /// Write a character to the Uart
    fn write_char(&self, c: char) {
        if !self.initialized {
            panic!("Cannot write to a non initialized UART");
        }
        while self.is_tx_full() {
            use super::gpio;
            let _ = gpio::set_pin_output_state(20, gpio::PinOutputState::High);
            unsafe { asm!("nop") };
        }
        self.memory_map.dr_data.write_without_read(c as u32);
    }

    /// Read a character from the UART.
    /// Blocking.
    fn blocking_read_char(&self) -> char {
        while self.is_rx_empty() {
            unsafe { asm!("nop") };
        }
        self.memory_map.dr_data.read() as u8 as char
    }

    /// Flush the output of the uart
    fn flush(&self) {
        while self.is_busy() {
            unsafe { asm!("nop") };
        }
    }

    /// Flush the input of the uart
    fn flush_input(&self) {
        while !self.is_rx_empty() {
            let _ = self.memory_map.dr_data.read() as u8 as char;
        }
    }
}

// Allow the use of uart for fmt::Write::write_fmt.
impl fmt::Write for UartInner<'_> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            if c == '\n' {
                UartInner::write_char(self, '\r');
            }
            UartInner::write_char(self, c);
        }
        Ok(())
    }
}

impl Write for Uart<'_> {
    fn write_char(&self, c: char) {
        self.inner.lock(|uart| uart.write_char(c))
    }

    fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result {
        self.inner.lock(|uart| fmt::Write::write_fmt(uart, args))
    }

    fn flush(&self) {
        self.inner.lock(|uart| uart.flush())
    }
}

impl Read for Uart<'_> {
    fn read_char(&self) -> char {
        self.inner.lock(|uart| uart.blocking_read_char())
    }

    fn flush_input(&self) {
        self.inner.lock(|uart| uart.flush_input())
    }
}

impl<'a> Uart<'a> {

    // TODO: not sure this should be public? public in bsp only?
    /// Create a new UART object
    const fn new(memory_map: &'a mm::UartMemoryMap) -> Self {
        Self {
            inner: DummyMutex::new(
                UartInner::new(memory_map)
            )
        }
    }

    /// Initialize the UART.
    fn init(&self) {
        self.inner.lock(|uart| uart.init())
    }
}

impl Console for Uart<'_> {}

/// The UART object
static UART: Uart = Uart::new(&mm::UART);

pub fn init() {
    UART.init();
}

// TODO: move?
/// Return a reference to the Uart Output.
pub fn console() -> &'static dyn Console {
    &UART
}
