#![no_std]

pub mod config;
mod consts;
pub mod error;
mod internal_radio;
mod internal_state;
pub mod rx;
pub(crate) mod state;

use core::{convert::Infallible, fmt::Debug};
use embedded_hal::{
    blocking::{delay::DelayUs, spi::Transfer},
    digital::v2::OutputPin,
};

use consts::*;
use error::{RfError, SpiErrorToOtherError, TransferError, TxError};
use internal_state::InternalState;
use rx::RxPacket;
use state::State;

use crate::internal_radio::InternalRadio;

const VCXO_FREQ: u32 = 30_000_000;

/// All the rx_buf methods MUST be passed the same rx_buf
/// All the tx_buf methods MUST be passed the same tx_buf
/// We do this because we don't keep that data locally for space reasons
/// And we don't keep references to it for lifetime reasons!
pub struct Rf4463<SdnPin, CsPin, Delay> {
    radio: InternalRadio<SdnPin, CsPin, Delay>,
    state: InternalState,
    rx_forever: bool,
    channel: u8,
}

impl<
        SdnPin: OutputPin<Error = Infallible>,
        CsPin: OutputPin<Error = Infallible>,
        Delay: DelayUs<u16>,
    > Rf4463<SdnPin, CsPin, Delay>
{
    pub fn new<Spi: Transfer<u8>>(
        spi: &mut Spi,
        sdn: SdnPin,
        mut cs: CsPin,
        delay: Delay,
        config: &mut [u8],
    ) -> Result<Self, RfError<Spi::Error>>
    where
        Spi::Error: Debug,
    {
        cs.set_high().unwrap();

        Ok(Self {
            radio: InternalRadio::new(spi, sdn, cs, delay, config)?,
            state: InternalState::Idle,
            rx_forever: false,
            channel: 0,
        })
    }

    /// Do not use this in the middle of an RX event
    /// Otherwise weird things will happen! I think this is a limitation
    /// of the si4463, but it's not documented anywhere.
    /// TX may or may not have a similar issue - I haven't tested it
    pub fn get_temp<Spi: Transfer<u8>>(&mut self, spi: &mut Spi) -> Result<f32, Spi::Error> {
        self.radio.get_temp(spi)
    }

    pub fn get_rssi<Spi: Transfer<u8>>(&mut self, spi: &mut Spi) -> Result<f64, Spi::Error> {
        self.radio.get_rssi(spi)
    }

    pub fn sleep<Spi: Transfer<u8>>(&mut self, spi: &mut Spi) -> Result<(), RfError<Spi::Error>>
    where
        Spi::Error: Debug,
    {
        self.state = InternalState::Idle;
        self.radio.sleep(spi)
    }

    pub fn set_channel(&mut self, channel: u8) {
        self.channel = channel;
    }

    /// Frequency given in Hz
    pub fn set_frequency<Spi: Transfer<u8>>(
        &mut self,
        spi: &mut Spi,
        freq: u32,
    ) -> Result<(), Spi::Error> {
        let outdiv;
        let band;

        match freq {
            x if x < 177_000_000 => {
                outdiv = 24;
                band = 5;
            }
            x if x < 239_000_000 => {
                outdiv = 16;
                band = 4;
            }
            x if x < 353_000_000 => {
                outdiv = 12;
                band = 3;
            }
            x if x < 525_000_000 => {
                outdiv = 8;
                band = 2;
            }
            x if x < 705_000_000 => {
                outdiv = 6;
                band = 1;
            }
            _ => {
                outdiv = 4;
                band = 0;
            }
        }

        let f_pfd = 2 * VCXO_FREQ / outdiv;
        let n: u8 = (freq / f_pfd - 1).try_into().unwrap();
        let ratio = freq as f32 / f_pfd as f32;
        let rest = ratio - n as f32;
        let m = (rest * 524_288.0) as u32;

        // set band parameter
        self.radio
            .send_command::<_, 0>(spi, &mut [SET_PROPERTY, 0x20, 0x01, 0x51, 8 + band])?;

        // set pll parameters
        self.radio.send_command::<_, 0>(
            spi,
            &mut [
                SET_PROPERTY,
                0x40,
                0x04,
                0x00,
                n,
                ((m >> 16) & 0xFF).try_into().unwrap(),
                ((m >> 8) & 0xFF).try_into().unwrap(),
                (m & 0xFF).try_into().unwrap(),
            ],
        )?;

        Ok(())
    }

    pub fn is_idle(&mut self) -> bool {
        matches!(self.state, InternalState::Idle)
    }

    // in other words, we're actively receiving a packet (not just waiting for one)
    pub fn is_busy_rxing<Spi: Transfer<u8>>(&mut self, spi: &mut Spi) -> Result<bool, Spi::Error> {
        if let InternalState::Rx { i, .. } = self.state {
            return Ok(i > 0 || self.radio.rx_fifo_len(spi)? > 0);
        }

        Ok(false)
    }

    // Len is none when using a variable-length packet
    pub fn start_rx<Spi: Transfer<u8>>(
        &mut self,
        spi: &mut Spi,
        len: Option<usize>,
        rx_forever: bool,
    ) -> Result<(), Spi::Error> {
        self.radio.clear_fifo(spi)?;
        self.radio.clear_ph_and_modem_interrupts(spi)?;

        let len = len.unwrap_or(0);

        self.state = InternalState::Rx {
            i: 0,
            received: false,
            rssi: None,
        };

        self.radio.send_command::<_, 0>(
            spi,
            &mut [
                START_RX,
                self.channel,
                0,
                (len >> 8).try_into().unwrap(),
                (len & 0xff).try_into().unwrap(),
                State::Rx.into(),
                if rx_forever {
                    State::Rx.into()
                } else {
                    State::Sleep.into()
                },
                State::Rx.into(),
            ],
        )?;

        self.rx_forever = rx_forever;

        Ok(())
    }

    pub fn finish_rx<'a, Spi: Transfer<u8>>(
        &mut self,
        spi: &mut Spi,
        rx_buf: &'a mut [u8],
    ) -> Result<Option<RxPacket<'a>>, Spi::Error> {
        let pkt = match self.state {
            InternalState::Rx { received, i, rssi } if received => {
                let rssi = match rssi {
                    Some(x) => x,
                    None => self.get_rssi(spi)?,
                };

                let ret = Some(RxPacket::new(rx_buf, i, rssi));

                if self.rx_forever {
                    self.radio.clear_ph_and_modem_interrupts(spi)?;

                    self.state = InternalState::Rx {
                        i: 0,
                        received: false,
                        rssi: None,
                    };
                } else {
                    self.state = InternalState::Idle;
                }

                ret
            }

            _ => None,
        };

        Ok(pkt)
    }

    /// Panics if data length is > 8191
    pub fn start_tx<Spi: Transfer<u8>>(
        &mut self,
        spi: &mut Spi,
        tx_buf: &[u8],
    ) -> Result<(), TxError<Spi::Error>>
    where
        Spi::Error: Debug,
    {
        let len = tx_buf.len();
        assert!(len < 8192, "Packet length cannot be above 8191 bytes");

        if matches!(self.state, InternalState::Rx { .. }) {
            // if we're in the middle of rxing, we need to cancel that first
            // otherwise it gets confused
            self.radio.set_state(spi, State::Sleep).txe()?;
        }

        self.radio.clear_fifo(spi).txe()?;
        self.radio.clear_ph_and_modem_interrupts(spi).txe()?;

        // 128-byte TX buffer
        let i = tx_buf.len().min(128);
        let mut segment = [0; 128];
        segment[..i].copy_from_slice(&tx_buf[..i]);

        self.radio.with_cs(|s| {
            s.spi_transfer_byte(spi, WRITE_TX_FIFO).txe()?;
            spi.transfer(&mut segment[..i]).txe()?;

            Ok(())
        })?;

        self.radio
            .send_command::<_, 0>(
                spi,
                &mut [
                    START_TX,
                    self.channel,
                    u8::from(State::Sleep) << 4,
                    (len >> 8).try_into().unwrap(),
                    (len & 0xff).try_into().unwrap(),
                    0,
                    0,
                ],
            )
            .txe()?;

        self.state = InternalState::Tx { i, len };

        Ok(())
    }

    /// You only *need* to pass in rx_buf or tx_buf depending on what we're doing,
    /// But you're free to pass in both without any issues
    pub fn interrupt<Spi: Transfer<u8>>(
        &mut self,
        spi: &mut Spi,
        rx_buf: Option<&mut [u8]>,
        tx_buf: Option<&[u8]>,
    ) -> Result<(), TransferError<Spi::Error>>
    where
        Spi::Error: Debug,
    {
        if self.radio.fifo_underflow_pending(spi).te()? {
            return Err(TransferError::FifoOverflow);
        }

        match &mut self.state {
            InternalState::Idle => {
                // we were not expecting an interrupt
                // ignore
            }

            InternalState::Rx { received, .. } => {
                if self.radio.packet_received_pending(spi).te()? {
                    *received = true;
                }

                if let Err(e) =
                    self.rx_step(spi, rx_buf.expect("We're receiving but there's no rx buf"))
                {
                    // something went wrong. Go back to idle
                    self.state = InternalState::Idle;
                    let _ = self.radio.sleep(spi);

                    return Err(e);
                }
            }

            InternalState::Tx { .. } => {
                if self.radio.packet_sent_pending(spi).te()? {
                    self.state = InternalState::Idle;
                }

                if let Err(e) = self.tx_step(
                    spi,
                    tx_buf.expect("We're transmitting but there's no tx buf"),
                ) {
                    // something went wrong. Go back to idle
                    self.state = InternalState::Idle;
                    let _ = self.radio.sleep(spi);

                    return Err(e);
                }
            }
        }

        Ok(())
    }

    fn rx_step<Spi: Transfer<u8>>(
        &mut self,
        spi: &mut Spi,
        rx_buf: &mut [u8],
    ) -> Result<(), TransferError<Spi::Error>>
    where
        Spi::Error: Debug,
    {
        let (i, rssi) = match &mut self.state {
            InternalState::Rx { i, rssi, .. } => (i, rssi),
            _ => return Ok(()),
        };

        let fifo_len: usize = self.radio.rx_fifo_len(spi).te()?.into();
        if fifo_len == 0 {
            return Ok(());
        }

        if fifo_len + *i > rx_buf.len() {
            return Err(TransferError::TooMuchData);
        }

        if self.radio.fifo_underflow_pending(spi).te()? {
            return Err(TransferError::FifoOverflow);
        }

        let data = &mut rx_buf[*i..(*i + fifo_len)];
        self.radio
            .with_cs(|s| {
                s.spi_transfer_byte(spi, READ_RX_FIFO)?;
                spi.transfer(data)?;

                Ok(())
            })
            .te()?;

        if rssi.is_none() {
            *rssi = Some(self.radio.get_rssi(spi).te()?)
        }

        *i += fifo_len;

        Ok(())
    }

    fn tx_step<Spi: Transfer<u8>>(
        &mut self,
        spi: &mut Spi,
        tx_buf: &[u8],
    ) -> Result<(), TransferError<Spi::Error>>
    where
        Spi::Error: Debug,
    {
        let (data_len, i) = match &mut self.state {
            InternalState::Tx { i, len } => (*len, i),
            _ => return Ok(()),
        };

        // only look at the slice we haven't sent yet
        let data = &tx_buf[*i..data_len];

        let len: usize = self.radio.tx_fifo_space(spi).te()?.into();
        let len = len.min(data.len());
        if len == 0 {
            return Ok(());
        }

        // we need a mutable reference on data for our transfer function
        // so we copy that data into a temporary buffer
        const FIFO_BUFFER_LEN: usize = 129;
        let mut buf = [0; FIFO_BUFFER_LEN];
        buf[..len].copy_from_slice(&data[..len]);

        if self.radio.fifo_underflow_pending(spi).te()? {
            return Err(TransferError::FifoOverflow);
        }

        self.radio.with_cs(|s| {
            s.spi_transfer_byte(spi, WRITE_TX_FIFO).te()?;
            spi.transfer(&mut buf[0..len]).te()?;

            Ok(())
        })?;

        *i += len;

        Ok(())
    }
}