//! A radio driver integration for the radio found on STM32WL family devices.
use core::future::{poll_fn, Future};
use core::task::Poll;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_stm32::dma::NoDma;
use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO};
use embassy_stm32::subghz::{
    CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, HseTrim, Irq, LoRaBandwidth, LoRaModParams,
    LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PacketType, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
    Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
};
use embassy_sync::waitqueue::AtomicWaker;
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
use lorawan_device::async_device::Timings;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
    Idle,
    Txing,
    Rxing,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RadioError;
static IRQ_WAKER: AtomicWaker = AtomicWaker::new();
/// The radio peripheral keeping the radio state and owning the radio IRQ.
pub struct SubGhzRadio<'d, RS> {
    radio: SubGhz<'d, NoDma, NoDma>,
    switch: RS,
    irq: PeripheralRef<'d, SUBGHZ_RADIO>,
}
#[derive(Default)]
#[non_exhaustive]
pub struct SubGhzRadioConfig {
    pub reg_mode: RegMode,
    pub calibrate_image: CalibrateImage,
    pub pa_config: PaConfig,
    pub tx_params: TxParams,
}
impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
    /// Create a new instance of a SubGhz radio for LoRaWAN.
    pub fn new(
        mut radio: SubGhz<'d, NoDma, NoDma>,
        switch: RS,
        irq: impl Peripheral
 + 'd,
        config: SubGhzRadioConfig,
    ) -> Result {
        into_ref!(irq);
        radio.reset();
        irq.disable();
        irq.set_handler(|_| {
            IRQ_WAKER.wake();
            unsafe { SUBGHZ_RADIO::steal().disable() };
        });
        configure_radio(&mut radio, config)?;
        Ok(Self { radio, switch, irq })
    }
    /// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
    /// the upcoming RX window start.
    async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result {
        trace!("TX request: {:?}", config);
        self.switch.set_tx();
        self.radio
            .set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
        self.set_lora_mod_params(config.rf)?;
        let packet_params = LoRaPacketParams::new()
            .set_preamble_len(8)
            .set_header_type(HeaderType::Variable)
            .set_payload_len(buf.len() as u8)
            .set_crc_en(true)
            .set_invert_iq(false);
        self.radio.set_lora_packet_params(&packet_params)?;
        let irq_cfg = CfgIrq::new().irq_enable_all(Irq::TxDone).irq_enable_all(Irq::Timeout);
        self.radio.set_irq_cfg(&irq_cfg)?;
        self.radio.set_buffer_base_address(0, 0)?;
        self.radio.write_buffer(0, buf)?;
        // The maximum airtime for any LoRaWAN package is 2793.5ms.
        // The value of 4000ms is copied from C driver and gives us a good safety margin.
        self.radio.set_tx(Timeout::from_millis_sat(4000))?;
        trace!("TX started");
        loop {
            let (_status, irq_status) = self.irq_wait().await;
            if irq_status & Irq::TxDone.mask() != 0 {
                trace!("TX done");
                return Ok(0);
            }
            if irq_status & Irq::Timeout.mask() != 0 {
                return Err(RadioError);
            }
        }
    }
    fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
        let mod_params = LoRaModParams::new()
            .set_sf(convert_spreading_factor(&config.spreading_factor))
            .set_bw(convert_bandwidth(&config.bandwidth))
            .set_cr(CodingRate::Cr45)
            .set_ldro_en(matches!(
                (config.spreading_factor, config.bandwidth),
                (SpreadingFactor::_12, Bandwidth::_125KHz)
                    | (SpreadingFactor::_12, Bandwidth::_250KHz)
                    | (SpreadingFactor::_11, Bandwidth::_125KHz)
            ));
        self.radio.set_lora_mod_params(&mod_params)
    }
    /// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must
    /// be able to hold a single LoRaWAN packet.
    async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
        assert!(buf.len() >= 255);
        trace!("RX request: {:?}", config);
        self.switch.set_rx();
        self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
        self.set_lora_mod_params(config)?;
        let packet_params = LoRaPacketParams::new()
            .set_preamble_len(8)
            .set_header_type(HeaderType::Variable)
            .set_payload_len(0xFF)
            .set_crc_en(false)
            .set_invert_iq(true);
        self.radio.set_lora_packet_params(&packet_params)?;
        let irq_cfg = CfgIrq::new()
            .irq_enable_all(Irq::RxDone)
            .irq_enable_all(Irq::PreambleDetected)
            .irq_enable_all(Irq::HeaderValid)
            .irq_enable_all(Irq::HeaderErr)
            .irq_enable_all(Irq::Err)
            .irq_enable_all(Irq::Timeout);
        self.radio.set_irq_cfg(&irq_cfg)?;
        self.radio.set_buffer_base_address(0, 0)?;
        // NOTE: Upper layer handles timeout by cancelling the future
        self.radio.set_rx(Timeout::DISABLED)?;
        trace!("RX started");
        loop {
            let (_status, irq_status) = self.irq_wait().await;
            if irq_status & Irq::RxDone.mask() != 0 {
                let (_status, len, ptr) = self.radio.rx_buffer_status()?;
                let packet_status = self.radio.lora_packet_status()?;
                let rssi = packet_status.rssi_pkt().to_integer();
                let snr = packet_status.snr_pkt().to_integer();
                self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
                self.radio.set_standby(StandbyClk::Rc)?;
                #[cfg(feature = "defmt")]
                trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]);
                #[cfg(feature = "log")]
                trace!("RX done: {:02x?}", &mut buf[..len as usize]);
                return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
            }
            if irq_status & Irq::Timeout.mask() != 0 {
                return Err(RadioError);
            }
        }
    }
    async fn irq_wait(&mut self) -> (Status, u16) {
        poll_fn(|cx| {
            self.irq.unpend();
            self.irq.enable();
            IRQ_WAKER.register(cx.waker());
            let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
            self.radio
                .clear_irq_status(irq_status)
                .expect("error clearing irq status");
            trace!("SUGHZ IRQ 0b{:016b}, {:?}", irq_status, status);
            if irq_status == 0 {
                Poll::Pending
            } else {
                Poll::Ready((status, irq_status))
            }
        })
        .await
    }
}
fn configure_radio(radio: &mut SubGhz<'_, NoDma, NoDma>, config: SubGhzRadioConfig) -> Result<(), RadioError> {
    trace!("Configuring STM32WL SUBGHZ radio");
    radio.set_regulator_mode(config.reg_mode)?;
    radio.set_standby(StandbyClk::Rc)?;
    let tcxo_mode = TcxoMode::new()
        .set_txco_trim(TcxoTrim::Volts1pt7)
        .set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(100)));
    radio.set_tcxo_mode(&tcxo_mode)?;
    // Reduce input capacitance as shown in Reference Manual "Figure 23. HSE32 TCXO control".
    // The STM32CUBE C driver also does this.
    radio.set_hse_in_trim(HseTrim::MIN)?;
    // Re-calibrate everything after setting the TXCO config.
    radio.calibrate(0x7F)?;
    radio.calibrate_image(config.calibrate_image)?;
    radio.set_pa_config(&config.pa_config)?;
    radio.set_tx_params(&config.tx_params)?;
    radio.set_pa_ocp(Ocp::Max140m)?;
    radio.set_packet_type(PacketType::LoRa)?;
    radio.set_lora_sync_word(LoRaSyncWord::Public)?;
    trace!("Done initializing STM32WL SUBGHZ radio");
    Ok(())
}
impl<'d, RS: RadioSwitch> PhyRxTx for SubGhzRadio<'d, RS> {
    type PhyError = RadioError;
    type TxFuture<'m> = impl Future