//! Event source for tracking Unix signals
//!
//! Only available on Linux.
//!
//! This allows you to track  and receive Unix signals through the event loop
//! rather than by registering signal handlers. It uses `signalfd` under the hood.
//!
//! The source will take care of masking and unmasking signals for the thread it runs on,
//! but you are responsible for masking them on other threads if you run them. The simplest
//! way to ensure that is to setup the signal event source before spawning any thread, as
//! they'll inherit their parent signal mask.

use std::convert::TryFrom;
use std::fmt;
use std::io::Error as IoError;
use std::os::raw::c_int;

use nix::sys::signal::SigSet;
use nix::sys::signalfd::{siginfo, SfdFlags, SignalFd};
use tracing::warn;

use super::generic::{FdWrapper, Generic};
use crate::{EventSource, Interest, Mode, Poll, PostAction, Readiness, Token, TokenFactory};

// Code taken from async-signal
macro_rules! define_signal_enum {
    (
        $(#[$outer:meta])*
        pub enum Signal {
            $(
                $(#[$inner:meta])*
                $value:ident,
            )*
        }
    ) => {
        $(#[$outer])*
        #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
        #[repr(i32)]
        pub enum Signal {
            $(
                $(#[$inner])*
                $value = nix::sys::signal::Signal::$value as i32,
            )*
        }

        impl Signal {
            /// Returns the signal number.
            fn as_nix(self) -> nix::sys::signal::Signal {
                match self {
                    $(
                        Signal::$value => nix::sys::signal::Signal::$value,
                    )*
                }
            }

            /// Generate from a signal number.
            fn from_num(id: i32) -> Self {
                match nix::sys::signal::Signal::try_from(id) {
                    $(
                        Ok(nix::sys::signal::Signal::$value) => Self::$value,
                    )*

                    Ok(sig) => panic!("unknown signal: {:?}", sig),
                    Err(_) => panic!("unknown signal: {}", id)
                }
            }
        }
    }
}

define_signal_enum! {
    // Copied from https://github.com/bytecodealliance/rustix/blob/main/src/backend/linux_raw/process/types.rs#L81-L161

    /// The signal types that we are able to listen for.
    pub enum Signal {
        /// `SIGHUP`
        SIGHUP,
        /// `SIGINT`
        SIGINT,
        /// `SIGQUIT`
        SIGQUIT,
        /// `SIGILL`
        SIGILL,
        /// `SIGTRAP`
        SIGTRAP,
        /// `SIGABRT`, aka `SIGIOT`
        #[doc(alias = "Iot")]
        #[doc(alias = "Abrt")]
        SIGABRT,
        /// `SIGBUS`
        SIGBUS,
        /// `SIGFPE`
        SIGFPE,
        /// `SIGKILL`
        SIGKILL,
        /// `SIGUSR1`
        SIGUSR1,
        /// `SIGSEGV`
        SIGSEGV,
        /// `SIGUSR2`
        SIGUSR2,
        /// `SIGPIPE`
        SIGPIPE,
        /// `SIGALRM`
        #[doc(alias = "Alrm")]
        SIGALRM,
        /// `SIGTERM`
        SIGTERM,
        /// `SIGCHLD`
        #[doc(alias = "Chld")]
        SIGCHLD,
        /// `SIGCONT`
        SIGCONT,
        /// `SIGSTOP`
        SIGSTOP,
        /// `SIGTSTP`
        SIGTSTP,
        /// `SIGTTIN`
        SIGTTIN,
        /// `SIGTTOU`
        SIGTTOU,
        /// `SIGURG`
        SIGURG,
        /// `SIGXCPU`
        SIGXCPU,
        /// `SIGXFSZ`
        SIGXFSZ,
        /// `SIGVTALRM`
        #[doc(alias = "Vtalrm")]
        SIGVTALRM,
        /// `SIGPROF`
        SIGPROF,
        /// `SIGWINCH`
        SIGWINCH,
        /// `SIGIO`, aka `SIGPOLL`
        #[doc(alias = "Poll")]
        SIGIO,
        /// `SIGSYS`, aka `SIGUNUSED`
        #[doc(alias = "Unused")]
        SIGSYS,
    }
}

/// An event generated by the signal event source
#[derive(Copy, Clone, Debug)]
pub struct Event {
    info: siginfo,
}

macro_rules! inner_accessors {
    ($($name:ident => $sname:ident: $ty:ty),*) => {$(
        /// Gets the `
        #[doc = stringify!($sname)]
        /// ` field.
        pub fn $name(&self) -> $ty {
            use core::convert::TryInto;
            self.info.$sname.try_into().unwrap()
        }
    )*}
}

impl Event {
    /// Retrieve the signal number that was receive
    pub fn signal(&self) -> Signal {
        Signal::from_num(self.info.ssi_signo as c_int)
    }

    inner_accessors! {
        errno => ssi_errno: i32,
        code => ssi_code: i32,
        pid => ssi_pid: u32,
        uid => ssi_uid: u32,
        fd => ssi_fd: i32,
        tid => ssi_tid: u32,
        bad => ssi_band: u32,
        overrun => ssi_overrun: u32,
        trapno => ssi_trapno: u32,
        status => ssi_status: u32
    }
}

/// An event source for receiving Unix signals
#[derive(Debug)]
pub struct Signals {
    sfd: Generic<FdWrapper<SignalFd>>,
    mask: SigSet,
}

impl Signals {
    /// Create a new signal event source listening on the specified list of signals
    pub fn new(signals: &[Signal]) -> crate::Result<Signals> {
        let mut mask = SigSet::empty();
        for &s in signals {
            mask.add(s.as_nix());
        }

        // Mask the signals for this thread
        mask.thread_block().map_err(IoError::from)?;
        // Create the SignalFd
        let sfd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK | SfdFlags::SFD_CLOEXEC)
            .map_err(IoError::from)?;

        Ok(Signals {
            sfd: Generic::new(unsafe { FdWrapper::new(sfd) }, Interest::READ, Mode::Level),
            mask,
        })
    }

    /// Add a list of signals to the signals source
    ///
    /// If this function returns an error, the signal mask of the thread may
    /// have still been changed.
    pub fn add_signals(&mut self, signals: &[Signal]) -> crate::Result<()> {
        for &s in signals {
            self.mask.add(s.as_nix());
        }
        self.mask.thread_block().map_err(IoError::from)?;

        // SAFETY: We don't drop the underlying mask.
        unsafe {
            self.sfd
                .get_mut()
                .set_mask(&self.mask)
                .map_err(IoError::from)?;
        }
        Ok(())
    }

    /// Remove a list of signals from the signals source
    ///
    /// If this function returns an error, the signal mask of the thread may
    /// have still been changed.
    pub fn remove_signals(&mut self, signals: &[Signal]) -> crate::Result<()> {
        let mut removed = SigSet::empty();
        for &s in signals {
            self.mask.remove(s.as_nix());
            removed.add(s.as_nix());
        }
        removed.thread_unblock().map_err(IoError::from)?;

        // SAFETY: We don't drop the underlying mask.
        unsafe {
            self.sfd
                .get_mut()
                .set_mask(&self.mask)
                .map_err(IoError::from)?;
        }
        Ok(())
    }

    /// Replace the list of signals of the source
    ///
    /// If this function returns an error, the signal mask of the thread may
    /// have still been changed.
    pub fn set_signals(&mut self, signals: &[Signal]) -> crate::Result<()> {
        let mut new_mask = SigSet::empty();
        for &s in signals {
            new_mask.add(s.as_nix());
        }

        self.mask.thread_unblock().map_err(IoError::from)?;
        new_mask.thread_block().map_err(IoError::from)?;

        // SAFETY: We don't drop the underlying mask.
        unsafe {
            self.sfd
                .get_mut()
                .set_mask(&new_mask)
                .map_err(IoError::from)?;
        }
        self.mask = new_mask;

        Ok(())
    }
}

impl Drop for Signals {
    fn drop(&mut self) {
        // we cannot handle error here
        if let Err(e) = self.mask.thread_unblock() {
            warn!("Failed to unmask signals: {e:?}");
        }
    }
}

impl EventSource for Signals {
    type Event = Event;
    type Metadata = ();
    type Ret = ();
    type Error = SignalError;

    fn process_events<C>(
        &mut self,
        readiness: Readiness,
        token: Token,
        mut callback: C,
    ) -> Result<PostAction, Self::Error>
    where
        C: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
    {
        self.sfd
            .process_events(readiness, token, |_, sfd| {
                loop {
                    match unsafe { sfd.get_mut().read_signal() } {
                        Ok(Some(info)) => callback(Event { info }, &mut ()),
                        Ok(None) => break,
                        Err(e) => {
                            warn!("Error reading from signalfd: {e}");
                            return Err(e.into());
                        }
                    }
                }
                Ok(PostAction::Continue)
            })
            .map_err(|e| SignalError(e.into()))
    }

    fn register(&mut self, poll: &mut Poll, token_factory: &mut TokenFactory) -> crate::Result<()> {
        self.sfd.register(poll, token_factory)
    }

    fn reregister(
        &mut self,
        poll: &mut Poll,
        token_factory: &mut TokenFactory,
    ) -> crate::Result<()> {
        self.sfd.reregister(poll, token_factory)
    }

    fn unregister(&mut self, poll: &mut Poll) -> crate::Result<()> {
        self.sfd.unregister(poll)
    }
}

/// An error arising from processing events for a process signal.
#[derive(Debug)]
pub struct SignalError(Box<dyn std::error::Error + Sync + Send>);

impl fmt::Display for SignalError {
    #[cfg_attr(feature = "nightly_coverage", coverage(off))]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl std::error::Error for SignalError {
    #[cfg_attr(feature = "nightly_coverage", coverage(off))]
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&*self.0)
    }
}
