use crate::options::structs::CustomError;
use std::sync::Arc;
use tokio::io;
use tokio::sync::mpsc;
use tokio::{
    select,
    signal::unix::{signal, Signal, SignalKind},
};

type SendersVec = Arc<Vec<Arc<mpsc::Sender<u8>>>>;

/// # Fn set_valid_destructor
/// ## for initializing process of unstoppable grubbing metrics.
/// 
/// *input*      : `Result<()>`
///
/// *output*     : `Err` if it cant create signals listeners | `Ok` on returning Monitor 
///
/// *initiator*  : main thread
///
/// *managing*   : `Arc<Vec<Arc<mpsc::Sender<u8>>>>`
///
/// *depends on* : Sig, Signals
/// 
pub async fn set_valid_destructor(senders: SendersVec) -> Result<(), CustomError> {
    let (mut int, mut term, mut stop) = (
        Sig::new(Signals::Sigint, senders.clone()),
        Sig::new(Signals::Sigterm, senders.clone()),
        Sig::new(Signals::Sigstop, senders.clone()),
    );

    select! {
        _ = int.post_processing() => {log::info!("Initializing interruption...")},
        _ = term.post_processing() => {log::info!("Initializing termination...")},
        _ = stop.post_processing() => {log::info!("Initializing freezing...")},
    }
    Ok(())
}
/// # Enum Signals
/// ## for instancing each managed system signals (such as SIGINT)
/// 
/// > (element needed in Sig constructor's signature)
/// 
/// *depends on* : -
enum Signals {
    Sigint,
    Sigterm,
    Sigstop,
}

/// # Struct Signals
/// ## for instancing each managed system signals (such as SIGINT)
/// 
/// > (needed to construct system signals listener)
/// 
/// *depends on* : Signals
struct Sig {
    signal: Signal,
    sig_type: Signals,
    senders: SendersVec,
}
/// ## default Sig's constructor
impl Sig {
    fn new(signal_type: Signals, sends: SendersVec) -> Self {
        Sig {
            signal: signal_type.get_signal().unwrap(),
            sig_type: signal_type,
            senders: sends,
        }
    }
}
/// ## trait Display realization for returning String-name of signal
/// 
/// > (needed in logs)
impl std::fmt::Display for Signals {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Signals::Sigint => write!(f, "SIGINT"),
            Signals::Sigterm => write!(f, "SIGTERM"),
            Signals::Sigstop => write!(f, "SIGSTOP"),
        }
    }
}
/// ## associated func to init signals listener
impl Signals {
    fn get_signal(&self) -> io::Result<Signal> {
        match self {
            Signals::Sigint => signal(SignalKind::interrupt()),
            Signals::Sigterm => signal(SignalKind::terminate()),
            Signals::Sigstop => signal(SignalKind::quit()),
        }
    }
}
/// # Trait SigPostProcessing 
/// ## to handle post-processing jobs after getting system signal
/// 
/// ## > (needed in signals post-processing)
/// 
trait SigPostProcessing {
    async fn post_processing(&mut self) -> io::Result<()>;
}

/// # Trait SigPostProcessing realization for Sig struct 
/// ## to deinitialize Monitor correctly after getting signal
/// 
/// ## > (needed in signals post-processing)
/// 
impl SigPostProcessing for Sig {
    async fn post_processing(&mut self) -> io::Result<()> {
        // manipulations ...
        if self.signal.recv().await.is_some() {
            log::info!("Got {} signal", self.sig_type);
            for prc in self.senders.clone().iter() {
                let _ = prc.send(111).await;
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod signals_unittest {
    use super::*;
    #[tokio::test]
    async fn get_signal_check() {
        assert!(Signals::Sigint.get_signal().is_ok())
    }
}
