//! Textdarstellung von Zahlen (PRINT/STR$) und `VAL`-Parsen nach der
//! Matrix in docs/tbvm-design.md: SINGLE bis 7, DOUBLE bis 16
//! signifikante Stellen, keine führende Null vor dem Dezimalpunkt,
//! Exponentialform `E±xx` bzw. `D±xx` außerhalb des Festformat-Bereichs.

use crate::value::Value;

/// Nackte Ziffernfolge ohne Vorzeichenspalte (`1.5`, `-2.5`, `.5`, `1E+08`).
pub fn format_number(v: &Value) -> String {
    match v {
        Value::Int(x) => x.to_string(),
        Value::Lng(x) => x.to_string(),
        Value::Sng(x) => fmt_float(*x as f64, 7, 'E'),
        Value::Dbl(x) => fmt_float(*x, 16, 'D'),
        Value::Cur(c) => fmt_currency(*c),
        _ => String::new(),
    }
}

/// STR$-Stil: führendes Leerzeichen für nicht-negative Werte, `-` sonst;
/// kein nachgestelltes Leerzeichen.
pub fn format_str_fn(v: &Value) -> String {
    let s = format_number(v);
    if s.starts_with('-') {
        s
    } else {
        format!(" {s}")
    }
}

/// PRINT-Stil: wie STR$ plus nachgestelltes Leerzeichen.
pub fn format_print(v: &Value) -> String {
    let mut s = format_str_fn(v);
    s.push(' ');
    s
}

fn fmt_currency(c: i64) -> String {
    let neg = c < 0;
    let abs = c.unsigned_abs();
    let int = abs / 10_000;
    let frac = abs % 10_000;
    let mut s = if frac == 0 {
        int.to_string()
    } else {
        let f = format!("{frac:04}");
        let f = f.trim_end_matches('0');
        if int == 0 {
            format!(".{f}")
        } else {
            format!("{int}.{f}")
        }
    };
    if neg {
        s.insert(0, '-');
    }
    s
}

/// Gleitkomma mit maximal `sig` signifikanten Stellen; Exponentialform
/// (Marke `E` bzw. `D`), wenn der Dezimalexponent < −7 oder ≥ `sig` ist
/// (Schwelle: Festlegung der Matrix in docs/tbvm-design.md; Verifikation
/// gegen die Original-Hilfe ist in PLAN.md Phase 3 eingeplant).
fn fmt_float(x: f64, sig: usize, expch: char) -> String {
    if x == 0.0 {
        return "0".to_string();
    }
    if x.is_nan() {
        return "NaN".to_string();
    }
    if x.is_infinite() {
        return if x < 0.0 {
            "-1E+38".into()
        } else {
            "1E+38".into()
        };
    }
    let neg = x < 0.0;
    let ax = x.abs();
    // Mantisse/Exponent mit `sig` Stellen bestimmen.
    let e = format!("{:.*e}", sig - 1, ax); // z. B. "1.500000e0"
    let (mant, exp) = e.split_once('e').unwrap();
    let exp: i32 = exp.parse().unwrap();
    let mut digits: String = mant.chars().filter(|c| c.is_ascii_digit()).collect();
    // Rundungsüberlauf ("9.99…e5" → "10.0e5") normalisiert `format!` bereits.
    // Nachgestellte Nullen der Mantisse entfernen.
    while digits.len() > 1 && digits.ends_with('0') {
        digits.pop();
    }

    let body = if exp < -7 || exp >= sig as i32 {
        // Exponentialform: D. Mantisse "d[.rest]"
        let mut m = String::new();
        m.push(digits.as_bytes()[0] as char);
        if digits.len() > 1 {
            m.push('.');
            m.push_str(&digits[1..]);
        }
        format!(
            "{m}{expch}{}{:02}",
            if exp < 0 { '-' } else { '+' },
            exp.abs()
        )
    } else if exp >= 0 {
        let e = exp as usize;
        if (e + 1) >= digits.len() {
            // Ganzzahl, ggf. Nullen anhängen
            let mut s = digits.clone();
            s.push_str(&"0".repeat(e + 1 - digits.len()));
            s
        } else {
            format!("{}.{}", &digits[..e + 1], &digits[e + 1..])
        }
    } else {
        // 0 > exp >= -7: ".0…digits" ohne führende Null
        let zeros = (-exp - 1) as usize;
        let mut s = String::from(".");
        s.push_str(&"0".repeat(zeros));
        s.push_str(&digits);
        s
    };
    if neg {
        format!("-{body}")
    } else {
        body
    }
}

/// `VAL`: liest das führende Zahlenpräfix (Leerraum wird übersprungen;
/// `&H`/`&O`-Präfixe wie beim Vorbild), ignoriert Restzeichen.
pub fn val(s: &str) -> f64 {
    let t = s.trim_start();
    // Hex/Oktal
    if let Some(rest) = t.strip_prefix("&H").or_else(|| t.strip_prefix("&h")) {
        let hex: String = rest.chars().take_while(|c| c.is_ascii_hexdigit()).collect();
        return i64::from_str_radix(&hex, 16).unwrap_or(0) as f64;
    }
    if let Some(rest) = t.strip_prefix("&O").or_else(|| t.strip_prefix("&o")) {
        let oct: String = rest
            .chars()
            .take_while(|c| ('0'..='7').contains(c))
            .collect();
        return i64::from_str_radix(&oct, 8).unwrap_or(0) as f64;
    }
    let bytes: Vec<char> = t.chars().collect();
    let mut i = 0;
    let mut num = String::new();
    if i < bytes.len() && (bytes[i] == '+' || bytes[i] == '-') {
        num.push(bytes[i]);
        i += 1;
    }
    let mut seen_digit = false;
    let mut seen_dot = false;
    while i < bytes.len() {
        let c = bytes[i];
        if c.is_ascii_digit() {
            seen_digit = true;
            num.push(c);
            i += 1;
        } else if c == '.' && !seen_dot {
            seen_dot = true;
            num.push(c);
            i += 1;
        } else if c == ' ' || c == '\t' {
            // Vorbild: VAL ignoriert eingebetteten Leerraum
            i += 1;
        } else {
            break;
        }
    }
    // Exponent (E/D)
    if seen_digit && i < bytes.len() && matches!(bytes[i], 'e' | 'E' | 'd' | 'D') {
        let mut j = i + 1;
        let mut exp = String::new();
        if j < bytes.len() && (bytes[j] == '+' || bytes[j] == '-') {
            exp.push(bytes[j]);
            j += 1;
        }
        let mut exp_digits = false;
        while j < bytes.len() && bytes[j].is_ascii_digit() {
            exp.push(bytes[j]);
            exp_digits = true;
            j += 1;
        }
        if exp_digits {
            num.push('e');
            num.push_str(&exp);
        }
    }
    if !seen_digit {
        return 0.0;
    }
    num.parse().unwrap_or(0.0)
}

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

    #[test]
    fn ganzzahlen() {
        assert_eq!(format_number(&Value::Int(42)), "42");
        assert_eq!(format_number(&Value::Int(-7)), "-7");
        assert_eq!(format_print(&Value::Int(1)), " 1 ");
        assert_eq!(format_print(&Value::Int(-2)), "-2 ");
        assert_eq!(format_str_fn(&Value::Int(42)), " 42");
    }

    #[test]
    fn single_darstellung() {
        assert_eq!(format_number(&Value::Sng(1.5)), "1.5");
        assert_eq!(format_number(&Value::Sng(-2.5)), "-2.5");
        assert_eq!(format_number(&Value::Sng(0.5)), ".5");
        assert_eq!(format_number(&Value::Sng(3.0)), "3");
        assert_eq!(format_number(&Value::Sng(1.0 / 3.0)), ".3333333");
        assert_eq!(format_number(&Value::Sng(1e8)), "1E+08");
        assert_eq!(format_number(&Value::Sng(9_999_999.0)), "9999999");
        assert_eq!(format_number(&Value::Sng(1e7)), "1E+07");
    }

    #[test]
    fn double_darstellung() {
        assert_eq!(format_number(&Value::Dbl(1.5)), "1.5");
        assert_eq!(format_number(&Value::Dbl(1e16)), "1D+16");
        assert_eq!(
            format_number(&Value::Dbl(0.3333333333333333)),
            ".3333333333333333"
        );
    }

    #[test]
    fn currency_darstellung() {
        assert_eq!(format_number(&Value::Cur(15_000)), "1.5");
        assert_eq!(format_number(&Value::Cur(10_000)), "1");
        assert_eq!(format_number(&Value::Cur(-12_345)), "-1.2345");
        assert_eq!(format_number(&Value::Cur(2_500)), ".25");
    }

    #[test]
    fn val_parsen() {
        assert_eq!(val(" 12.5abc"), 12.5);
        assert_eq!(val("-3"), -3.0);
        assert_eq!(val("1e2"), 100.0);
        assert_eq!(val("1D2"), 100.0);
        assert_eq!(val("&HFF"), 255.0);
        assert_eq!(val("&O10"), 8.0);
        assert_eq!(val("abc"), 0.0);
        assert_eq!(val(" 1 2 3"), 123.0); // eingebetteter Leerraum
    }
}
