mod serial_config;

use numtoa::NumToA;

use core::future::Future;

use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::USB;
use embassy_rp::usb::{Driver, InterruptHandler};

use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
use embassy_usb::{Builder, Config, UsbDevice};

pub use serial_config::SerialConfig;

static mut DEVICE_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256];
static mut CONFIG_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256];
static mut BOS_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256];
static mut MSOS_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256];
static mut CONTROL_BUFFER: [u8; 64] = [0; 64];
static mut STATE: Option<State<'static>> = None;
static mut USB: Option<UsbDevice<'static, Driver<'static, USB>>> = None;

pub trait Sender {
    async fn send_msg(&mut self, msg: &str);

    async fn send_number<T, I>(&mut self, i: T, base: I)
    where
        T: NumToA<I>;
}

bind_interrupts!(struct Irqs {
    USBCTRL_IRQ => InterruptHandler<USB>;
});

pub struct Serial<'a> {
    class: CdcAcmClass<'a, Driver<'a, USB>>,
}

impl Serial<'static> {
    pub async fn new(
        usb: USB,
        serial_config: SerialConfig<'static>,
    ) -> (Serial<'static>, impl Future<Output = !>) {
        // Create the driver, from the HAL.
        let driver = Driver::new(usb, Irqs);

        // Create embassy-usb Config
        let mut config = Config::new(
            serial_config.vendor_id as u16,
            serial_config.product_id as u16,
        );
        config.manufacturer = Some(serial_config.manufacturer);
        config.product = Some(serial_config.product);
        config.serial_number = Some(serial_config.serial_number);
        config.max_power = 100;
        config.max_packet_size_0 = 64;

        // Required for windows compatiblity.
        // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
        config.device_class = 0xEF;
        config.device_sub_class = 0x02;
        config.device_protocol = 0x01;
        config.composite_with_iads = true;

        unsafe {
            STATE = Some(State::new());
        }

        let mut builder = Builder::new(
            driver,
            config,
            unsafe { &mut DEVICE_DESCRIPTOR_BUFFER },
            unsafe { &mut CONFIG_DESCRIPTOR_BUFFER },
            unsafe { &mut BOS_DESCRIPTOR_BUFFER },
            unsafe { &mut MSOS_DESCRIPTOR_BUFFER },
            unsafe { &mut CONTROL_BUFFER },
        );

        // Create classes on the builder.
        let class = CdcAcmClass::new(&mut builder, unsafe { STATE.as_mut().unwrap() }, 64);

        // Build the builder.
        unsafe {
            USB = Some(builder.build());
        };

        (Self { class }, Self::run_usb().await)
    }

    async fn run_usb() -> impl Future<Output = !> {
        unsafe { USB.as_mut().unwrap().run() }
    }

    pub async fn send_msg(&mut self, s: &str) {
        self.class.write_packet(s.as_bytes()).await.unwrap_or(());
    }

    pub async fn send_number<T, I>(&mut self, i: T, base: I)
    where
        T: NumToA<I>,
    {
        let mut buf = [0; 256];

        let s = i.numtoa_str(base, &mut buf);

        self.send_msg(s).await;
    }
}

impl Sender for Serial<'static> {
    async fn send_msg(&mut self, msg: &str) {
        self.send_msg(msg).await;
    }

    async fn send_number<T, I>(&mut self, i: T, base: I)
    where
        T: NumToA<I>,
    {
        self.send_number(i, base).await;
    }
}