use crate::abend::{Abend, AbendCode, Signal};
use crate::fixed::{pow10, zoned_digits};
use crate::storage::{Kind, Loc, Val};
use crate::store::{self, ProgramFacts};
use crate::vocab::{Pos, SignClause, SignPosition};
use numeric::{Dialect, DispSign, Trunc};
use std::io::Write;
use zarch::decimal;
use zarch::ebcdic::CodePage;
type R<T> = Result<T, Abend>;
pub fn place(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc, pos: Pos, upon_console: bool) -> R<String> {
let separate = facts.options().dispsign == DispSign::Sep;
Ok(match loc.kind {
Kind::National => national(facts.page(), store::bytes(mem, loc), upon_console),
Kind::Dbcs { .. } => facts.page().decode_dbcs(store::bytes(mem, loc)),
Kind::Packed { digits, signed, .. } | Kind::Binary { digits, signed, .. } if facts.options().dialect == Dialect::Gnucobol => {
let Val::Num(f) = store::read_stored(facts, mem, loc, pos)? else { unreachable!() };
let shown = match loc.kind {
Kind::Binary { .. } => zoned_digits(f.magnitude.to_u128().unwrap_or(0), gnucobol_binary_width(loc.len), decimal::UNSIGNED),
_ => zoned_digits(f.magnitude.div_rem(pow10(digits)).1.to_u128().unwrap_or(0), digits as usize, decimal::UNSIGNED),
};
facts.page().decode(&if signed { sign_first(f.negative, shown) } else { shown })
}
Kind::Packed { digits, signed, .. } | Kind::Binary { digits, signed, .. } => {
let Val::Num(f) = store::read_stored(facts, mem, loc, pos)? else { unreachable!() };
let zone = if signed && !separate && f.negative { decimal::MINUS } else { decimal::UNSIGNED };
let whole = match loc.kind {
Kind::Binary { native, .. } => native || facts.options().trunc == Trunc::Bin,
_ => false,
};
let shown = if whole {
let width = match loc.len {
2 => 5,
4 => 10,
_ if signed => 19,
_ => 20,
};
zoned_digits(f.magnitude.to_u128().unwrap_or(0), width, zone)
} else {
zoned_digits(f.magnitude.div_rem(pow10(digits)).1.to_u128().unwrap_or(0), digits as usize, zone)
};
facts.page().decode(&if signed && separate { sign_first(f.negative, shown) } else { shown })
}
Kind::Zoned { signed: true, sign, .. } if separate && !sign.is_some_and(|s| s.separate) => {
let mut shown = store::bytes(mem, loc).to_vec();
let at = if sign == Some(SignClause { position: SignPosition::Leading, separate: false }) { 0 } else { shown.len() - 1 };
let negative = matches!(shown[at] >> 4, 0xB | 0xD);
shown[at] |= 0xF0;
facts.page().decode(&sign_first(negative, shown))
}
Kind::Float(_) => return Err(Abend::ironwork("DISPLAY of a floating-point item is not supported yet", pos)),
Kind::Pointer | Kind::Index | Kind::ObjectReference | Kind::ProgramPointer => {
return Err(Abend::ironwork("DISPLAY of a pointer, index or object reference is not supported", pos));
}
_ => facts.page().decode(store::bytes(mem, loc)),
})
}
pub const fn gnucobol_binary_width(len: usize) -> usize {
match len {
2 => 5,
4 => 10,
_ => 20,
}
}
fn sign_first(negative: bool, digits: Vec<u8>) -> Vec<u8> {
let mut shown = vec![if negative { 0x60 } else { 0x4E }];
shown.extend(digits);
shown
}
pub fn number(written: &str, facts: &dyn ProgramFacts) -> String {
literal(written, facts.decimal_point(), facts.options().dialect)
}
pub fn literal(written: &str, decimal_point: char, dialect: Dialect) -> String {
match dialect {
Dialect::Ibm => written.replace('.', &decimal_point.to_string()),
Dialect::Gnucobol => written.replace('.', ""),
}
}
pub fn value(facts: &dyn ProgramFacts, val: Val, pos: Pos, upon_console: bool) -> R<String> {
Ok(match val {
Val::Bytes(b) | Val::All(b) => facts.page().decode(&b),
Val::National(b) | Val::AllNational(b) => national(facts.page(), &b, upon_console),
Val::Dbcs(b) => facts.page().decode_dbcs(&b),
Val::Fig(f) => facts.page().decode_byte(facts.figurative(f)).to_string(),
Val::Num(f) => facts.page().decode(&zoned_digits(f.magnitude.to_u128().unwrap_or(0), f.places.total() as usize, decimal::UNSIGNED)),
Val::Float(_) => return Err(Abend::ironwork("DISPLAY of a floating-point value is not supported yet", pos)),
Val::Address(_) => return Err(Abend::ironwork("DISPLAY of a pointer is not supported", pos)),
})
}
pub fn write(out: &mut dyn Write, text: &str, no_advancing: bool, pos: Pos) -> R<()> {
let result = if no_advancing { write!(out, "{text}") } else { writeln!(out, "{text}") };
result.map_err(|e| match e.kind() {
std::io::ErrorKind::BrokenPipe => Abend { code: AbendCode::Signal(Signal::ClosedOutput), message: "standard output closed".into(), pos, file: None },
_ => Abend::ironwork(format!("DISPLAY: {e}"), pos),
})
}
pub fn national(page: &CodePage, units: &[u8], upon_console: bool) -> String {
if upon_console { page.decode(&to_page(page, units)) } else { page.decode(units) }
}
pub fn to_page(page: &CodePage, units: &[u8]) -> Vec<u8> {
let (mut out, mut run) = (Vec::with_capacity(units.len()), String::new());
for c in utf16_text(units).chars() {
if page.encode(c.encode_utf8(&mut [0; 4])).is_ok() {
run.push(c);
} else {
out.extend(page.encode(&std::mem::take(&mut run)).unwrap_or_default());
out.push(SUBSTITUTE);
}
}
out.extend(page.encode(&run).unwrap_or_default());
out
}
const SUBSTITUTE: u8 = 0x3F;
pub fn utf16_text(bytes: &[u8]) -> String {
let units: Vec<u16> = bytes.chunks(2).map(|c| u16::from_be_bytes([c[0], *c.get(1).unwrap_or(&0)])).collect();
String::from_utf16_lossy(&units)
}