/src/console.rs
use core::cell::RefCell;
use core::fmt::Write;

use critical_section::Mutex;
use usb_device::UsbError;

use crate::{capabilities::CapType, peripherals::with_usb, task::with_task_manager};

static CONSOLE: Mutex<RefCell<Console>> = Mutex::new(RefCell::new(Console::new()));

#[derive(Debug)]
pub enum ConsoleError {
    Read,
    Write,
    Blocked,
}

pub struct Console {
    read_buffer: heapless::Deque<u8, 64>,
}

impl Console {
    const fn new() -> Console {
        Console {
            read_buffer: heapless::Deque::new(),
        }
    }

    pub fn fill_read_buffer(&mut self, data: &[u8]) {
        for b in data {
            match self.read_buffer.push_back(*b) {
                Ok(()) => (),
                Err(_) => break, // All remaining characters read will be dropped
            }
            with_task_manager(|t| t.unblock_task_with_capability(CapType::ConsoleRead));
        }
    }

    pub fn write_ready(&mut self) {
        with_task_manager(|tm| tm.unblock_task_with_capability(CapType::ConsoleWrite));
    }

    pub fn write(&mut self, bytes: impl AsRef<[u8]>) -> Result<usize, ConsoleError> {
        with_usb(|u| match u.serial().write(bytes.as_ref()) {
            Ok(n) => Ok(n),
            Err(UsbError::WouldBlock) => Err(ConsoleError::Blocked),
            Err(_) => Err(ConsoleError::Write),
        })
    }

    pub fn read(&mut self, data: &mut [u8]) -> Result<usize, ConsoleError> {
        if self.read_buffer.is_empty() {
            return Err(ConsoleError::Blocked);
        }
        let mut l = 0;
        for i in 0..data.len() {
            data[i] = match self.read_buffer.pop_front() {
                Some(b) => b,
                None => break,
            };
            l += 1;
        }
        Ok(l)
    }
}

pub fn init_console() {
    // Does nothing since console can be statically initialized
}

pub fn with_console<F, R>(mut f: F) -> R
where
    F: FnMut(&mut Console) -> R,
{
    critical_section::with(|cs| {
        let mut console = CONSOLE.borrow_ref_mut(cs);
        f(&mut console)
    })
}

pub fn write(bytes: &[u8]) -> Result<usize, ConsoleError> {
    critical_section::with(|cs| {
        let mut console = CONSOLE.borrow_ref_mut(cs);
        console.write(bytes)
    })
}

pub fn read(bytes: &mut [u8]) -> Result<usize, ConsoleError> {
    critical_section::with(|cs| {
        let mut console = CONSOLE.borrow_ref_mut(cs);
        console.read(bytes)
    })
}

pub fn flush() {
    with_usb(
        |u| {
            while let Err(UsbError::WouldBlock) = u.serial().flush() {}
        },
    );
}

pub(crate) fn _printf(s: core::fmt::Arguments) {
    let mut buf: heapless::Vec<u8, 256> = heapless::Vec::new();
    write!(&mut buf, "{}", s).expect("write!");
    let mut bytes_written = 0;
    while bytes_written < buf.len() {
        bytes_written += match write(&buf[bytes_written..]) {
            Ok(n) => n,
            Err(ConsoleError::Blocked) => 0,
            Err(_) => panic!("printf"),
        }
    }
}

#[macro_export]
macro_rules! kprintf {
    ($($arg:tt)*) => {{
        $crate::console::_printf(core::format_args!($($arg)*));
    }}
}

#[macro_export]
macro_rules! kprint {
    ($($arg:tt)*) => {{
        $($crate::console::write($arg.as_bytes());)*
    }}
}