use super::{BarState, PostalVariant};
use crate::error::{Error, Result};
const ALPHABET: &[u8; 36] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const PATTERNS: [[bool; 4]; 6] = [
[false, false, true, true], [false, true, false, true], [false, true, true, false], [true, false, false, true], [true, false, true, false], [true, true, false, false], ];
fn grid_values(index: u8) -> (u8, u8) {
(index / 6 + 1, index % 6 + 1)
}
fn char_index(byte: u8) -> Option<u8> {
ALPHABET.iter().position(|&c| c == byte).map(|p| p as u8)
}
fn char_to_bars(index: u8) -> [BarState; 4] {
let (top, bottom) = grid_values(index);
let asc = PATTERNS[(top - 1) as usize];
let desc = PATTERNS[(bottom - 1) as usize];
core::array::from_fn(|i| BarState::from_parts(asc[i], desc[i]))
}
fn pattern_value(pattern: [bool; 4]) -> Option<u8> {
PATTERNS
.iter()
.position(|&p| p == pattern)
.map(|p| p as u8 + 1)
}
fn bars_to_char(chunk: &[BarState]) -> Option<u8> {
let asc: [bool; 4] = core::array::from_fn(|i| chunk[i].has_ascender());
let desc: [bool; 4] = core::array::from_fn(|i| chunk[i].has_descender());
let top = pattern_value(asc)?;
let bottom = pattern_value(desc)?;
Some((top - 1) * 6 + (bottom - 1))
}
fn checksum_value(sum: u32) -> u8 {
let v = (sum % 6) as u8;
if v == 0 { 6 } else { v }
}
fn checksum_index(indices: &[u8]) -> u8 {
let mut top_sum = 0u32;
let mut bottom_sum = 0u32;
for &i in indices {
let (t, b) = grid_values(i);
top_sum += t as u32;
bottom_sum += b as u32;
}
(checksum_value(top_sum) - 1) * 6 + (checksum_value(bottom_sum) - 1)
}
pub(super) fn validate(data: &[u8]) -> Result<()> {
if data.is_empty() {
return Err(Error::invalid_data("RM4SCC/KIX data must not be empty"));
}
if data.iter().any(|&b| char_index(b).is_none()) {
return Err(Error::invalid_data(
"RM4SCC/KIX data must be 0-9 or A-Z (uppercase)",
));
}
Ok(())
}
pub(super) fn encode(kix: bool, data: &[u8]) -> Result<Vec<BarState>> {
validate(data)?;
let indices: Vec<u8> = data.iter().map(|&b| char_index(b).unwrap()).collect();
let mut bars = Vec::new();
if !kix {
bars.push(BarState::Ascender); }
for &i in &indices {
bars.extend_from_slice(&char_to_bars(i));
}
if !kix {
bars.extend_from_slice(&char_to_bars(checksum_index(&indices)));
bars.push(BarState::Full); }
Ok(bars)
}
pub(super) fn decode_rm4scc(bars: &[BarState]) -> Result<(PostalVariant, Vec<u8>)> {
let n = bars.len();
if n < 6 || !(n - 2).is_multiple_of(4) {
return Err(Error::undecodable("RM4SCC bar count invalid"));
}
if bars[0] != BarState::Ascender {
return Err(Error::undecodable("RM4SCC start bar must be an ascender"));
}
if bars[n - 1] != BarState::Full {
return Err(Error::undecodable("RM4SCC stop bar must be a full bar"));
}
let body = &bars[1..n - 1];
let indices = decode_groups(body)?;
let (&check, data) = indices
.split_last()
.ok_or_else(|| Error::undecodable("RM4SCC has no checksum character"))?;
if checksum_index(data) != check {
return Err(Error::undecodable("RM4SCC checksum mismatch"));
}
Ok((PostalVariant::RoyalMail, indices_to_ascii(data)))
}
pub(super) fn decode_kix(bars: &[BarState]) -> Result<(PostalVariant, Vec<u8>)> {
if bars.is_empty() || !bars.len().is_multiple_of(4) {
return Err(Error::undecodable("KIX bar count invalid"));
}
let indices = decode_groups(bars)?;
Ok((PostalVariant::KixCode, indices_to_ascii(&indices)))
}
fn decode_groups(body: &[BarState]) -> Result<Vec<u8>> {
body.chunks_exact(4)
.map(|chunk| {
bars_to_char(chunk).ok_or_else(|| Error::undecodable("invalid RM4SCC character"))
})
.collect()
}
fn indices_to_ascii(indices: &[u8]) -> Vec<u8> {
indices.iter().map(|&i| ALPHABET[i as usize]).collect()
}