//! `PRINT USING`, `LPRINT USING` und `FORMAT$`: Formatzeichenketten des
//! Vorbilds.
//!
//! Die Formatzeichenkette beschreibt Felder; jeder Wert füllt das nächste
//! Feld. Sind noch Werte übrig, wenn die Zeichenkette zu Ende ist, beginnt
//! sie von vorn. Passt ein Wert nicht in sein Feld, wird er vollständig
//! ausgegeben und mit `%` gekennzeichnet.

use crate::errors::RuntimeError;
use crate::value::{as_f64, Value};

/// Numerisches Feld.
#[derive(Debug, Clone, Default, PartialEq)]
struct NumFeld {
    /// Ziffernstellen vor dem Dezimalpunkt (ohne die Stellen aus `$$`/`**`).
    vor: usize,
    /// Ziffernstellen nach dem Dezimalpunkt.
    nach: usize,
    /// Dezimalpunkt vorhanden?
    punkt: bool,
    /// Tausendertrennung (`,` vor dem Punkt).
    tausender: bool,
    /// `+` vorangestellt bzw. nachgestellt.
    plus_vorn: bool,
    plus_hinten: bool,
    /// `-` nachgestellt (nur für negative Werte).
    minus_hinten: bool,
    /// `$$` — mitlaufendes Währungszeichen.
    waehrung: bool,
    /// `**` — führende Stellen mit `*` füllen.
    sterne: bool,
    /// `^^^^` — Exponentialform.
    exponent: bool,
}

/// Stringfeld.
#[derive(Debug, Clone, PartialEq)]
enum StrFeld {
    /// `!` — nur das erste Zeichen.
    Erstes,
    /// `\   \` — feste Länge (Anzahl Zeichen inklusive beider Schrägstriche).
    Fest(usize),
    /// `&` — der ganze String.
    Ganz,
}

#[derive(Debug, Clone, PartialEq)]
enum Teil {
    Literal(String),
    Zahl(NumFeld),
    Text(StrFeld),
}

/// Währungszeichen für `$$` (siehe `SetFormatCC`).
#[derive(Debug, Clone)]
pub struct Waehrung {
    pub zeichen: String,
}

impl Default for Waehrung {
    fn default() -> Self {
        Waehrung {
            zeichen: "$".into(),
        }
    }
}

// ---- Zerlegen der Formatzeichenkette ---------------------------------------

fn zerlege(fmt: &str) -> Vec<Teil> {
    let z: Vec<char> = fmt.chars().collect();
    let mut teile = Vec::new();
    let mut lit = String::new();
    let mut i = 0;
    let schiebe = |lit: &mut String, teile: &mut Vec<Teil>| {
        if !lit.is_empty() {
            teile.push(Teil::Literal(std::mem::take(lit)));
        }
    };

    while i < z.len() {
        match z[i] {
            // `_` macht das Folgezeichen literal.
            '_' if i + 1 < z.len() => {
                lit.push(z[i + 1]);
                i += 2;
            }
            '!' => {
                schiebe(&mut lit, &mut teile);
                teile.push(Teil::Text(StrFeld::Erstes));
                i += 1;
            }
            '&' => {
                schiebe(&mut lit, &mut teile);
                teile.push(Teil::Text(StrFeld::Ganz));
                i += 1;
            }
            // `\   \` — Leerzeichen zwischen zwei Schrägstrichen.
            '\\' => {
                let mut j = i + 1;
                while j < z.len() && z[j] == ' ' {
                    j += 1;
                }
                if j < z.len() && z[j] == '\\' {
                    schiebe(&mut lit, &mut teile);
                    teile.push(Teil::Text(StrFeld::Fest(j - i + 1)));
                    i = j + 1;
                } else {
                    lit.push('\\');
                    i += 1;
                }
            }
            '#' | '+' | '.' | '*' | '$' => {
                if let Some((feld, weiter)) = zahlfeld(&z, i) {
                    schiebe(&mut lit, &mut teile);
                    teile.push(Teil::Zahl(feld));
                    i = weiter;
                } else {
                    lit.push(z[i]);
                    i += 1;
                }
            }
            c => {
                lit.push(c);
                i += 1;
            }
        }
    }
    schiebe(&mut lit, &mut teile);
    teile
}

/// Versucht ab `start` ein numerisches Feld zu lesen.
fn zahlfeld(z: &[char], start: usize) -> Option<(NumFeld, usize)> {
    let mut f = NumFeld::default();
    let mut i = start;

    if z[i] == '+' {
        f.plus_vorn = true;
        i += 1;
    }
    // `**$`, `**`, `$$` belegen selbst Ziffernstellen.
    if i + 1 < z.len() && z[i] == '*' && z[i + 1] == '*' {
        f.sterne = true;
        f.vor += 2;
        i += 2;
        if i < z.len() && z[i] == '$' {
            f.waehrung = true;
            f.vor += 1;
            i += 1;
        }
    } else if i + 1 < z.len() && z[i] == '$' && z[i + 1] == '$' {
        f.waehrung = true;
        f.vor += 2;
        i += 2;
    }

    let ziffern_start = i;
    while i < z.len() {
        match z[i] {
            '#' => {
                f.vor += 1;
                i += 1;
            }
            ',' if i + 1 < z.len() && (z[i + 1] == '#' || z[i + 1] == '.') => {
                f.tausender = true;
                i += 1;
            }
            _ => break,
        }
    }
    // Ohne jede Ziffernstelle ist es kein Zahlfeld.
    if i == ziffern_start && !f.waehrung && !f.sterne {
        return None;
    }
    if i < z.len() && z[i] == '.' {
        f.punkt = true;
        i += 1;
        while i < z.len() && z[i] == '#' {
            f.nach += 1;
            i += 1;
        }
    }
    // Exponentialform: vier oder mehr `^`.
    let exp_start = i;
    while i < z.len() && z[i] == '^' {
        i += 1;
    }
    if i - exp_start >= 4 {
        f.exponent = true;
    } else {
        i = exp_start;
    }
    if i < z.len() && z[i] == '+' {
        f.plus_hinten = true;
        i += 1;
    } else if i < z.len() && z[i] == '-' {
        f.minus_hinten = true;
        i += 1;
    }
    Some((f, i))
}

// ---- Werte formatieren ------------------------------------------------------

fn zahl_formatieren(f: &NumFeld, wert: f64, waehrung: &Waehrung) -> String {
    let negativ = wert < 0.0 || (wert == 0.0 && wert.is_sign_negative());
    let betrag = wert.abs();

    if f.exponent {
        return exponent_formatieren(f, wert);
    }

    // Ziffern runden (kaufmännisch zur geraden Zahl wie sonst auch).
    let gerundet = runde(betrag, f.nach);
    let mut ganz = format!("{:.0}", gerundet.trunc());
    let bruch = if f.punkt {
        let b = gerundet - gerundet.trunc();
        let s = format!("{:.*}", f.nach, b);
        // "0.25" → "25"
        s.split_once('.')
            .map(|(_, r)| r.to_string())
            .unwrap_or_default()
    } else {
        String::new()
    };
    // Die Tausendertrennung fügt Zeichen hinzu, die keine Ziffernstelle
    // belegen; der Überlauf entscheidet sich an der Ziffernzahl.
    let ziffern = ganz.chars().count();
    if f.tausender {
        ganz = tausender_gruppieren(&ganz);
    }

    // Vorzeichen mit eigener Position: `+` vorn bzw. `+`/`-` hinten stehen
    // neben dem Zahlenfeld, nicht darin. Ohne Vorzeichenangabe teilt sich ein
    // `-` die Ziffernstellen.
    let (vorz_vorn, vorz_hinten, minus_im_feld) = if f.plus_vorn {
        (
            if negativ { "-" } else { "+" }.to_string(),
            String::new(),
            false,
        )
    } else if f.plus_hinten {
        (
            String::new(),
            if negativ { "-" } else { "+" }.to_string(),
            false,
        )
    } else if f.minus_hinten {
        (
            String::new(),
            if negativ { "-" } else { " " }.to_string(),
            false,
        )
    } else {
        (String::new(), String::new(), negativ)
    };

    // Inhalt der Ziffernstellen, rechtsbündig: Vorzeichen (falls im Feld),
    // Währungszeichen, Ziffern. `$$`/`**` haben ihre Stellen mitgebracht.
    let mut inhalt = String::new();
    if minus_im_feld {
        inhalt.push('-');
    }
    if f.waehrung {
        inhalt.push_str(&waehrung.zeichen);
    }
    inhalt.push_str(&ganz);

    let ueberlauf = ziffern + usize::from(minus_im_feld) + usize::from(f.waehrung) > f.vor;
    let gefuellt = if ueberlauf {
        inhalt
    } else {
        // Trennzeichen belegen keine Ziffernstelle und können die Breite
        // überschreiten, ohne Überlauf zu sein.
        let fehlt = f.vor.saturating_sub(inhalt.chars().count());
        let fueller = if f.sterne { '*' } else { ' ' };
        let mut t: String = std::iter::repeat_n(fueller, fehlt).collect();
        t.push_str(&inhalt);
        t
    };

    let mut out = String::new();
    if ueberlauf {
        out.push('%');
    }
    out.push_str(&vorz_vorn);
    out.push_str(&gefuellt);
    if f.punkt {
        out.push('.');
        out.push_str(&bruch);
    }
    out.push_str(&vorz_hinten);
    out
}

/// Kaufmännische Rundung zur geraden Zahl auf `stellen` Nachkommastellen.
fn runde(x: f64, stellen: usize) -> f64 {
    let faktor = 10f64.powi(stellen as i32);
    let skaliert = x * faktor;
    let ab = skaliert.floor();
    let rest = skaliert - ab;
    let gerundet = if (rest - 0.5).abs() < f64::EPSILON * skaliert.abs().max(1.0) {
        if (ab as i64) % 2 == 0 {
            ab
        } else {
            ab + 1.0
        }
    } else {
        skaliert.round()
    };
    gerundet / faktor
}

fn tausender_gruppieren(s: &str) -> String {
    let z: Vec<char> = s.chars().collect();
    let mut out = String::new();
    for (i, c) in z.iter().enumerate() {
        if i > 0 && (z.len() - i).is_multiple_of(3) {
            out.push(',');
        }
        out.push(*c);
    }
    out
}

fn exponent_formatieren(f: &NumFeld, wert: f64) -> String {
    let stellen = f.nach;
    let s = format!("{:.*E}", stellen, wert);
    // Rust liefert `1.5E3`; das Vorbild schreibt `1.5E+03`.
    match s.split_once('E') {
        Some((m, e)) => {
            let (vz, betrag) = match e.strip_prefix('-') {
                Some(r) => ('-', r),
                None => ('+', e.trim_start_matches('+')),
            };
            format!("{m}E{vz}{betrag:0>2}")
        }
        None => s,
    }
}

fn text_formatieren(f: &StrFeld, s: &str) -> String {
    match f {
        StrFeld::Erstes => s.chars().take(1).collect(),
        StrFeld::Ganz => s.to_string(),
        StrFeld::Fest(n) => {
            let mut t: String = s.chars().take(*n).collect();
            let fehlt = n.saturating_sub(t.chars().count());
            t.extend(std::iter::repeat_n(' ', fehlt));
            t
        }
    }
}

// ---- Einstiegspunkt ---------------------------------------------------------

/// Werte nach `fmt` formatieren. Die Zeichenkette wird so oft wiederholt,
/// wie Werte übrig sind; nach dem letzten Wert folgen noch die Literale bis
/// zum nächsten Feld.
pub fn using(fmt: &str, werte: &[Value], waehrung: &Waehrung) -> Result<String, RuntimeError> {
    let teile = zerlege(fmt);
    let hat_feld = teile
        .iter()
        .any(|t| matches!(t, Teil::Zahl(_) | Teil::Text(_)));
    if !hat_feld {
        if werte.is_empty() {
            return Ok(fmt.to_string());
        }
        // Werte ohne Feld sind ein Bedienfehler (Vorbild: Fehler 5).
        return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
    }

    let mut out = String::new();
    let mut i = 0usize; // Index im Teile-Vektor
    let mut w = 0usize; // Index im Wertevektor
                        // Schutz gegen Endlosläufe bei Formaten ohne Fortschritt.
    let mut runden = 0usize;

    while w < werte.len() {
        if i >= teile.len() {
            i = 0;
            runden += 1;
            if runden > werte.len() + 1 {
                break;
            }
        }
        match &teile[i] {
            Teil::Literal(l) => out.push_str(l),
            Teil::Zahl(f) => {
                if matches!(werte[w], Value::Str(_)) {
                    return Err(RuntimeError::TYPE_MISMATCH);
                }
                let v = as_f64(&werte[w]);
                out.push_str(&zahl_formatieren(f, v, waehrung));
                w += 1;
            }
            Teil::Text(f) => {
                let Value::Str(s) = &werte[w] else {
                    return Err(RuntimeError::TYPE_MISMATCH);
                };
                out.push_str(&text_formatieren(f, s));
                w += 1;
            }
        }
        i += 1;
    }
    // Nachlaufende Literale bis zum nächsten Feld.
    while i < teile.len() {
        match &teile[i] {
            Teil::Literal(l) => out.push_str(l),
            _ => break,
        }
        i += 1;
    }
    Ok(out)
}

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

    fn u(fmt: &str, werte: &[Value]) -> String {
        using(fmt, werte, &Waehrung::default()).unwrap()
    }
    fn z(x: f64) -> Value {
        Value::Dbl(x)
    }
    fn s(x: &str) -> Value {
        Value::Str(Rc::from(x))
    }

    #[test]
    fn numerisches_feld_mit_nachkommastellen() {
        assert_eq!(u("###.##", &[z(3.126)]), "  3.13");
        assert_eq!(u("##.##", &[z(-1.5)]), "-1.50");
    }

    #[test]
    fn feldueberlauf_bekommt_prozentzeichen() {
        let r = u("##", &[z(12345.0)]);
        assert!(r.starts_with('%'), "{r}");
        assert!(r.contains("12345"), "{r}");
    }

    #[test]
    fn format_wird_wiederverwendet() {
        assert_eq!(u("## ", &[z(1.0), z(2.0), z(3.0)]), " 1  2  3 ");
    }

    #[test]
    fn tausendertrennung() {
        assert_eq!(u("#######", &[z(1234567.0)]), "1234567");
        assert_eq!(u("##,#####", &[z(1234567.0)]), "1,234,567");
        // Mehr Ziffern als Stellen → Überlauf mit %-Präfix.
        assert!(u("##,####", &[z(1234567.0)]).starts_with('%'));
    }

    #[test]
    fn vorzeichenfuehrung() {
        assert_eq!(u("+###", &[z(12.0)]), "+ 12");
        assert_eq!(u("###-", &[z(-12.0)]), " 12-");
        assert_eq!(u("###-", &[z(12.0)]), " 12 ");
    }

    #[test]
    fn waehrung_und_sterne() {
        // `$$` bringt zwei Stellen mit, eine davon ist das Zeichen selbst.
        assert_eq!(u("$$###", &[z(12.0)]), "  $12");
        assert_eq!(u("**###", &[z(12.0)]), "***12");
    }

    #[test]
    fn stringfelder() {
        assert_eq!(u("!", &[s("abc")]), "a");
        assert_eq!(u("&", &[s("abc")]), "abc");
        assert_eq!(u("\\  \\", &[s("abcdef")]), "abcd");
        assert_eq!(u("\\  \\", &[s("ab")]), "ab  ");
    }

    #[test]
    fn literale_und_unterstrich() {
        assert_eq!(u("Wert: ##!", &[z(7.0), s("x")]), "Wert:  7x");
        assert_eq!(u("_###", &[z(7.0)]), "# 7");
    }

    #[test]
    fn exponentialform() {
        assert_eq!(u("#.##^^^^", &[z(1234.0)]), "1.23E+03");
    }

    #[test]
    fn ohne_feld_aber_mit_werten_ist_fehler_5() {
        let e = using("nur Text", &[z(1.0)], &Waehrung::default()).unwrap_err();
        assert_eq!(e, RuntimeError::ILLEGAL_FUNCTION_CALL);
    }

    #[test]
    fn ohne_feld_und_ohne_werte_bleibt_der_text() {
        assert_eq!(u("nur Text", &[]), "nur Text");
    }
}
