#![no_main]
#![no_std]

extern crate cortex_m;
extern crate cortex_m_rt as rt;
extern crate panic_semihosting;

extern crate stm32f4xx_hal as hal;

mod packet;

#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true)]
mod app {
    use hal::block;
    use hal::gpio;
    use hal::pac::{SPI1, TIM5, USART1};
    use hal::prelude::*;
    use hal::serial::{Config, Event, Serial};
    use hal::spi::{Mode, Phase, Polarity, Spi};
    use hal::timer::Delay;
    use rf4463::config::RADIO_CONFIG_500_2;
    use rf4463::Rf4463;
    use ringbuffer::{ConstGenericRingBuffer, RingBuffer, RingBufferRead, RingBufferWrite};

    use crate::packet::Packet;

    // in # packets
    const BUFFER_LEN: usize = 50;

    const MODE: Mode = Mode {
        polarity: Polarity::IdleLow,
        phase: Phase::CaptureOnFirstTransition,
    };

    type Radio = Rf4463<
        Spi<SPI1>,
        gpio::Pin<'B', 1, gpio::Output>,
        gpio::Pin<'B', 0, gpio::Output>,
        Delay<TIM5, 1000000>,
    >;

    #[derive(Debug)]
    enum SlaveCmd {
        BufferStatus,
        SendPacket,
        GetTemp,
    }

    impl SlaveCmd {
        pub fn from_u8(i: u8) -> Option<Self> {
            match i {
                0x00 => Some(Self::BufferStatus),
                0x01 => Some(Self::SendPacket),
                0x02 => Some(Self::GetTemp),
                _ => None,
            }
        }
    }

    #[allow(clippy::large_enum_variant)]
    pub enum SlaveState {
        Idle,
        RecvPacket(Packet),
    }

    #[shared]
    struct Shared {
        radio_temp: f32,
        tx_buf: ConstGenericRingBuffer<Packet, BUFFER_LEN>,
    }

    #[local]
    struct Local {
        radio: Radio,
        usart: Serial<USART1>,
        state: SlaveState,
    }

    #[init]
    fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) {
        let rcc = ctx.device.RCC.constrain();

        let gpioa = ctx.device.GPIOA.split();
        let gpiob = ctx.device.GPIOB.split();

        // setup clocks
        let clocks = rcc
            .cfgr
            .use_hse(25.MHz())
            .sysclk(100.MHz())
            .pclk1(48.MHz())
            .pclk2(48.MHz())
            .freeze();

        // setup 4463 spi
        let mosi = gpioa.pa7.into_alternate();
        let miso = gpioa.pa6.into_alternate();
        let sclk = gpioa.pa5.into_alternate();

        let sdn = gpiob.pb1.into_push_pull_output();

        let cs = gpiob.pb0.into_push_pull_output();

        let spi = Spi::new(ctx.device.SPI1, (sclk, miso, mosi), MODE, 10.MHz(), &clocks);

        let delay = ctx.device.TIM5.delay_us(&clocks);
        let radio = Rf4463::new(spi, sdn, cs, delay, &mut RADIO_CONFIG_500_2.clone()).unwrap();

        // setup Pi UART
        let tx = gpioa.pa9;
        let rx = gpioa.pa10;

        let mut usart = Serial::new(
            ctx.device.USART1,
            (tx, rx),
            Config::default().baudrate(921_600.bps()),
            &clocks,
        )
        .unwrap();

        usart.listen(Event::Rxne);

        (
            Shared {
                radio_temp: 0.0,
                tx_buf: ConstGenericRingBuffer::new(),
            },
            Local {
                radio,
                usart,
                state: SlaveState::Idle,
            },
            init::Monotonics(),
        )
    }

    #[idle(local=[radio], shared=[tx_buf, radio_temp])]
    fn idle(mut ctx: idle::Context) -> ! {
        let mut i = 0;
        loop {
            if let Some(mut pkt) = ctx.shared.tx_buf.lock(|buf| buf.dequeue()) {
                ctx.local.radio.tx(&mut pkt.data).unwrap();
            }

            i += 1;

            // get temp every 500 packets, or whenever when idle TODO
            if i >= 500 {
                i = 0;

                if let Ok(new_temp) = ctx.local.radio.get_temp() {
                    ctx.shared.radio_temp.lock(|cur_temp| *cur_temp = new_temp);
                }
            }
        }
    }

    #[task(binds = USART1, priority=1, local = [state, usart], shared = [tx_buf, radio_temp])]
    fn usart1(mut ctx: usart1::Context) {
        let state = ctx.local.state;
        let usart = ctx.local.usart;
        let mut buf = ctx.shared.tx_buf;

        let x = block!(usart.read()).unwrap();

        match state {
            SlaveState::Idle => {
                if let Some(cmd) = SlaveCmd::from_u8(x) {
                    match cmd {
                        SlaveCmd::BufferStatus => {
                            if buf.lock(|b| b.is_full()) {
                                block!(usart.write(0x01)).unwrap(); // not enough space for a new packet
                            } else {
                                block!(usart.write(0x02)).unwrap(); // safe to send a packet
                            }
                        }

                        SlaveCmd::SendPacket => {
                            if buf.lock(|b| b.len() >= b.capacity() - 1) {
                                block!(usart.write(0x01)).unwrap(); // not enough space for a new packet
                            } else {
                                block!(usart.write(0x02)).unwrap(); // safe to send a packet
                            }

                            *state = SlaveState::RecvPacket(Packet::default());
                        }

                        SlaveCmd::GetTemp => {
                            let temp = ctx.shared.radio_temp.lock(|x| *x);
                            usart.bwrite_all(&temp.to_le_bytes()).unwrap();
                        }
                    }
                }
            }

            SlaveState::RecvPacket(pkt) => {
                pkt.push(x);
                if pkt.is_complete() {
                    buf.lock(|b| b.enqueue(pkt.clone()));

                    *state = SlaveState::Idle;
                }
            }
        };
    }
}