use core::convert::TryInto;

use embedded_hal::blocking::delay::DelayMs;
use embedded_hal::digital::v2::OutputPin;
use embedded_nrf24l01::{Configuration, CrcMode, DataRate, RxMode, StandbyMode, NRF24L01};
use mkl25z4_hal::time::CopyableMonoTimer;
use mkl25z4_hal::NoError;
use protocol::{Packet, Report, Value};

use super::pins::{RadioCe, RadioCs, RadioIrq, RadioPins, RadioPwr, RadioSpi};
use super::sensors::BME280Data;

const DEVICE_ID: u8 = 0x20;
const KEY: [u8; 16] = include!("../../../common/display_key.txt");

pub struct Radio {
    nrf24: Option<NRF24L01<NoError, RadioCe, RadioCs, RadioSpi>>,
    pwr: RadioPwr,
    irq: RadioIrq,
    salt: Option<u64>,
    time: CopyableMonoTimer,
}

impl Radio {
    pub fn init(mut pins: RadioPins, time: CopyableMonoTimer) -> Radio {
        pins.pwr.set_low().ok();
        let nrf24 = NRF24L01::new(pins.ce, pins.cs, pins.spi).unwrap();
        let nrf24 = nrf24.power_down().unwrap();
        pins.pwr.set_high().ok();
        Radio {
            nrf24: Some(nrf24),
            pwr: pins.pwr,
            irq: pins.irq,
            salt: None,
            time,
        }
    }

    pub fn enable<'a>(&'a mut self) -> EnabledRadio<'a> {
        // Enable supply voltage.
        self.pwr.set_low().ok();
        self.time.delay_ms(10u8);

        // Configure the device.
        let nrf24 = self.nrf24.take().unwrap();
        let mut nrf24 = StandbyMode::power_up(nrf24).unwrap();
        self.time.delay_ms(2u8);
        nrf24.set_frequency(0x32).unwrap();
        nrf24.set_rf(DataRate::R2Mbps, 3).unwrap();
        nrf24.set_crc(Some(CrcMode::OneByte)).unwrap();
        nrf24.set_auto_retransmit(250, 1).unwrap();
        nrf24.set_auto_ack(&[true; 6]).unwrap();
        nrf24
            .set_pipes_rx_enable(&[true, true, false, false, false, false])
            .unwrap();
        nrf24.set_tx_addr(&[0xB3, 0xB3, 0xB3, 0xB3, 0x00]).unwrap();
        nrf24
            .set_rx_addr(0, &[0xB3, 0xB3, 0xB3, 0xB3, 0x00])
            .unwrap();
        nrf24
            .set_rx_addr(1, &[0xB3, 0xB3, 0xB3, 0xB3, 0x20])
            .unwrap();
        nrf24.flush_rx().unwrap();
        nrf24.flush_tx().unwrap();
        nrf24.set_pipes_rx_lengths(&[None; 6]).unwrap();

        EnabledRadio {
            radio: self,
            nrf24: Some(nrf24),
        }
    }
}

pub struct EnabledRadio<'a> {
    radio: &'a mut Radio,
    nrf24: Option<StandbyMode<NRF24L01<NoError, RadioCe, RadioCs, RadioSpi>>>,
}

impl<'a> EnabledRadio<'a> {
    // TODO: Use generic "SensorValues" instead of BME280Data, which includes ambient light
    // readings
    pub fn send_sensor_values(&mut self, values: BME280Data) -> bool {
        if self.radio.salt.is_none() {
            if !self.fetch_salt() {
                return false;
            }
        }

        let mut tx = self.nrf24.take().unwrap().tx().unwrap();
        while !tx.can_send().unwrap() {}
        let mut payload = [0u8; 32];
        let mut report = Report {
            count: 3,
            values: [Value::Invalid; 8],
        };
        report.values[0] = Value::Temperature(values.temperature as i16);
        report.values[1] = Value::Pressure(values.pressure);
        report.values[2] = Value::Humidity(values.humidity as u16);
        let packet = Packet::Report(report);
        packet.encode_and_encrypt(&KEY, self.radio.salt.unwrap(), &mut payload);
        tx.send(&payload).unwrap();
        // TODO: Check whether the packet arrived.

        self.nrf24 = Some(tx.standby().unwrap());
        true
    }

    fn fetch_salt(&mut self) -> bool {
        // Request a device ID.
        let mut tx = self.nrf24.take().unwrap().tx().unwrap();
        while !tx.can_send().unwrap() {}
        let mut payload = [0u8; 32];
        let packet = Packet::GetSalt;
        packet.encode_and_encrypt(&KEY, DEVICE_ID as u64, &mut payload);
        tx.send(&payload).unwrap();
        // TODO: Check whether the packet arrived.

        // Wait for the initial salt.
        let mut salt_received = false;
        let mut rx = tx.standby().unwrap().rx().unwrap();
        loop {
            let packet = match self.receive_packet(&mut rx) {
                None => break,
                Some(p) => p,
            };

            match packet {
                Packet::Salt(salt) => {
                    self.radio.salt = Some((salt & 0x7fffffffffffff00) | DEVICE_ID as u64);
                    salt_received = true;
                    break;
                }
                _ => continue,
            }
        }

        self.nrf24 = Some(rx.standby());
        salt_received
    }

    fn receive_packet(
        &mut self,
        rx: &mut RxMode<NRF24L01<NoError, RadioCe, RadioCs, RadioSpi>>,
    ) -> Option<Packet> {
        // TODO: Timeout.
        loop {
            if let Some(_pipe) = rx.can_read().unwrap() {
                // Receive a packet.
                // TODO: Check pipe?
                let payload = rx.read().unwrap();
                if payload.len() != 32 {
                    continue;
                }
                let mut payload: [u8; 32] = payload[0..32].try_into().unwrap();

                let packet = match Packet::decrypt_and_decode(&KEY, &mut payload) {
                    Ok(p) => p,
                    Err(_) => continue,
                };

                return Some(packet);
            }
        }
    }
}

impl<'a> Drop for EnabledRadio<'a> {
    fn drop(&mut self) {
        let nrf24 = self.nrf24.take().unwrap();
        // Disable the chip and remove the supply voltage.
        let nrf24 = nrf24.power_down().unwrap();
        self.radio.pwr.set_high().ok();
        self.radio.nrf24 = Some(nrf24);
    }
}
