//! Implement the Qemu magic UART.

use core::fmt;

use crate::traits::console::{Console, Write};

use crate::traits::synchronization::{DummyMutex, Mutex};

/// The address for the magic qemu output
const QEMU_MAGIC_OUTPUT_ADDR: *mut u8 = 0x3F20_1000 as *mut u8;

/// The unique qemu output allowing access to the qemu output.
static QEMU_OUTPUT: QemuOutput = QemuOutput::new();

/// A structure allowing access to the qemu magic output.
struct QemuOutput {
    inner: DummyMutex<QemuOutputInner>,
}

/// Inner Qemu output.
struct QemuOutputInner;

impl QemuOutputInner {
    /// Constructor for [`QemuOutputInner`].
    const fn new() -> Self {
        Self {}
    }

    /// Write a character to the output.
    fn write_char(&mut self, c: char) {
        unsafe {
            core::ptr::write_volatile(QEMU_MAGIC_OUTPUT_ADDR, c as u8);
        }
    }
}

impl QemuOutput {
    /// Constructor for [`QemuOutput`].
    pub const fn new() -> Self {
        Self {
            inner: DummyMutex::new(QemuOutputInner::new()),
        }
    }
}

/// Allow to use QemuOutputInner for print! and formating macros.
/// `write_str` needs `&mut self` (mutable ref), so we can implement 
/// it only on the inner type, the `QemuOutput` need to be manipulable
/// using unmutable references (`&self`), so we will use a custom 
/// interface for it.
impl fmt::Write for QemuOutputInner {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            // \n -> \r\n
            if c == '\n' {
                self.write_char('\r');
            }
            self.write_char(c);
        }
        Ok(())
    }
}

impl Write for QemuOutput {
    fn write_char(&self, c: char) {
        self.inner.lock(|q_out| q_out.write_char(c))
    }

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

    /// Empty function, the qemu uart has no buffering afaik
    fn flush(&self) {
        // self.inner.lock(|q_out| q_out.flush())
    }
}

impl Console for QemuOutput {}

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