//! Driver for the UART.

use core::arch::asm;
use core::fmt;
use core::ptr::{read_volatile, write_volatile};

use crate::utils::field::Field;
use crate::traits::console::{Write, Console};

use super::memory_map::uart::*;


pub struct Uart {
    initialized: bool,
}

const TMP_UARTEN: Field = Field::new(CR, 0, 1);
const TMP_TXE: Field = Field::new(CR, 8, 1);
const TMP_BUSY: Field = Field::new(FR, 3, 1);
const TMP_TXFF: Field = Field::new(FR, 5, 1);
const TMP_FEN: Field = Field::new(LCRH, 4, 1);
const TMP_WLEN: Field = Field::new(LCRH, 5, 2);
const TMP_IBRD: Field = Field::new(IBRD, 0, 16);
const TMP_FBRD: Field = Field::new(FBRD, 0, 6);
const TMP_DATA: Field = Field::new(DR, 0, 8);

impl Uart {
    // TODO: not sure this should be public outside of bsp.
    /// Constructor for [`Uart`].
    pub const fn new() -> Self {
        Uart { initialized: false }
    }

    /// Initialise the UART.
    pub fn init(&mut self) {
        // TODO: Recover from possible previous test.
        
        self.flush();
        
        // Stop UART
        let mut cr_val = unsafe { read_volatile(TMP_UARTEN.get_address() as *mut u32) };
        cr_val &= !TMP_UARTEN.get_mask();
        unsafe { write_volatile(TMP_UARTEN.get_address() as *mut u32, cr_val); }

        // Flush the FIFOs
        let mut lcrh_val = unsafe { read_volatile(TMP_FEN.get_address() as *mut u32) };
        lcrh_val &= !TMP_FEN.get_mask();
        unsafe { write_volatile(TMP_FEN.get_address() as *mut u32, lcrh_val); }

        // Clear all interrupt
        unsafe { write_volatile(ICR as *mut u32, 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?
        unsafe { write_volatile(TMP_IBRD.get_address() as *mut u32, 26 & TMP_IBRD.get_mask()); }
        unsafe { write_volatile(TMP_FBRD.get_address() as *mut u32, 3 & TMP_FBRD.get_mask()); }

        lcrh_val = 0;
        // Set word len to 8
        lcrh_val |= 0b11 << TMP_WLEN.get_offset();
        // Reenable the FIFOs
        lcrh_val |= TMP_FEN.get_mask();
        unsafe { write_volatile(LCRH as *mut u32, lcrh_val); }

        let mut cr_val = 0;
        cr_val |= TMP_TXE.get_mask(); // enable TX
        unsafe { write_volatile(TMP_TXE.get_address() as *mut u32, cr_val); }
        
        // Start the UART
        cr_val |= TMP_UARTEN.get_mask();
        unsafe { write_volatile(TMP_UARTEN.get_address() as *mut u32, cr_val); }

        self.initialized = true;
    }

    /// Test if the UART is busy.
    fn is_busy(&self) -> bool {
        let fr_val = unsafe { read_volatile(TMP_BUSY.get_address() as *mut u32) };
        (fr_val & TMP_BUSY.get_mask()) != 0
    }

    /// Test if the the TX FIFO is full.
    fn is_tx_full(&self) -> bool {
        let fr_val = unsafe { read_volatile(TMP_TXFF.get_address() as *mut u32) };
        (fr_val & TMP_TXFF.get_mask()) != 0
    }
}

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

// TODO: add sync
impl Write for 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") };
        }
        unsafe { write_volatile(TMP_DATA.get_address() as *mut u32, c as u32) };
    }

    fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result {
        // TODO: use syncronisation here
        let mut new_uart = Uart { initialized: self.initialized };
        fmt::Write::write_fmt(&mut new_uart, args)
    }

    fn flush(&self) {
        while self.is_busy() {
            unsafe { asm!("nop") };
        }
    }
}

impl Console for Uart {}

static UART: Uart = Uart { initialized: true }; // TODO: use sync

pub fn init() {
    let mut uart = Uart::new();
    uart.init();
}

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