//! Betriebssystemsignale als Ereignisquelle (design.md, D8).
//!
//! Der Signalhandler tut nur eines: er setzt eine Marke. Übersetzt wird sie
//! erst beim Abholen durch den Host, an einem gewöhnlichen Zustellpunkt —
//! im Signalkontext läuft damit kein Programmcode und nichts von der
//! Laufzeit.
//!
//! Abgebildet wird nur, was auf allen Zielplattformen besteht:
//! `SIGNAL(1)` = `SIGINT`, `SIGNAL(2)` = `SIGTERM` (Sprachreferenz,
//! Abweichung 8). Unter Windows nutzt `signal-hook` das `signal` der
//! C-Laufzeit; `SIGTERM` ist dort von außen nicht zustellbar.

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use signal_hook::consts::{SIGINT, SIGTERM};

/// Kennung von `SIGNAL(n%)`.
pub const SIGNAL_INT: u8 = 1;
pub const SIGNAL_TERM: u8 = 2;

pub struct Signalquelle {
    int: Arc<AtomicBool>,
    term: Arc<AtomicBool>,
}

impl Signalquelle {
    /// Handler eintragen. Schlägt das fehl, bleibt die Quelle stumm — ein
    /// Programm ohne `SIGNAL`-Trap merkt davon nichts, und eines mit Trap
    /// verhält sich wie unter `SIGNAL(n) OFF`.
    pub fn neu() -> Self {
        let int = Arc::new(AtomicBool::new(false));
        let term = Arc::new(AtomicBool::new(false));
        let _ = signal_hook::flag::register(SIGINT, Arc::clone(&int));
        let _ = signal_hook::flag::register(SIGTERM, Arc::clone(&term));
        Signalquelle { int, term }
    }

    /// Höchstens ein anstehendes Signal abholen und die Marke zurücksetzen.
    /// `SIGINT` hat Vorrang: es ist das dringendere der beiden.
    pub fn abholen(&self) -> Option<u8> {
        if self.int.swap(false, Ordering::SeqCst) {
            return Some(SIGNAL_INT);
        }
        if self.term.swap(false, Ordering::SeqCst) {
            return Some(SIGNAL_TERM);
        }
        None
    }
}

#[cfg(test)]
impl Signalquelle {
    /// Quelle ohne eingetragene Handler. Ein echtes Signal erreicht **jede**
    /// im Prozess registrierte Quelle; die reinen Übersetzungstests müssen
    /// deshalb aus dem Weg bleiben.
    fn ohne_registrierung() -> Self {
        Signalquelle {
            int: Arc::new(AtomicBool::new(false)),
            term: Arc::new(AtomicBool::new(false)),
        }
    }
}

impl Default for Signalquelle {
    fn default() -> Self {
        Signalquelle::neu()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Marke → Ereignis, ohne echtes Signal: prüft die Übersetzung.
    #[test]
    fn marke_wird_zu_kennung_und_verfaellt_danach() {
        let q = Signalquelle::ohne_registrierung();
        assert_eq!(q.abholen(), None);
        q.int.store(true, Ordering::SeqCst);
        assert_eq!(q.abholen(), Some(SIGNAL_INT));
        assert_eq!(q.abholen(), None, "eine Marke gilt genau einmal");
        q.term.store(true, Ordering::SeqCst);
        assert_eq!(q.abholen(), Some(SIGNAL_TERM));
    }

    #[test]
    fn sigint_hat_vorrang_vor_sigterm() {
        let q = Signalquelle::ohne_registrierung();
        q.int.store(true, Ordering::SeqCst);
        q.term.store(true, Ordering::SeqCst);
        assert_eq!(q.abholen(), Some(SIGNAL_INT));
        assert_eq!(q.abholen(), Some(SIGNAL_TERM));
    }

    /// Ganze Kette: echtes Signal an den eigenen Prozess. Der eingetragene
    /// Handler ersetzt die Vorgabewirkung, der Test überlebt es also.
    #[test]
    fn echtes_signal_setzt_die_marke() {
        let q = Signalquelle::neu();
        signal_hook::low_level::raise(SIGINT).expect("raise");
        // Zustellung ist asynchron; ein paar Versuche genuegen.
        for _ in 0..1000 {
            if let Some(n) = q.abholen() {
                assert_eq!(n, SIGNAL_INT);
                return;
            }
            std::thread::yield_now();
        }
        panic!("SIGINT kam nicht an");
    }
}
