Documentation
use std::sync::Arc;

use evdev::InputEvent;
use tokio::sync::{Mutex, mpsc, oneshot};
use tracing::error;

use crate::{Result, Runtime};

mod device_scanner;
mod input_scanner;

pub fn get_runtime() -> Result<impl Runtime> {
    Ok(LinuxRuntime::initialize())
}

struct LinuxRuntime {
    input_sxs: Arc<Mutex<Vec<mpsc::Sender<InputEvent>>>>,
    kill_sxs: Vec<oneshot::Sender<()>>,
}

impl Drop for LinuxRuntime {
    fn drop(&mut self) {
        for kill_sx in self.kill_sxs.drain(..) {
            let _ = kill_sx.send(());
        }
    }
}

impl LinuxRuntime {
    fn initialize() -> Self {
        let input_sxs = Arc::new(Mutex::new(vec![]));
        let (device_kill_sx, device_kill_rx) = oneshot::channel();
        let kill_sxs = vec![device_kill_sx];

        let (sx_devices, rx_devices) = mpsc::channel(100);
        device_scanner::start_device_scanner(sx_devices, device_kill_rx);

        let (sx_input, rx_input) = mpsc::channel(100);
        input_scanner::start_input_scanner(rx_devices, sx_input);

        start_input_sender(rx_input, input_sxs.clone());

        LinuxRuntime { input_sxs, kill_sxs }
    }

    async fn get_input_rx(&mut self) -> mpsc::Receiver<InputEvent> {
        let (sx, rx) = mpsc::channel(100);
        let mut sxs = self.input_sxs.lock().await;
        sxs.push(sx);
        rx
    }
}

fn start_input_sender(mut rx: mpsc::Receiver<InputEvent>, sxs: Arc<Mutex<Vec<mpsc::Sender<InputEvent>>>>) -> () {
    tokio::spawn(async move {
        while let Some(event) = rx.recv().await {
            let mut senders = sxs.lock().await;

            let mut to_remove = vec![];
            for (idx, s) in senders.iter().enumerate() {
                match s.send(event).await {
                    Ok(_) => {},
                    Err(_) => {
                        error!("error sending event, will stop sending through error channel");
                        to_remove.push(idx);
                    }
                }
            }

            if to_remove.len() == 0 {
                continue;
            }

            for idx in to_remove.iter().rev() {
                senders.swap_remove(*idx);
            }
        }
    });
}

impl Runtime for LinuxRuntime {
    async fn get_input_rx(&mut self) -> mpsc::Receiver<InputEvent> {
        LinuxRuntime::get_input_rx(self).await
    }
}