use std::convert::TryInto;
use std::io;
use std::time::Duration;

use futures_util::stream::StreamExt;
use log::{error, info};
use nrf24l01_stick_driver::{Configuration, CrcMode, DataRate, Receiver, NRF24L01};
use protocol::Packet;
use rand::Rng;
use tokio::select;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::time::delay_for;

use super::get_key;

pub struct RadioTask {
    nrf: Option<Receiver>,

    tx: UnboundedReceiver<(u8, Packet)>,
    rx: UnboundedSender<(u8, Packet)>,
}

impl RadioTask {
    pub fn new(
        tx: UnboundedReceiver<(u8, Packet)>,
        rx: UnboundedSender<(u8, Packet)>,
    ) -> RadioTask {
        RadioTask { nrf: None, tx, rx }
    }

    pub async fn init(&mut self) -> Result<(), RadioError> {
        let mut config = Configuration::default();
        config.channel = 0x32;
        config.rate = DataRate::R2Mbps;
        config.power = 3;
        config.crc = Some(CrcMode::OneByte);
        config.auto_retransmit_delay_count = Some((250, 3));
        // TODO: Auto ACK

        let mut nrf24l01 = NRF24L01::open_default(config)
            .await
            .expect("could not open device");
        nrf24l01
            .set_receive_addr(
                Some((&[0xb3u8, 0xb3u8, 0xb3u8, 0xb3u8, 0x00u8] as &[u8]).into()),
                None,
                None,
                None,
                None,
            )
            .await
            .expect("could not set receive address");
        self.nrf = Some(nrf24l01.receive().await.expect("could not start receiving"));

        Ok(())
    }

    pub async fn run(&mut self) {
        loop {
            let e = self.run_with_initialized_radio().await;
            error!("Error in radio task: {:?}", e);
            self.nrf = None;
            // Try to reinitialize the radio.
            loop {
                info!("Trying to reinitialize radio: {:?}", e);
                match self.init().await {
                    Ok(_) => break,
                    Err(e) => error!("Radio reinitialization failed: {:?}", e),
                }
                delay_for(Duration::from_secs(3)).await;
            }
        }
    }

    async fn run_with_initialized_radio(&mut self) -> Result<(), RadioError> {
        // Wait for an RX interrupt of until we have to send a packet.
        loop {
            select! {
                packet = self.nrf.as_mut().unwrap().receive() => {
                    let packet = packet?;
                    info!(
                        "packet received on pipe {}: {:x?}, {}",
                        packet.pipe,
                        packet.payload,
                        packet.payload.len()
                    );
                    if packet.payload.len() != 32 {
                        continue;
                    }

                    let mut payload: [u8; 32] = packet.payload[0..32].try_into().unwrap();
                    self.handle_packet(&mut payload).await;
                },
                packet = self.tx.next() => {
                    let (device_id, packet) = packet.unwrap();
                    // Switch to TX mode and send the packet.
                    self.send_packet(device_id, packet).await?;
                },
            }
        }
    }

    async fn handle_packet(&mut self, packet: &mut [u8]) {
        // Get the key of the device.
        let device_id = packet[0];
        let key = match get_key(device_id) {
            Some(k) => k,
            None => {
                info!("packet from unknown device {:02x}", device_id);
                return;
            }
        };

        // Decode the packet.
        let packet = match Packet::decrypt_and_decode(key, packet) {
            Err(e) => {
                info!(
                    "invalid packet from device {:02x}, error {:?}",
                    device_id, e
                );
                return;
            }
            Ok(p) => p,
        };
        // TODO: For all packets except for salt requests, check whether the
        // salt was incremented to prevent replay attacks.
        // TODO: Also check whether the device bit in the salt is 0.

        info!("packet from {}: {:?}", device_id, packet);

        self.rx.send((device_id, packet)).unwrap();
    }

    async fn send_packet(&mut self, device_id: u8, packet: Packet) -> Result<(), RadioError> {
        let salt = rand::thread_rng().gen::<u64>();
        // TODO: Fix salt (device bit, device id).
        let mut encoded = [0u8; 32];
        let key = get_key(device_id);

        info!("sending packet: {:?}", packet);
        if packet.encode_and_encrypt(key.unwrap(), salt, &mut encoded) {
            match self
                .nrf
                .as_mut()
                .unwrap()
                .send(
                    (&[0xb3u8, 0xb3u8, 0xb3u8, 0xb3u8, device_id][..]).into(),
                    &encoded,
                )
                .await
            {
                Ok(()) => {}
                Err(e) => println!("could not send: {:?}", e), // TODO: Distinguish between packet loss and other errors!
            }
        } else {
            info!("could not encode packet {:?}", packet);
        }
        Ok(())
    }
}

#[derive(thiserror::Error, Debug)]
pub enum RadioError {
    #[error("I/O error")]
    Io(#[from] io::Error),
    #[error("radio error: {0:?}")]
    Radio(#[from] nrf24l01_stick_driver::Error),
}
