// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
// SPDX-License-Identifier: BSD-2-Clause
//! Common definitions for the `timelimit` library.

use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::str::FromStr;

use anyhow::{Context as _, Error as AnyError, Result, anyhow};
use nix::sys::signal::{self, Signal as NixSignal};

/// The features supported by this version of the library.
pub const FEATURES: [(&str, &str); 2] = [
    ("timelimit", env!("CARGO_PKG_VERSION")),
    ("subsecond", "1.0"),
];

/// The definition of a single Unix signal.
#[derive(Debug, Clone, Copy)]
pub struct Signal {
    /// The underlying representation of the signal.
    sig: NixSignal,
}

impl Signal {
    /// A termination signal.
    pub(crate) const SIGTERM: Self = Self {
        sig: NixSignal::SIGTERM,
    };

    /// A kill signal.
    pub(crate) const SIGKILL: Self = Self {
        sig: NixSignal::SIGKILL,
    };

    /// The underlying representation of the signal.
    #[inline]
    #[must_use]
    pub(crate) const fn sig(self) -> NixSignal {
        self.sig
    }

    /// Parse a raw integer value into a signal.
    ///
    /// # Errors
    ///
    /// Propagate errors from the [`nix::sys::signal::Signal::try_from`] method.
    #[inline]
    pub fn from_raw(value: i32) -> Result<Self> {
        let sig = NixSignal::try_from(value).context("Signal value out of range")?;
        Ok(Self { sig })
    }

    /// Raise the signal in the current process.
    ///
    /// # Errors
    ///
    /// Propagate errors from the [`nix::sys::signal::raise`] function.
    #[inline]
    pub fn raise(self) -> Result<()> {
        signal::raise(self.sig).with_context(|| {
            format!(
                "Could not send {sig} to the timelimit process",
                sig = self.sig
            )
        })
    }

    /// List the signal names supported by the `smol` runtime.
    #[inline]
    pub fn list() -> impl Iterator<Item = &'static str> {
        NixSignal::iterator().map(|sig| {
            let name = sig.as_str();

            name.strip_prefix("SIG").unwrap_or(name)
        })
    }
}

impl Display for Signal {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{sig}", sig = self.sig)
    }
}

impl FromStr for Signal {
    type Err = AnyError;

    #[inline]
    fn from_str(value: &str) -> Result<Self> {
        let first_char = value
            .chars()
            .next()
            .ok_or_else(|| anyhow!("Empty signal name"))?;
        if first_char.is_ascii_alphabetic() {
            let sig = (if value.starts_with("SIG") {
                NixSignal::from_str(value)
            } else {
                NixSignal::from_str(&format!("SIG{value}"))
            })
            .context("Unrecognized signal name")?;
            Ok(Self { sig })
        } else {
            Self::from_raw(i32::from_str(value).context("Invalid signal value")?)
        }
    }
}

/// Build the runtime configuration for the `timelimit` routines.
#[derive(Debug)]
pub struct ConfigBuilder {
    /// The time to wait before sending the warning signal.
    warntime: f32,

    /// The warning signal to send.
    warnsig: Signal,

    /// The time to wait before sending the kill signal after the warning signal has been sent.
    killtime: f32,

    /// The kill signal to send.
    killsig: Signal,
}

impl ConfigBuilder {
    /// Prepare the default values.
    #[inline]
    #[must_use]
    pub const fn new() -> Self {
        Self {
            warntime: 3600_f32,
            warnsig: Signal::SIGTERM,
            killtime: 120_f32,
            killsig: Signal::SIGKILL,
        }
    }

    /// Specify the time to wait before sending the warning signal.
    #[inline]
    #[must_use]
    pub const fn warntime(self, warntime: f32) -> Self {
        Self { warntime, ..self }
    }

    /// Specify the warning signal to send.
    #[inline]
    #[must_use]
    pub const fn warnsig(self, warnsig: Signal) -> Self {
        Self { warnsig, ..self }
    }

    /// Specify the additional time to wait before sending the kill signal.
    #[inline]
    #[must_use]
    pub const fn killtime(self, killtime: f32) -> Self {
        Self { killtime, ..self }
    }

    /// Specify the kill signal to send.
    #[inline]
    #[must_use]
    pub const fn killsig(self, killsig: Signal) -> Self {
        Self { killsig, ..self }
    }

    /// Build the [`Config`] object.
    #[inline]
    #[must_use]
    pub const fn build(self) -> Config {
        Config {
            warntime: self.warntime,
            warnsig: self.warnsig,
            killtime: self.killtime,
            killsig: self.killsig,
        }
    }
}

impl Default for ConfigBuilder {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}

/// Runtime configuration for the `timelimit` routines.
#[derive(Debug, Clone, Copy)]
pub struct Config {
    /// The time to wait before sending the warning signal.
    warntime: f32,

    /// The warning signal to send.
    warnsig: Signal,

    /// The time to wait before sending the kill signal after the warning signal has been sent.
    killtime: f32,

    /// The kill signal to send.
    killsig: Signal,
}

impl Config {
    /// The time to wait before sending the warning signal.
    #[inline]
    #[must_use]
    pub const fn warntime(&self) -> f32 {
        self.warntime
    }

    /// The warning signal to send.
    #[inline]
    #[must_use]
    pub const fn warnsig(&self) -> Signal {
        self.warnsig
    }

    /// The time to wait before sending the kill signal.
    #[inline]
    #[must_use]
    pub const fn killtime(&self) -> f32 {
        self.killtime
    }

    /// The kill signal to send.
    #[inline]
    #[must_use]
    pub const fn killsig(&self) -> Signal {
        self.killsig
    }

    /// Start building a [`Config`] object field by field.
    #[inline]
    #[must_use]
    pub const fn builder() -> ConfigBuilder {
        ConfigBuilder::new()
    }
}
