//! `Host`-Abstraktion für den Textbildschirm.
//!
//! Der Host ist bewusst dumm: er zeigt den Zellenpuffer an und liefert
//! Ereignisse. Alle Bildschirmwirkungen entstehen in der Laufzeitbibliothek
//! auf [`TextScreen`] — ein Zeichenstrom könnte `LOCATE`, `COLOR`, `CSRLIN`
//! oder das Zurücklesen per `SCREEN(z, s)` nicht transportieren
//! (Entscheidung 2026-09-03, D2).
//!
//! Der Host besitzt Zeit und Warten; die VM liefert nur an kooperativen
//! Zustellpunkten aus.

use std::collections::VecDeque;

use crate::screen::TextScreen;

/// Was vom Terminal hereinkommt.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Ereignis {
    /// Tastendruck in `INKEY$`-Form (ein Zeichen für Zeichentasten,
    /// Nullzeichen + Kennung für Sondertasten) samt Umschaltzustand.
    ///
    /// Der Umschaltzustand ist ein Bitfeld wie bei der Maus: 1 Umschalt,
    /// 2 Strg, 4 Alt. Er wird für `ON KEY(15)`–`ON KEY(25)` gebraucht —
    /// benutzerdefinierte Tasten sind über Umschaltzustand und Scancode
    /// erklärt, nicht über das erzeugte Zeichen.
    Taste(String, u8),
    /// Die Darstellungsfläche hat ihre Größe geändert.
    Groesse { cols: usize, rows: usize },
    /// Mausereignis in Zellenkoordinaten, 1-basiert wie `LOCATE`.
    Maus(MausEreignis),
    /// Betriebssystemsignal: 1 = SIGINT, 2 = SIGTERM (Sprachreferenz,
    /// Abweichung 8). Andere Signale werden nicht abgebildet.
    Signal(u8),
    /// Abbruchwunsch (Strg+Untbr).
    Abbruch,
    /// Eingabeende — es kommt nichts mehr.
    Ende,
}

/// Was die Maus getan hat. Die Position ist 1-basiert und in Zellen des
/// Textbildschirms — dieselbe Zählweise wie `LOCATE`, `CSRLIN` und `POS`;
/// die Umrechnung aus der 0-basierten Zählung des Terminals macht die
/// Darstellungsschicht (design.md, D7).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MausEreignis {
    pub art: MausArt,
    /// Gedrückte Taste: 1 links, 2 rechts, 4 Mitte; 0 bei reiner Bewegung.
    pub taste: u8,
    /// Umschaltzustand als Bitfeld: 1 Umschalt, 2 Strg, 4 Alt.
    pub shift: u8,
    pub zeile: usize,
    pub spalte: usize,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MausArt {
    Druck,
    Loslassen,
    Bewegung,
}

/// Umschaltzustand: Bitfeld 1 Umschalt, 2 Strg, 4 Alt.
pub mod umschalt {
    pub const SHIFT: u8 = 1;
    pub const CTRL: u8 = 2;
    pub const ALT: u8 = 4;
}

/// Steuerzeichen in `INKEY$`-Form (Kodierung des Vorbilds).
pub mod taste {
    pub const ENTER: &str = "\r";
    pub const BACKSPACE: &str = "\u{8}";
    pub const ESC: &str = "\u{1b}";
    pub const TAB: &str = "\t";

    /// Sondertaste: Nullzeichen + Kennung (F1 = 59, F2 = 60 …).
    pub fn sonder(code: u8) -> String {
        format!("\0{}", code as char)
    }

    /// Zeichen → Scancode der Tastatur (Tabelle aus `qb45advr.hlp`,
    /// „Keyboard Scan Codes"). Gebraucht für die benutzerdefinierten
    /// Trap-Tasten `KEY 15`–`KEY 25`, die über Scancode erklärt sind.
    ///
    /// ponytail: nur die Tasten der Original-Tabelle, keine Ziffernblock-
    /// Doppelbelegungen. Ceiling: ein hier nicht geführtes Zeichen kann
    /// keinen benutzerdefinierten Trap auslösen; die Tabelle wächst, wenn
    /// jemand eine solche Taste braucht.
    pub fn scancode(c: char) -> Option<u8> {
        let c = c.to_ascii_uppercase();
        Some(match c {
            '\u{1b}' => 0x01,
            '1' | '!' => 0x02,
            '2' | '@' => 0x03,
            '3' | '#' => 0x04,
            '4' | '$' => 0x05,
            '5' | '%' => 0x06,
            '6' | '^' => 0x07,
            '7' | '&' => 0x08,
            '8' | '*' => 0x09,
            '9' | '(' => 0x0A,
            '0' | ')' => 0x0B,
            '-' | '_' => 0x0C,
            '=' | '+' => 0x0D,
            '\u{8}' => 0x0E,
            '\t' => 0x0F,
            'Q' => 0x10,
            'W' => 0x11,
            'E' => 0x12,
            'R' => 0x13,
            'T' => 0x14,
            'Y' => 0x15,
            'U' => 0x16,
            'I' => 0x17,
            'O' => 0x18,
            'P' => 0x19,
            '[' | '{' => 0x1A,
            ']' | '}' => 0x1B,
            '\r' => 0x1C,
            'A' => 0x1E,
            'S' => 0x1F,
            'D' => 0x20,
            'F' => 0x21,
            'G' => 0x22,
            'H' => 0x23,
            'J' => 0x24,
            'K' => 0x25,
            'L' => 0x26,
            ';' | ':' => 0x27,
            '\'' | '"' => 0x28,
            '`' | '~' => 0x29,
            '\\' | '|' => 0x2B,
            'Z' => 0x2C,
            'X' => 0x2D,
            'C' => 0x2E,
            'V' => 0x2F,
            'B' => 0x30,
            'N' => 0x31,
            'M' => 0x32,
            ',' | '<' => 0x33,
            '.' | '>' => 0x34,
            '/' | '?' => 0x35,
            ' ' => 0x39,
            _ => return None,
        })
    }

    /// Scancode eines Tastendrucks in `INKEY$`-Form: bei Sondertasten die
    /// Kennung hinter dem Nullzeichen, sonst aus der Zeichentabelle.
    pub fn scancode_von(s: &str) -> Option<u8> {
        let mut it = s.chars();
        match (it.next(), it.next()) {
            (Some('\0'), Some(k)) => Some(k as u8),
            (Some(c), None) => scancode(c),
            _ => None,
        }
    }
}

pub trait Host {
    /// None means the frontend has scheduled a terminal handoff; retry the
    /// same request after it supplies the child result.
    fn shell(&mut self, command: &str) -> Result<Option<i32>, crate::errors::RuntimeError> {
        shell_command(command)
            .status()
            .map(|s| Some(s.code().unwrap_or(0)))
            .map_err(|_| crate::errors::RuntimeError(53))
    }

    /// Aktuellen Bildschirmzustand anzeigen.
    fn present(&mut self, screen: &TextScreen);

    /// Nächstes Ereignis holen. Mit `blockierend = false` sofort `None`,
    /// wenn nichts anliegt (Grundlage von `INKEY$`).
    fn next_event(&mut self, blockierend: bool) -> Option<Ereignis>;

    /// Wartet auf Eingabe oder eine absolute Deadline in `jetzt_ms`-Zeit.
    /// `None` bedeutet: Deadline erreicht. Ohne Deadline wird bis zu einem
    /// Ereignis (gegebenenfalls explizitem Eingabeende) gewartet. Virtuelle
    /// Hosts steuern den Zeitfortschritt selbst; keine reale Rückfalluhr.
    fn warten(&mut self, deadline_ms: Option<u64>) -> Option<Ereignis>;

    /// Monotone Zeit in Millisekunden seit Programmstart.
    ///
    /// Die Fälligkeit der Zeit-Traps rechnet die Laufzeit daraus aus; sie
    /// greift dafür **nicht** selbst auf die Uhr zu (design.md, D3). Nur so
    /// kann ein Host ohne Terminal die Zeit stellen und ein zeitgesteuertes
    /// Programm ohne Wartezeit und reproduzierbar prüfen. Von der Ortszeit
    /// hinter `TIME$`/`TIMER` ist das unabhängig — hier zählt Dauer, nicht
    /// Uhrzeit.
    fn jetzt_ms(&mut self) -> u64;
}

/// Host ohne Terminal: hält den Bildschirmzustand fest und speist
/// vorbereitete Ereignisse ein. Grundlage des Korpus-Harness.
#[derive(Default)]
pub struct CaptureHost {
    /// Zuletzt angezeigter Bildschirm.
    pub screen: Option<TextScreen>,
    /// Wie oft angezeigt wurde (Nachweis der Zustellpunkte).
    pub presents: usize,
    ereignisse: VecDeque<Ereignis>,
    /// Ereignisse, die erst nach der n-ten Anzeige eingereiht werden —
    /// so lässt sich eine Größenänderung mitten im Lauf nachstellen.
    verzoegert: Vec<(usize, Ereignis)>,
    /// Virtuelle Uhr in Millisekunden. Sie läuft nur, wenn der Test sie
    /// stellt — von allein vergeht hier keine Zeit.
    uhr_ms: u64,
}

impl CaptureHost {
    /// Host mit vorbereiteten Eingabezeilen: jede Zeile wird zu ihren
    /// Zeichen plus Enter.
    pub fn with_input(lines: &[&str]) -> Self {
        let mut h = CaptureHost::default();
        for l in lines {
            h.tippe(l);
        }
        h
    }

    /// Text als Folge von Tastendrücken anhängen, abgeschlossen mit Enter.
    pub fn tippe(&mut self, text: &str) {
        for c in text.chars() {
            self.ereignisse.push_back(Ereignis::Taste(c.to_string(), 0));
        }
        self.ereignisse
            .push_back(Ereignis::Taste(taste::ENTER.to_string(), 0));
    }

    /// Tastendruck mit Umschaltzustand einreihen (Strg+A, Alt+F1 …).
    pub fn taste_mit(&mut self, s: &str, shift: u8) {
        self.ereignisse
            .push_back(Ereignis::Taste(s.to_string(), shift));
    }

    /// Beliebiges Ereignis einreihen (Größenänderung, Abbruch, Sondertaste).
    pub fn ereignis(&mut self, e: Ereignis) {
        self.ereignisse.push_back(e);
    }

    /// Ereignis erst nach der `nach`-ten Anzeige einreihen.
    pub fn ereignis_nach(&mut self, nach: usize, e: Ereignis) {
        self.verzoegert.push((nach, e));
    }

    /// Virtuelle Uhr vorrücken.
    pub fn uhr_vorruecken(&mut self, ms: u64) {
        self.uhr_ms += ms;
    }

    /// Virtuelle Uhr auf einen Wert setzen.
    pub fn uhr_setzen(&mut self, ms: u64) {
        self.uhr_ms = ms;
    }
}

impl Host for CaptureHost {
    fn present(&mut self, screen: &TextScreen) {
        self.screen = Some(screen.clone());
        self.presents += 1;
        let faellig = self.presents;
        let mut i = 0;
        while i < self.verzoegert.len() {
            if self.verzoegert[i].0 <= faellig {
                let (_, e) = self.verzoegert.remove(i);
                self.ereignisse.push_back(e);
            } else {
                i += 1;
            }
        }
    }

    fn next_event(&mut self, _blockierend: bool) -> Option<Ereignis> {
        self.ereignisse.pop_front()
    }

    fn warten(&mut self, deadline_ms: Option<u64>) -> Option<Ereignis> {
        if let Some(event) = self.next_event(false) {
            return Some(event);
        }
        match deadline_ms {
            Some(deadline) => {
                self.uhr_ms = self.uhr_ms.max(deadline);
                None
            }
            None => Some(Ereignis::Ende),
        }
    }

    fn jetzt_ms(&mut self) -> u64 {
        self.uhr_ms
    }
}

/// Shared child command, inheriting the foreground terminal and stdio.
pub fn shell_command(command: &str) -> std::process::Command {
    let mut child = std::process::Command::new(if cfg!(windows) { "cmd" } else { "sh" });
    if !command.is_empty() {
        child.args([if cfg!(windows) { "/C" } else { "-c" }, command]);
    }
    child
}

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

    #[test]
    fn capture_host_haelt_zeichen_und_attribut() {
        let mut h = CaptureHost::default();
        let mut s = TextScreen::new();
        s.locate(5, 10).unwrap();
        s.set_color(14, 1);
        s.print("x");
        h.present(&s);
        let gehalten = h.screen.as_ref().unwrap();
        let z = gehalten.cell(5, 10);
        assert_eq!(z.ch, 'x');
        assert_eq!((z.fg, z.bg), (14, 1));
        assert_eq!(h.presents, 1);
    }

    #[test]
    fn verzoegertes_ereignis_kommt_nach_der_anzeige() {
        let mut h = CaptureHost::default();
        h.ereignis_nach(
            2,
            Ereignis::Groesse {
                cols: 120,
                rows: 40,
            },
        );
        let s = TextScreen::new();
        h.present(&s);
        assert_eq!(h.next_event(false), None);
        h.present(&s);
        assert_eq!(
            h.next_event(false),
            Some(Ereignis::Groesse {
                cols: 120,
                rows: 40
            })
        );
    }

    #[test]
    fn groessenaenderung_und_abbruch_als_ereignis() {
        let mut h = CaptureHost::default();
        h.ereignis(Ereignis::Groesse {
            cols: 120,
            rows: 40,
        });
        h.ereignis(Ereignis::Abbruch);
        assert_eq!(
            h.next_event(true),
            Some(Ereignis::Groesse {
                cols: 120,
                rows: 40
            })
        );
        assert_eq!(h.next_event(true), Some(Ereignis::Abbruch));
    }

    #[test]
    fn maus_und_tasten_behalten_ihre_reihenfolge() {
        let mut h = CaptureHost::default();
        let klick = Ereignis::Maus(MausEreignis {
            art: MausArt::Druck,
            taste: 1,
            shift: 0,
            zeile: 3,
            spalte: 7,
        });
        h.ereignis(Ereignis::Taste("a".into(), 0));
        h.ereignis(klick.clone());
        h.ereignis(Ereignis::Taste("b".into(), 0));
        assert_eq!(h.next_event(true), Some(Ereignis::Taste("a".into(), 0)));
        assert_eq!(h.next_event(true), Some(klick));
        assert_eq!(h.next_event(true), Some(Ereignis::Taste("b".into(), 0)));
    }

    #[test]
    fn virtuelle_uhr_laeuft_nur_wenn_gestellt() {
        let mut h = CaptureHost::default();
        assert_eq!(h.jetzt_ms(), 0);
        h.uhr_vorruecken(250);
        assert_eq!(h.jetzt_ms(), 250);
        h.uhr_vorruecken(250);
        assert_eq!(h.jetzt_ms(), 500);
        h.uhr_setzen(42);
        assert_eq!(h.jetzt_ms(), 42);
        // Ohne Zutun vergeht keine Zeit — das traegt die Reproduzierbarkeit.
        assert_eq!(h.jetzt_ms(), 42);
    }

    #[test]
    fn vorbereitete_zeile_wird_zu_tasten() {
        let mut h = CaptureHost::with_input(&["ab"]);
        assert_eq!(h.next_event(true), Some(Ereignis::Taste("a".into(), 0)));
        assert_eq!(h.next_event(true), Some(Ereignis::Taste("b".into(), 0)));
        assert_eq!(
            h.next_event(true),
            Some(Ereignis::Taste(taste::ENTER.into(), 0))
        );
        assert_eq!(h.next_event(false), None);
    }
}
