use core::convert::TryInto;

use embedded_hal::blocking::delay::DelayMs;
use embedded_hal::digital::v2::InputPin;
use embedded_hal::digital::v2::OutputPin;
use embedded_nrf24l01::{Configuration, CrcMode, DataRate, StandbyMode, NRF24L01};
use mkl25z4_hal::time::{CopyableMonoTimer, Instant, MilliSeconds, MonoTimer, U32Ext};
use mkl25z4_hal::NoError;
use protocol::{GetValues, Location, Packet, PacketType, Report, Value, ValueType};

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 {
        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);
        self.send_packet(Packet::Report(report));

        true
    }

    pub fn request_values(
        &mut self,
        location: Location,
        value_types: &[ValueType],
        values: &mut [Option<i64>],
    ) -> bool {
        // We always clear the value array so that the caller can ignore the return value.
        for i in 0..values.len() {
            values[i] = None;
        }

        // Request the values.
        let mut get_values = GetValues {
            count: value_types.len() as u8,
            location,
            types_: [ValueType::Time; 8],
        };
        get_values.types_[0..value_types.len()].copy_from_slice(value_types);
        self.send_packet(Packet::GetValues(get_values));

        // Receive the response.
        let response = match self.receive_packet(PacketType::Values, 100.ms()) {
            None => return false,
            Some(Packet::Values(values)) => values,
            _ => return false,
        };

        for i in 0..response.count {
            let (type_, value) = match response.values[i as usize].split() {
                Some(value) => value,
                None => return false,
            };

            for j in 0..values.len() {
                if value_types[j] == type_ {
                    values[j] = Some(value);
                }
            }
        }

        true
    }

    fn send_packet(&mut self, packet: Packet) -> 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];
        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 {
        self.send_packet_raw(Packet::GetSalt);

        // Wait for the initial salt.
        match self.receive_packet(PacketType::Salt, 100.ms()) {
            None => false,
            Some(Packet::Salt(salt)) => {
                self.radio.salt = Some((salt & 0x7fffffffffffff00) | DEVICE_ID as u64);
                true
            }
            _ => false,
        }
    }

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

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

    fn receive_packet<T: Into<MilliSeconds>>(
        &mut self,
        type_: PacketType,
        timeout: T,
    ) -> Option<Packet> {
        // TODO: Perhaps the radio should stay longer in RX mode?
        let mut rx = self.nrf24.take().unwrap().rx().unwrap();
        let timeout = Timeout::new(&self.radio.time, timeout);
        while !timeout.expired() {
            // Reduce bus activity as it can negatively affect the radio.
            // TODO: Wait for proper interrupt to reduce power consumption?
            if self.radio.irq.is_high().unwrap() {
                continue;
            }

            if let Some(_pipe) = rx.can_read().unwrap() {
                // Receive a packet.
                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,
                };
                if packet.get_type() != type_ {
                    continue;
                }

                return Some(packet);
            }
        }
        self.nrf24 = Some(rx.standby());
        None
    }
}

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);
    }
}

struct Timeout<'a, Timer> {
    timer: &'a Timer,
    start: Instant,
    timeout: u32,
}

impl<'a, Timer> Timeout<'a, Timer>
where
    Timer: MonoTimer,
{
    fn new<T: Into<MilliSeconds>>(timer: &'a Timer, timeout: T) -> Self {
        let ticks = (timer.frequency().0 as u64 * timeout.into().0 as u64 / 1000) as u32;
        Self {
            timer,
            start: timer.now(),
            timeout: ticks,
        }
    }

    fn expired(&self) -> bool {
        self.timer.now().elapsed(self.start) > self.timeout
    }
}
