//! Sub-GHz radio operating in the 150 - 960 MHz ISM band
//!
//! The main radio type is [`SubGhz`].
//!
//! ## LoRa user notice
//!
//! The Sub-GHz radio may have an undocumented erratum, see this ST community
//! post for more information: [link]
//!
//! [link]: https://community.st.com/s/question/0D53W00000hR8kpSAC/stm32wl55-erratum-clairification
//!
//! NOTE: This HAL is based on https://github.com/newAM/stm32wl-hal, but adopted for use with the stm32-metapac
//! and SPI HALs.

mod bit_sync;
mod cad_params;
mod calibrate;
mod fallback_mode;
mod hse_trim;
mod irq;
mod lora_sync_word;
mod mod_params;
mod ocp;
mod op_error;
mod pa_config;
mod packet_params;
mod packet_status;
mod packet_type;
mod pkt_ctrl;
mod pmode;
mod pwr_ctrl;
mod reg_mode;
mod rf_frequency;
mod rx_timeout_stop;
mod sleep_cfg;
mod smps;
mod standby_clk;
mod stats;
mod status;
mod tcxo_mode;
mod timeout;
mod tx_params;
mod value_error;

pub use bit_sync::BitSync;
pub use cad_params::{CadParams, ExitMode, NbCadSymbol};
pub use calibrate::{Calibrate, CalibrateImage};
use embassy_hal_common::ratio::Ratio;
pub use fallback_mode::FallbackMode;
pub use hse_trim::HseTrim;
pub use irq::{CfgIrq, Irq, IrqLine};
pub use lora_sync_word::LoRaSyncWord;
pub use mod_params::{
    BpskModParams, CodingRate, FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape, LoRaBandwidth,
    LoRaModParams, SpreadingFactor,
};
pub use ocp::Ocp;
pub use op_error::OpError;
pub use pa_config::{PaConfig, PaSel};
pub use packet_params::{
    AddrComp, BpskPacketParams, CrcType, GenericPacketParams, HeaderType, LoRaPacketParams, PreambleDetection,
};
pub use packet_status::{FskPacketStatus, LoRaPacketStatus};
pub use packet_type::PacketType;
pub use pkt_ctrl::{InfSeqSel, PktCtrl};
pub use pmode::PMode;
pub use pwr_ctrl::{CurrentLim, PwrCtrl};
pub use reg_mode::RegMode;
pub use rf_frequency::RfFreq;
pub use rx_timeout_stop::RxTimeoutStop;
pub use sleep_cfg::{SleepCfg, Startup};
pub use smps::SmpsDrv;
pub use standby_clk::StandbyClk;
pub use stats::{FskStats, LoRaStats, Stats};
pub use status::{CmdStatus, Status, StatusMode};
pub use tcxo_mode::{TcxoMode, TcxoTrim};
pub use timeout::Timeout;
pub use tx_params::{RampTime, TxParams};
pub use value_error::ValueError;

use crate::dma::NoDma;
use crate::peripherals::SUBGHZSPI;
use crate::rcc::sealed::RccPeripheral;
use crate::spi::{BitOrder, Config as SpiConfig, MisoPin, MosiPin, SckPin, Spi, MODE_0};
use crate::time::Hertz;
use crate::{pac, Peripheral};

/// Passthrough for SPI errors (for now)
pub type Error = crate::spi::Error;

struct Nss {
    _priv: (),
}

impl Nss {
    #[inline(always)]
    pub fn new() -> Nss {
        Self::clear();
        Nss { _priv: () }
    }

    /// Clear NSS, enabling SPI transactions
    #[inline(always)]
    fn clear() {
        let pwr = pac::PWR;
        unsafe {
            pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW));
        }
    }

    /// Set NSS, disabling SPI transactions
    #[inline(always)]
    fn set() {
        let pwr = pac::PWR;
        unsafe {
            pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH));
        }
    }
}

impl Drop for Nss {
    fn drop(&mut self) {
        Self::set()
    }
}

/// Wakeup the radio from sleep mode.
///
/// # Safety
///
/// 1. This must not be called when the SubGHz radio is in use.
/// 2. This must not be called when the SubGHz SPI bus is in use.
///
/// # Example
///
/// See [`SubGhz::set_sleep`]
#[inline]
unsafe fn wakeup() {
    Nss::clear();
    // RM0453 rev 2 page 171 section 5.7.2 "Sleep mode"
    // on a firmware request via the sub-GHz radio SPI NSS signal
    // (keeping sub-GHz radio SPI NSS low for at least 20 μs)
    //
    // I have found this to be a more reliable mechanism for ensuring NSS is
    // pulled low for long enough to wake the radio.
    while rfbusys() {}
    Nss::set();
}

/// Returns `true` if the radio is busy.
///
/// This may not be set immediately after NSS going low.
///
/// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more
/// details.
#[inline]
fn rfbusys() -> bool {
    // safety: atmoic read with no side-effects
    //unsafe { (*pac::PWR::ptr()).sr2.read().rfbusys().is_busy() }
    let pwr = pac::PWR;
    unsafe { pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY }
}

/*
/// Returns `true` if the radio is busy or NSS is low.
///
/// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more
/// details.
#[inline]
fn rfbusyms() -> bool {
    let pwr = pac::PWR;
    unsafe { pwr.sr2().read().rfbusyms() == pac::pwr::vals::Rfbusyms::BUSY }
}
*/

/// Sub-GHz radio peripheral
pub struct SubGhz<'d, Tx, Rx> {
    spi: Spi<'d, SUBGHZSPI, Tx, Rx>,
}

impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> {
    fn pulse_radio_reset() {
        let rcc = pac::RCC;
        unsafe {
            rcc.csr().modify(|w| w.set_rfrst(true));
            rcc.csr().modify(|w| w.set_rfrst(false));
        }
    }

    // TODO: This should be replaced with async handling based on IRQ
    fn poll_not_busy(&self) {
        let mut count: u32 = 1_000_000;
        while rfbusys() {
            count -= 1;
            if count == 0 {
                let pwr = pac::PWR;
                unsafe {
                    panic!(
                        "rfbusys timeout pwr.sr2=0x{:X} pwr.subghzspicr=0x{:X} pwr.cr1=0x{:X}",
                        pwr.sr2().read().0,
                        pwr.subghzspicr().read().0,
                        pwr.cr1().read().0
                    );
                }
            }
        }
    }

    /// Create a new sub-GHz radio driver from a peripheral.
    ///
    /// This will reset the radio and the SPI bus, and enable the peripheral
    /// clock.
    pub fn new(
        peri: impl Peripheral<P = SUBGHZSPI> + 'd,
        sck: impl Peripheral<P = impl SckPin<SUBGHZSPI>> + 'd,
        mosi: impl Peripheral<P = impl MosiPin<SUBGHZSPI>> + 'd,
        miso: impl Peripheral<P = impl MisoPin<SUBGHZSPI>> + 'd,
        txdma: impl Peripheral<P = Tx> + 'd,
        rxdma: impl Peripheral<P = Rx> + 'd,
    ) -> Self {
        Self::pulse_radio_reset();

        // see RM0453 rev 1 section 7.2.13 page 291
        // The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two.
        // The SUBGHZSPI_SCK clock maximum speed must not exceed 16 MHz.
        let clk = Hertz(core::cmp::min(SUBGHZSPI::frequency().0 / 2, 16_000_000));
        let mut config = SpiConfig::default();
        config.mode = MODE_0;
        config.bit_order = BitOrder::MsbFirst;
        let spi = Spi::new(peri, sck, mosi, miso, txdma, rxdma, clk, config);

        unsafe { wakeup() };

        SubGhz { spi }
    }

    pub fn is_busy(&mut self) -> bool {
        rfbusys()
    }

    pub fn reset(&mut self) {
        Self::pulse_radio_reset();
    }
}

impl<'d> SubGhz<'d, NoDma, NoDma> {
    fn read(&mut self, opcode: OpCode, data: &mut [u8]) -> Result<(), Error> {
        self.poll_not_busy();
        {
            let _nss: Nss = Nss::new();
            self.spi.blocking_write(&[opcode as u8])?;
            self.spi.blocking_transfer_in_place(data)?;
        }
        self.poll_not_busy();
        Ok(())
    }

    /// Read one byte from the sub-Ghz radio.
    fn read_1(&mut self, opcode: OpCode) -> Result<u8, Error> {
        let mut buf: [u8; 1] = [0; 1];
        self.read(opcode, &mut buf)?;
        Ok(buf[0])
    }

    /// Read a fixed number of bytes from the sub-Ghz radio.
    fn read_n<const N: usize>(&mut self, opcode: OpCode) -> Result<[u8; N], Error> {
        let mut buf: [u8; N] = [0; N];
        self.read(opcode, &mut buf)?;
        Ok(buf)
    }

    fn write(&mut self, data: &[u8]) -> Result<(), Error> {
        self.poll_not_busy();
        {
            let _nss: Nss = Nss::new();
            self.spi.blocking_write(data)?;
        }
        self.poll_not_busy();
        Ok(())
    }

    pub fn write_buffer(&mut self, offset: u8, data: &[u8]) -> Result<(), Error> {
        self.poll_not_busy();
        {
            let _nss: Nss = Nss::new();
            self.spi.blocking_write(&[OpCode::WriteBuffer as u8, offset])?;
            self.spi.blocking_write(data)?;
        }
        self.poll_not_busy();

        Ok(())
    }

    /// Read the radio buffer at the given offset.
    ///
    /// The offset and length of a received packet is provided by
    /// [`rx_buffer_status`](Self::rx_buffer_status).
    pub fn read_buffer(&mut self, offset: u8, buf: &mut [u8]) -> Result<Status, Error> {
        let mut status_buf: [u8; 1] = [0];

        self.poll_not_busy();
        {
            let _nss: Nss = Nss::new();
            self.spi.blocking_write(&[OpCode::ReadBuffer as u8, offset])?;
            self.spi.blocking_transfer_in_place(&mut status_buf)?;
            self.spi.blocking_transfer_in_place(buf)?;
        }
        self.poll_not_busy();

        Ok(status_buf[0].into())
    }
}

// helper to pack register writes into a single buffer to avoid multiple DMA
// transfers
macro_rules! wr_reg {
    [$reg:ident, $($data:expr),+] => {
        &[
            OpCode::WriteRegister as u8,
            Register::$reg.address().to_be_bytes()[0],
            Register::$reg.address().to_be_bytes()[1],
            $($data),+
        ]
    };
}

// 5.8.2
/// Register access
impl<'d> SubGhz<'d, NoDma, NoDma> {
    // register write with variable length data
    fn write_register(&mut self, register: Register, data: &[u8]) -> Result<(), Error> {
        let addr: [u8; 2] = register.address().to_be_bytes();

        self.poll_not_busy();
        {
            let _nss: Nss = Nss::new();
            self.spi
                .blocking_write(&[OpCode::WriteRegister as u8, addr[0], addr[1]])?;
            self.spi.blocking_write(data)?;
        }
        self.poll_not_busy();

        Ok(())
    }

    /// Set the LoRa bit synchronization.
    pub fn set_bit_sync(&mut self, bs: BitSync) -> Result<(), Error> {
        self.write(wr_reg![GBSYNC, bs.as_bits()])
    }

    /// Set the generic packet control register.
    pub fn set_pkt_ctrl(&mut self, pkt_ctrl: PktCtrl) -> Result<(), Error> {
        self.write(wr_reg![GPKTCTL1A, pkt_ctrl.as_bits()])
    }

    /// Set the initial value for generic packet whitening.
    ///
    /// This sets the first 8 bits, the 9th bit is set with
    /// [`set_pkt_ctrl`](Self::set_pkt_ctrl).
    pub fn set_init_whitening(&mut self, init: u8) -> Result<(), Error> {
        self.write(wr_reg![GWHITEINIRL, init])
    }

    /// Set the initial value for generic packet CRC polynomial.
    pub fn set_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> {
        let bytes: [u8; 2] = polynomial.to_be_bytes();
        self.write(wr_reg![GCRCINIRH, bytes[0], bytes[1]])
    }

    /// Set the generic packet CRC polynomial.
    pub fn set_initial_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> {
        let bytes: [u8; 2] = polynomial.to_be_bytes();
        self.write(wr_reg![GCRCPOLRH, bytes[0], bytes[1]])
    }

    /// Set the synchronization word registers.
    pub fn set_sync_word(&mut self, sync_word: &[u8; 8]) -> Result<(), Error> {
        self.write_register(Register::GSYNC7, sync_word)
    }

    /// Set the LoRa synchronization word registers.
    pub fn set_lora_sync_word(&mut self, sync_word: LoRaSyncWord) -> Result<(), Error> {
        let bytes: [u8; 2] = sync_word.bytes();
        self.write(wr_reg![LSYNCH, bytes[0], bytes[1]])
    }

    /// Set the RX gain control.
    pub fn set_rx_gain(&mut self, pmode: PMode) -> Result<(), Error> {
        self.write(wr_reg![RXGAINC, pmode as u8])
    }

    /// Set the power amplifier over current protection.
    pub fn set_pa_ocp(&mut self, ocp: Ocp) -> Result<(), Error> {
        self.write(wr_reg![PAOCP, ocp as u8])
    }

    /// Restart the radio RTC.
    ///
    /// This is used to workaround an erratum for [`set_rx_duty_cycle`].
    ///
    /// [`set_rx_duty_cycle`]: crate::subghz::SubGhz::set_rx_duty_cycle
    pub fn restart_rtc(&mut self) -> Result<(), Error> {
        self.write(wr_reg![RTCCTLR, 0b1])
    }

    /// Set the radio real-time-clock period.
    ///
    /// This is used to workaround an erratum for [`set_rx_duty_cycle`].
    ///
    /// [`set_rx_duty_cycle`]: crate::subghz::SubGhz::set_rx_duty_cycle
    pub fn set_rtc_period(&mut self, period: Timeout) -> Result<(), Error> {
        let tobits: u32 = period.into_bits();
        self.write(wr_reg![
            RTCPRDR2,
            (tobits >> 16) as u8,
            (tobits >> 8) as u8,
            tobits as u8
        ])
    }

    /// Set the HSE32 crystal OSC_IN load capacitor trimming.
    pub fn set_hse_in_trim(&mut self, trim: HseTrim) -> Result<(), Error> {
        self.write(wr_reg![HSEINTRIM, trim.into()])
    }

    /// Set the HSE32 crystal OSC_OUT load capacitor trimming.
    pub fn set_hse_out_trim(&mut self, trim: HseTrim) -> Result<(), Error> {
        self.write(wr_reg![HSEOUTTRIM, trim.into()])
    }

    /// Set the SMPS clock detection enabled.
    ///
    /// SMPS clock detection must be enabled fore enabling the SMPS.
    pub fn set_smps_clock_det_en(&mut self, en: bool) -> Result<(), Error> {
        self.write(wr_reg![SMPSC0, (en as u8) << 6])
    }

    /// Set the power current limiting.
    pub fn set_pwr_ctrl(&mut self, pwr_ctrl: PwrCtrl) -> Result<(), Error> {
        self.write(wr_reg![PC, pwr_ctrl.as_bits()])
    }

    /// Set the maximum SMPS drive capability.
    pub fn set_smps_drv(&mut self, drv: SmpsDrv) -> Result<(), Error> {
        self.write(wr_reg![SMPSC2, (drv as u8) << 1])
    }

    /// Set the node address.
    ///
    /// Used with [`GenericPacketParams::set_addr_comp`] to filter packets based
    /// on node address.
    pub fn set_node_addr(&mut self, addr: u8) -> Result<(), Error> {
        self.write(wr_reg![NODE, addr])
    }

    /// Set the broadcast address.
    ///
    /// Used with [`GenericPacketParams::set_addr_comp`] to filter packets based
    /// on broadcast address.
    pub fn set_broadcast_addr(&mut self, addr: u8) -> Result<(), Error> {
        self.write(wr_reg![BROADCAST, addr])
    }

    /// Set both the broadcast address and node address.
    ///
    /// This is a combination of [`set_node_addr`] and [`set_broadcast_addr`]
    /// in a single SPI transfer.
    ///
    /// [`set_node_addr`]: Self::set_node_addr
    /// [`set_broadcast_addr`]: Self::set_broadcast_addr
    pub fn set_addrs(&mut self, node: u8, broadcast: u8) -> Result<(), Error> {
        self.write(wr_reg![NODE, node, broadcast])
    }
}

// 5.8.3
/// Operating mode commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
    /// Put the radio into sleep mode.
    ///
    /// This command is only accepted in standby mode.
    /// The cfg argument allows some optional functions to be maintained
    /// in sleep mode.
    ///
    /// # Safety
    ///
    /// 1. After the `set_sleep` command, the sub-GHz radio NSS must not go low
    ///    for 500 μs.
    ///    No reason is provided, the reference manual (RM0453 rev 2) simply
    ///    says "you must".
    /// 2. The radio cannot be used while in sleep mode.
    /// 3. The radio must be woken up with [`wakeup`] before resuming use.
    ///
    /// # Example
    ///
    /// Put the radio into sleep mode.
    ///
    /// ```no_run
    /// # let dp = unsafe { embassy_stm32::pac::Peripherals::steal() };
    /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
    /// use embassy_stm32::{
    ///     subghz::{wakeup, SleepCfg, StandbyClk},
    /// };
    ///
    /// sg.set_standby(StandbyClk::Rc)?;
    /// unsafe { sg.set_sleep(SleepCfg::default())? };
    /// embassy_time::Timer::after(embassy_time::Duration::from_micros(500)).await;
    /// unsafe { wakeup() };
    /// # Ok::<(), embassy_stm32::subghz::Error>(())
    /// ```
    pub unsafe fn set_sleep(&mut self, cfg: SleepCfg) -> Result<(), Error> {
        // poll for busy before, but not after
        // radio idles with busy high while in sleep mode
        self.poll_not_busy();
        {
            let _nss: Nss = Nss::new();
            self.spi.blocking_write(&[OpCode::SetSleep as u8, u8::from(cfg)])?;
        }
        Ok(())
    }

    /// Put the radio into standby mode.
    pub fn set_standby(&mut self, standby_clk: StandbyClk) -> Result<(), Error> {
        self.write(&[OpCode::SetStandby as u8, u8::from(standby_clk)])
    }

    /// Put the subghz radio into frequency synthesis mode.
    ///
    /// The RF-PLL frequency must be set with [`set_rf_frequency`] before using
    /// this command.
    ///
    /// Check the datasheet for more information, this is a test command but
    /// I honestly do not see any use for it.  Please update this description
    /// if you know more than I do.
    ///
    /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency
    pub fn set_fs(&mut self) -> Result<(), Error> {
        self.write(&[OpCode::SetFs.into()])
    }

    /// Setup the sub-GHz radio for TX.
    pub fn set_tx(&mut self, timeout: Timeout) -> Result<(), Error> {
        let tobits: u32 = timeout.into_bits();
        self.write(&[
            OpCode::SetTx.into(),
            (tobits >> 16) as u8,
            (tobits >> 8) as u8,
            tobits as u8,
        ])
    }

    /// Setup the sub-GHz radio for RX.
    pub fn set_rx(&mut self, timeout: Timeout) -> Result<(), Error> {
        let tobits: u32 = timeout.into_bits();
        self.write(&[
            OpCode::SetRx.into(),
            (tobits >> 16) as u8,
            (tobits >> 8) as u8,
            tobits as u8,
        ])
    }

    /// Allows selection of the receiver event which stops the RX timeout timer.
    pub fn set_rx_timeout_stop(&mut self, rx_timeout_stop: RxTimeoutStop) -> Result<(), Error> {
        self.write(&[OpCode::SetStopRxTimerOnPreamble.into(), rx_timeout_stop.into()])
    }

    /// Put the radio in non-continuous RX mode.
    ///
    /// This command must be sent in Standby mode.
    /// This command is only functional with FSK and LoRa packet type.
    ///
    /// The following steps are performed:
    /// 1. Save sub-GHz radio configuration.
    /// 2. Enter Receive mode and listen for a preamble for the specified `rx_period`.
    /// 3. Upon the detection of a preamble, the `rx_period` timeout is stopped
    ///    and restarted with the value 2 x `rx_period` + `sleep_period`.
    ///    During this new period, the sub-GHz radio looks for the detection of
    ///    a synchronization word when in (G)FSK modulation mode,
    ///    or a header when in LoRa modulation mode.
    /// 4. If no packet is received during the listen period defined by
    ///    2 x `rx_period` + `sleep_period`, the sleep mode is entered for a
    ///    duration of `sleep_period`. At the end of the receive period,
    ///    the sub-GHz radio takes some time to save the context before starting
    ///    the sleep period.
    /// 5. After the sleep period, a new listening period is automatically
    ///    started. The sub-GHz radio restores the sub-GHz radio configuration
    ///    and continuous with step 2.
    ///
    /// The listening mode is terminated in one of the following cases:
    /// * if a packet is received during the listening period: the sub-GHz radio
    ///   issues a [`RxDone`] interrupt and enters standby mode.
    /// * if [`set_standby`] is sent during the listening period or after the
    ///   sub-GHz has been requested to exit sleep mode by sub-GHz radio SPI NSS
    ///
    /// # Erratum
    ///
    /// When a preamble is detected the radio should restart the RX timeout
    /// with a value of 2 × `rx_period` + `sleep_period`.
    /// Instead the radio erroneously uses `sleep_period`.
    ///
    /// To workaround this use [`restart_rtc`] and [`set_rtc_period`] to
    /// reprogram the radio timeout to  2 × `rx_period` + `sleep_period`.
    ///
    /// Use code similar to this in the [`PreambleDetected`] interrupt handler.
    ///
    /// ```no_run
    /// # let rx_period: Timeout = Timeout::from_millis_sat(100);
    /// # let sleep_period: Timeout = Timeout::from_millis_sat(100);
    /// # let mut sg = unsafe { stm32wlxx_hal::subghz::SubGhz::steal() };
    /// use stm32wlxx_hal::subghz::Timeout;
    ///
    /// let period: Timeout = rx_period
    ///     .saturating_add(rx_period)
    ///     .saturating_add(sleep_period);
    ///
    /// sg.set_rtc_period(period)?;
    /// sg.restart_rtc()?;
    /// # Ok::<(), stm32wlxx_hal::subghz::Error>(())
    /// ```
    ///
    /// Please read the erratum for more details.
    ///
    /// [`PreambleDetected`]: crate::subghz::Irq::PreambleDetected
    /// [`restart_rtc`]: crate::subghz::SubGhz::restart_rtc
    /// [`RxDone`]: crate::subghz::Irq::RxDone
    /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency
    /// [`set_rtc_period`]: crate::subghz::SubGhz::set_rtc_period
    /// [`set_standby`]: crate::subghz::SubGhz::set_standby
    pub fn set_rx_duty_cycle(&mut self, rx_period: Timeout, sleep_period: Timeout) -> Result<(), Error> {
        let rx_period_bits: u32 = rx_period.into_bits();
        let sleep_period_bits: u32 = sleep_period.into_bits();
        self.write(&[
            OpCode::SetRxDutyCycle.into(),
            (rx_period_bits >> 16) as u8,
            (rx_period_bits >> 8) as u8,
            rx_period_bits as u8,
            (sleep_period_bits >> 16) as u8,
            (sleep_period_bits >> 8) as u8,
            sleep_period_bits as u8,
        ])
    }

    /// Channel Activity Detection (CAD) with LoRa packets.
    ///
    /// The channel activity detection (CAD) is a specific LoRa operation mode,
    /// where the sub-GHz radio searches for a LoRa radio signal.
    /// After the search is completed, the Standby mode is automatically
    /// entered, CAD is done and IRQ is generated.
    /// When a LoRa radio signal is detected, the CAD detected IRQ is also
    /// generated.
    ///
    /// The length of the search must be configured with [`set_cad_params`]
    /// prior to calling `set_cad`.
    ///
    /// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params
    pub fn set_cad(&mut self) -> Result<(), Error> {
        self.write(&[OpCode::SetCad.into()])
    }

    /// Generate a continuous transmit tone at the RF-PLL frequency.
    ///
    /// The sub-GHz radio remains in continuous transmit tone mode until a mode
    /// configuration command is received.
    pub fn set_tx_continuous_wave(&mut self) -> Result<(), Error> {
        self.write(&[OpCode::SetTxContinuousWave as u8])
    }

    /// Generate an infinite preamble at the RF-PLL frequency.
    ///
    /// The preamble is an alternating 0s and 1s sequence in generic (G)FSK and
    /// (G)MSK modulations.
    /// The preamble is symbol 0 in LoRa modulation.
    /// The sub-GHz radio remains in infinite preamble mode until a mode
    /// configuration command is received.
    pub fn set_tx_continuous_preamble(&mut self) -> Result<(), Error> {
        self.write(&[OpCode::SetTxContinuousPreamble as u8])
    }
}

// 5.8.4
/// Radio configuration commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
    /// Set the packet type (modulation scheme).
    pub fn set_packet_type(&mut self, packet_type: PacketType) -> Result<(), Error> {
        self.write(&[OpCode::SetPacketType as u8, packet_type as u8])
    }

    /// Get the packet type.
    pub fn packet_type(&mut self) -> Result<Result<PacketType, u8>, Error> {
        let pkt_type: [u8; 2] = self.read_n(OpCode::GetPacketType)?;
        Ok(PacketType::from_raw(pkt_type[1]))
    }

    /// Set the radio carrier frequency.
    pub fn set_rf_frequency(&mut self, freq: &RfFreq) -> Result<(), Error> {
        self.write(freq.as_slice())
    }

    /// Set the transmit output power and the PA ramp-up time.
    pub fn set_tx_params(&mut self, params: &TxParams) -> Result<(), Error> {
        self.write(params.as_slice())
    }

    /// Power amplifier configuration.
    ///
    /// Used to customize the maximum output power and efficiency.
    pub fn set_pa_config(&mut self, pa_config: &PaConfig) -> Result<(), Error> {
        self.write(pa_config.as_slice())
    }

    /// Operating mode to enter after a successful packet transmission or
    /// packet reception.
    pub fn set_tx_rx_fallback_mode(&mut self, fm: FallbackMode) -> Result<(), Error> {
        self.write(&[OpCode::SetTxRxFallbackMode as u8, fm as u8])
    }

    /// Set channel activity detection (CAD) parameters.
    pub fn set_cad_params(&mut self, params: &CadParams) -> Result<(), Error> {
        self.write(params.as_slice())
    }

    /// Set the data buffer base address for the packet handling in TX and RX.
    ///
    /// There is a single buffer for both TX and RX.
    /// The buffer is not memory mapped, it is accessed via the
    /// [`read_buffer`](SubGhz::read_buffer) and
    /// [`write_buffer`](SubGhz::write_buffer) methods.
    pub fn set_buffer_base_address(&mut self, tx: u8, rx: u8) -> Result<(), Error> {
        self.write(&[OpCode::SetBufferBaseAddress as u8, tx, rx])
    }

    /// Set the (G)FSK modulation parameters.
    pub fn set_fsk_mod_params(&mut self, params: &FskModParams) -> Result<(), Error> {
        self.write(params.as_slice())
    }

    /// Set the LoRa modulation parameters.
    pub fn set_lora_mod_params(&mut self, params: &LoRaModParams) -> Result<(), Error> {
        self.write(params.as_slice())
    }

    /// Set the BPSK modulation parameters.
    pub fn set_bpsk_mod_params(&mut self, params: &BpskModParams) -> Result<(), Error> {
        self.write(params.as_slice())
    }

    /// Set the generic (FSK) packet parameters.
    pub fn set_packet_params(&mut self, params: &GenericPacketParams) -> Result<(), Error> {
        self.write(params.as_slice())
    }

    /// Set the BPSK packet parameters.
    pub fn set_bpsk_packet_params(&mut self, params: &BpskPacketParams) -> Result<(), Error> {
        self.write(params.as_slice())
    }

    /// Set the LoRa packet parameters.
    pub fn set_lora_packet_params(&mut self, params: &LoRaPacketParams) -> Result<(), Error> {
        self.write(params.as_slice())
    }

    /// Set the number of LoRa symbols to be received before starting the
    /// reception of a LoRa packet.
    ///
    /// Packet reception is started after `n` + 1 symbols are detected.
    pub fn set_lora_symb_timeout(&mut self, n: u8) -> Result<(), Error> {
        self.write(&[OpCode::SetLoRaSymbTimeout.into(), n])
    }
}

// 5.8.5
/// Communication status and information commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
    /// Get the radio status.
    ///
    /// The hardware (or documentation) appears to have many bugs where this
    /// will return reserved values.
    /// See this thread in the ST community for details: [link]
    ///
    /// [link]: https://community.st.com/s/question/0D53W00000hR9GQSA0/stm32wl55-getstatus-command-returns-reserved-cmdstatus
    pub fn status(&mut self) -> Result<Status, Error> {
        Ok(self.read_1(OpCode::GetStatus)?.into())
    }

    /// Get the RX buffer status.
    ///
    /// The return tuple is (status, payload_length, buffer_pointer).
    pub fn rx_buffer_status(&mut self) -> Result<(Status, u8, u8), Error> {
        let data: [u8; 3] = self.read_n(OpCode::GetRxBufferStatus)?;
        Ok((data[0].into(), data[1], data[2]))
    }

    /// Returns information on the last received (G)FSK packet.
    pub fn fsk_packet_status(&mut self) -> Result<FskPacketStatus, Error> {
        Ok(FskPacketStatus::from(self.read_n(OpCode::GetPacketStatus)?))
    }

    /// Returns information on the last received LoRa packet.
    pub fn lora_packet_status(&mut self) -> Result<LoRaPacketStatus, Error> {
        Ok(LoRaPacketStatus::from(self.read_n(OpCode::GetPacketStatus)?))
    }

    /// Get the instantaneous signal strength during packet reception.
    ///
    /// The units are in dbm.
    pub fn rssi_inst(&mut self) -> Result<(Status, Ratio<i16>), Error> {
        let data: [u8; 2] = self.read_n(OpCode::GetRssiInst)?;
        let status: Status = data[0].into();
        let rssi: Ratio<i16> = Ratio::new_raw(i16::from(data[1]), -2);

        Ok((status, rssi))
    }

    /// (G)FSK packet stats.
    pub fn fsk_stats(&mut self) -> Result<Stats<FskStats>, Error> {
        let data: [u8; 7] = self.read_n(OpCode::GetStats)?;
        Ok(Stats::from_raw_fsk(data))
    }

    /// LoRa packet stats.
    pub fn lora_stats(&mut self) -> Result<Stats<LoRaStats>, Error> {
        let data: [u8; 7] = self.read_n(OpCode::GetStats)?;
        Ok(Stats::from_raw_lora(data))
    }

    /// Reset the stats as reported in [`lora_stats`](SubGhz::lora_stats) and
    /// [`fsk_stats`](SubGhz::fsk_stats).
    pub fn reset_stats(&mut self) -> Result<(), Error> {
        const RESET_STATS: [u8; 7] = [0x00; 7];
        self.write(&RESET_STATS)
    }
}

// 5.8.6
/// IRQ commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
    /// Set the interrupt configuration.
    pub fn set_irq_cfg(&mut self, cfg: &CfgIrq) -> Result<(), Error> {
        self.write(cfg.as_slice())
    }

    /// Get the IRQ status.
    pub fn irq_status(&mut self) -> Result<(Status, u16), Error> {
        let data: [u8; 3] = self.read_n(OpCode::GetIrqStatus)?;
        let irq_status: u16 = u16::from_be_bytes([data[1], data[2]]);
        Ok((data[0].into(), irq_status))
    }

    /// Clear the IRQ status.
    pub fn clear_irq_status(&mut self, mask: u16) -> Result<(), Error> {
        self.write(&[OpCode::ClrIrqStatus as u8, (mask >> 8) as u8, mask as u8])
    }
}

// 5.8.7
/// Miscellaneous commands
impl<'d> SubGhz<'d, NoDma, NoDma> {
    /// Calibrate one or several blocks at any time when in standby mode.
    pub fn calibrate(&mut self, cal: u8) -> Result<(), Error> {
        // bit 7 is reserved and must be kept at reset value.
        self.write(&[OpCode::Calibrate as u8, cal & 0x7F])
    }

    /// Calibrate the image at the given frequencies.
    ///
    /// Requires the radio to be in standby mode.
    pub fn calibrate_image(&mut self, cal: CalibrateImage) -> Result<(), Error> {
        self.write(&[OpCode::CalibrateImage as u8, cal.0, cal.1])
    }

    /// Set the radio power supply.
    pub fn set_regulator_mode(&mut self, reg_mode: RegMode) -> Result<(), Error> {
        self.write(&[OpCode::SetRegulatorMode as u8, reg_mode as u8])
    }

    /// Get the radio operational errors.
    pub fn op_error(&mut self) -> Result<(Status, u16), Error> {
        let data: [u8; 3] = self.read_n(OpCode::GetError)?;
        Ok((data[0].into(), u16::from_be_bytes([data[1], data[2]])))
    }

    /// Clear all errors as reported by [`op_error`](SubGhz::op_error).
    pub fn clear_error(&mut self) -> Result<(), Error> {
        self.write(&[OpCode::ClrError as u8, 0x00])
    }
}

// 5.8.8
/// Set TCXO mode command
impl<'d> SubGhz<'d, NoDma, NoDma> {
    /// Set the TCXO trim and HSE32 ready timeout.
    pub fn set_tcxo_mode(&mut self, tcxo_mode: &TcxoMode) -> Result<(), Error> {
        self.write(tcxo_mode.as_slice())
    }
}

/// sub-GHz radio opcodes.
///
/// See Table 41 "Sub-GHz radio SPI commands overview"
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub(crate) enum OpCode {
    Calibrate = 0x89,
    CalibrateImage = 0x98,
    CfgDioIrq = 0x08,
    ClrError = 0x07,
    ClrIrqStatus = 0x02,
    GetError = 0x17,
    GetIrqStatus = 0x12,
    GetPacketStatus = 0x14,
    GetPacketType = 0x11,
    GetRssiInst = 0x15,
    GetRxBufferStatus = 0x13,
    GetStats = 0x10,
    GetStatus = 0xC0,
    ReadBuffer = 0x1E,
    RegRegister = 0x1D,
    ResetStats = 0x00,
    SetBufferBaseAddress = 0x8F,
    SetCad = 0xC5,
    SetCadParams = 0x88,
    SetFs = 0xC1,
    SetLoRaSymbTimeout = 0xA0,
    SetModulationParams = 0x8B,
    SetPacketParams = 0x8C,
    SetPacketType = 0x8A,
    SetPaConfig = 0x95,
    SetRegulatorMode = 0x96,
    SetRfFrequency = 0x86,
    SetRx = 0x82,
    SetRxDutyCycle = 0x94,
    SetSleep = 0x84,
    SetStandby = 0x80,
    SetStopRxTimerOnPreamble = 0x9F,
    SetTcxoMode = 0x97,
    SetTx = 0x83,
    SetTxContinuousPreamble = 0xD2,
    SetTxContinuousWave = 0xD1,
    SetTxParams = 0x8E,
    SetTxRxFallbackMode = 0x93,
    WriteBuffer = 0x0E,
    WriteRegister = 0x0D,
}

impl From<OpCode> for u8 {
    fn from(opcode: OpCode) -> Self {
        opcode as u8
    }
}

#[repr(u16)]
#[allow(clippy::upper_case_acronyms)]
pub(crate) enum Register {
    /// Generic bit synchronization.
    GBSYNC = 0x06AC,
    /// Generic packet control.
    GPKTCTL1A = 0x06B8,
    /// Generic whitening.
    GWHITEINIRL = 0x06B9,
    /// Generic CRC initial.
    GCRCINIRH = 0x06BC,
    /// Generic CRC polynomial.
    GCRCPOLRH = 0x06BE,
    /// Generic synchronization word 7.
    GSYNC7 = 0x06C0,
    /// Node address.
    NODE = 0x06CD,
    /// Broadcast address.
    BROADCAST = 0x06CE,
    /// LoRa synchronization word MSB.
    LSYNCH = 0x0740,
    /// LoRa synchronization word LSB.
    #[allow(dead_code)]
    LSYNCL = 0x0741,
    /// Receiver gain control.
    RXGAINC = 0x08AC,
    /// PA over current protection.
    PAOCP = 0x08E7,
    /// RTC control.
    RTCCTLR = 0x0902,
    /// RTC period MSB.
    RTCPRDR2 = 0x0906,
    /// RTC period mid-byte.
    #[allow(dead_code)]
    RTCPRDR1 = 0x0907,
    /// RTC period LSB.
    #[allow(dead_code)]
    RTCPRDR0 = 0x0908,
    /// HSE32 OSC_IN capacitor trim.
    HSEINTRIM = 0x0911,
    /// HSE32 OSC_OUT capacitor trim.
    HSEOUTTRIM = 0x0912,
    /// SMPS control 0.
    SMPSC0 = 0x0916,
    /// Power control.
    PC = 0x091A,
    /// SMPS control 2.
    SMPSC2 = 0x0923,
}

impl Register {
    pub const fn address(self) -> u16 {
        self as u16
    }
}
