use crate::record01::View;
use anyhow::{bail, Result};
pub const LEN: usize = 0x400;
const LEVELS: usize = 16;
const SLOTS: u32 = 32;
const MAX_ENTRIES: usize = 0xE7;
const GRAY: u32 = 14;
const FIELD_TABLE_16SEG_GRAY14: [(u32, u32); 13] = [
(1, 1 << 1),
(1, 1 << 2),
(1, 1 << 6),
(1, 1 << 7),
(1, 1 << 9),
(1, 1 << 10),
(1, 1 << 14),
(1, 1 << 15),
(2, (1 << 5) | (1 << 13)),
(2, (1 << 3) | (1 << 11)),
(4, 0x1111),
(8, 0x5555),
(8, 0xAAAA),
];
struct FieldTable {
id: [u32; LEVELS],
enable: [u32; LEVELS],
value: [[i32; SLOTS as usize]; LEVELS],
}
pub fn body(rec: View<'_>, card_scan_len: u16) -> Result<[u8; LEN]> {
let n_seg = rec.segments();
let style = rec.hr_style();
if style != 0 || n_seg != 16 {
bail!(
"scan-table solver: style {style}, {n_seg} segments is not a transcribed case \
(style 0 / 16 segments)"
);
}
if rec.hr_scan_style() != 0 {
bail!("scan-table solver: high-refresh scan style {} not transcribed", rec.hr_scan_style());
}
let line_time = (u32::from(card_scan_len) * u32::from(rec.serial_clock()) * 8) as f32;
let mut ft = init_field_table(line_time, rec.min_oe())?;
fill_bit_times(&mut ft, n_seg, rec.min_oe());
Ok(render(&ft, n_seg, rec.scan()))
}
fn init_field_table(line_time: f32, min_oe: f32) -> Result<FieldTable> {
let mut k = 16u32;
let (x0, mut x1) = (line_time * 1.2, min_oe);
for e in 0..GRAY {
if x1 > x0 {
k = e;
break;
}
x1 += x1;
}
let hi = k + 3;
let c = GRAY - 1; let top = if hi > c { c.max(1) } else { hi };
if top != GRAY - 1 {
bail!("scan-table solver: two-level fill path not transcribed");
}
let mut ft = FieldTable {
id: [0; LEVELS],
enable: [0; LEVELS],
value: [[0; SLOTS as usize]; LEVELS],
};
for (level, &(seg, bits)) in FIELD_TABLE_16SEG_GRAY14.iter().enumerate() {
ft.id[level] = seg;
ft.enable[level] = bits;
}
ft.id[top as usize] = 16;
ft.enable[top as usize] = 0xFFFF;
Ok(ft)
}
fn snap8(x: f32) -> f32 {
let t = x * 0.125;
let t = t + 0.499_999_97_f32.copysign(t);
t.trunc() * 8.0
}
fn fill_bit_times(ft: &mut FieldTable, n_seg: u32, min_oe: f32) {
for (id, en) in ft.id.iter().zip(&mut ft.enable) {
if *id == n_seg {
*en = 0xFFFF_0000;
}
}
for level in (1..GRAY).rev() {
let l = level as usize;
let mut seg = ft.id[l];
let t = ((1u64 << level) as f64 * f64::from(min_oe) / f64::from(seg)) as f32;
if seg >= n_seg {
let q = seg / n_seg;
seg %= n_seg;
let mut a = 0.0f32;
for j in n_seg..2 * n_seg {
a += q as f32 * t;
let r = snap8(a);
ft.value[l][j as usize] = r as i32;
a -= r;
}
}
if seg > 0 {
let mut a = 0.0f32;
for j in 0..n_seg {
if ft.enable[l] & (1 << j) == 0 {
continue;
}
a += t;
let r = snap8(a);
ft.value[l][j as usize] = r as i32;
a -= r;
}
}
}
}
fn render(ft: &FieldTable, n_seg: u32, scan_mode: u8) -> [u8; LEN] {
let mut buckets: Vec<Vec<(u8, i32)>> = vec![Vec::new(); n_seg as usize];
for level in (0..LEVELS).rev() {
for b in 0..SLOTS {
if ft.enable[level] & (1 << b) != 0 {
let v = ft.value[level][b as usize];
let v8 = if v < 0 { (v + 7) >> 3 } else { v >> 3 };
buckets[(b % n_seg) as usize].push((level as u8, v8));
}
}
}
let mut st = [0u8; LEN];
st[0x39F] = n_seg as u8;
let mut pos = 0usize;
for (i, bucket) in buckets.iter().enumerate() {
let start = pos;
for &(level, v) in bucket {
if pos >= MAX_ENTRIES {
continue;
}
st[pos * 4] = level;
st[pos * 4 + 1..pos * 4 + 4].copy_from_slice(&v.to_be_bytes()[1..4]);
pos += 1;
}
st[0x3C0 + 2 * i] = start as u8;
st[0x3C1 + 2 * i] = (pos - 1) as u8;
}
st[0x39E] = scan_mode;
for i in 0..scan_mode {
st[0x3A0 + usize::from(i)] = i;
}
st
}