use crate::Capability;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CellKind {
Empty,
PrimaryFull,
SecondaryFull,
PrimaryBoundary,
SecondaryBoundary,
DegradedOverlap,
OverflowFull,
OverflowInnerBoundary,
OverflowOuterBoundary,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cell {
pub kind: CellKind,
pub sub_fill: u8,
}
pub fn classify(width: usize, p1: f64, p2: f64, cap: Capability) -> Vec<Cell> {
let n = cap.sub_positions();
let total = (width as u32).saturating_mul(n);
let p1s = (p1 * total as f64).round() as u32;
let p2s = (p2 * total as f64).round() as u32;
let mut out = Vec::with_capacity(width);
for i in 0..width as u32 {
let s = i * n;
let e = s + n;
let p1_in = p1s > s && p1s < e;
let p2_in = p2s > s && p2s < e;
let kind = if p1_in && p2_in && p1s != p2s {
CellKind::DegradedOverlap
} else if p1_in {
CellKind::PrimaryBoundary
} else if p2_in {
CellKind::SecondaryBoundary
} else if e <= p1s {
CellKind::PrimaryFull
} else if e <= p2s {
CellKind::SecondaryFull
} else {
CellKind::Empty
};
let sub_fill = if p1_in { (p1s - s) as u8 } else if p2_in { (p2s - s) as u8 } else { 0 };
out.push(Cell { kind, sub_fill });
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn width_zero_returns_empty() {
let v = classify(0, 0.5, 0.7, Capability::EighthBlock);
assert!(v.is_empty());
}
#[test]
fn full_progress_all_primary_full() {
let v = classify(5, 1.0, 1.0, Capability::EighthBlock);
assert_eq!(v.len(), 5);
assert!(v.iter().all(|c| c.kind == CellKind::PrimaryFull));
}
#[test]
fn zero_progress_all_empty() {
let v = classify(5, 0.0, 0.0, Capability::EighthBlock);
assert!(v.iter().all(|c| c.kind == CellKind::Empty));
}
#[test]
fn boundaries_in_distinct_cells_13_at_8ths() {
let v = classify(13, 0.33, 0.67, Capability::EighthBlock);
for i in 0..4 { assert_eq!(v[i].kind, CellKind::PrimaryFull, "cell {i}"); }
assert_eq!(v[4].kind, CellKind::PrimaryBoundary);
assert_eq!(v[4].sub_fill, 2);
for i in 5..8 { assert_eq!(v[i].kind, CellKind::SecondaryFull, "cell {i}"); }
assert_eq!(v[8].kind, CellKind::SecondaryBoundary);
assert_eq!(v[8].sub_fill, 6);
for i in 9..13 { assert_eq!(v[i].kind, CellKind::Empty, "cell {i}"); }
}
#[test]
fn same_cell_overlap_triggers_degrade() {
let v = classify(13, 0.12, 0.13, Capability::EighthBlock);
assert_eq!(v[1].kind, CellKind::DegradedOverlap);
assert_eq!(v[1].sub_fill, 4);
}
#[test]
fn p1_equals_p2_no_secondary_band() {
let v = classify(8, 0.4, 0.4, Capability::EighthBlock);
assert_eq!(v[3].kind, CellKind::PrimaryBoundary);
assert!(v[4..].iter().all(|c| c.kind == CellKind::Empty));
}
#[test]
fn boundary_exactly_on_cell_edge_falls_into_full() {
let v = classify(4, 0.25, 0.25, Capability::EighthBlock);
assert_eq!(v[0].kind, CellKind::PrimaryFull);
assert_eq!(v[1].kind, CellKind::Empty);
assert!(!v.iter().any(|c| c.kind == CellKind::PrimaryBoundary));
}
}
#[cfg(test)]
mod prop {
use super::*;
use proptest::prelude::*;
fn any_cap() -> impl Strategy<Value = Capability> {
prop_oneof![
Just(Capability::Ascii),
Just(Capability::EighthBlock),
]
}
proptest! {
#[test]
fn length_matches_width(
width in 1usize..=200,
p1 in 0.0f64..=1.0,
p2 in 0.0f64..=1.0,
cap in any_cap(),
) {
let (lo, hi) = (p1.min(p2), p1.max(p2));
let cells = classify(width, lo, hi, cap);
prop_assert_eq!(cells.len(), width);
}
#[test]
fn sub_fill_within_bounds(
width in 1usize..=200,
p1 in 0.0f64..=1.0,
p2 in 0.0f64..=1.0,
cap in any_cap(),
) {
let (lo, hi) = (p1.min(p2), p1.max(p2));
let n = cap.sub_positions() as u8;
for c in classify(width, lo, hi, cap) {
prop_assert!(c.sub_fill < n.max(1));
}
}
}
}