use alloc::vec;
use alloc::vec::Vec;
use crate::error::WriteError;
use crate::format::locale_key;
use crate::number::{OTHER, TemplatePart};
use crate::unit::{B_NAME, B_PER, B_SAME, F_NAMES, Width};
use crate::writer::number::{forms, str8, template};
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct UnitWidthSpec<'s> {
pub name: Option<&'s str>,
pub per_unit: Option<Vec<TemplatePart<'s>>>,
pub patterns: Vec<(u8, Vec<TemplatePart<'s>>)>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct UnitSpec<'s> {
pub id: &'s str,
pub widths: Vec<UnitWidthSpec<'s>>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct UnitsSpec<'s> {
pub widths: Vec<Width>,
pub names: bool,
pub per: Vec<Vec<TemplatePart<'s>>>,
pub units: Vec<UnitSpec<'s>>,
}
const ERR: WriteError = WriteError::LocaleEntry(locale_key::UNIT_DATA);
fn block(b: &UnitWidthSpec<'_>, names: bool) -> Result<Vec<u8>, WriteError> {
let other = b.patterns.iter().find(|(c, _)| *c == OTHER).map(|(_, p)| p);
if !b.patterns.is_empty() && other.is_none() {
return Err(ERR);
}
let name = b.name.filter(|_| names);
let mut head = 0u8;
if name.is_some() {
head |= B_NAME;
}
if b.per_unit.is_some() {
head |= B_PER;
}
let mut out = vec![head];
if let Some(n) = name {
str8(n, &mut out, &ERR)?;
}
if let Some(p) = &b.per_unit {
template(p, &mut out, &ERR)?;
}
let kept: Vec<(u8, &[TemplatePart<'_>])> = b
.patterns
.iter()
.filter(|(c, p)| *c == OTHER || Some(p) != other)
.map(|(c, p)| (*c, p.as_slice()))
.collect();
forms(&kept, &mut out, &ERR, |p, out| template(p, out, &ERR))?;
Ok(out)
}
pub fn units(spec: &UnitsSpec<'_>) -> Result<Vec<u8>, WriteError> {
let mut widths = spec.widths.clone();
widths.sort_unstable();
widths.dedup();
if widths.len() != spec.widths.len() || spec.per.len() != widths.len() {
return Err(ERR);
}
let mut flags = widths.iter().fold(0u8, |f, w| f | w.bit());
if spec.names {
flags |= F_NAMES;
}
let mut out = vec![flags];
for w in &widths {
let at = spec.widths.iter().position(|x| x == w).ok_or(ERR)?;
template(spec.per.get(at).ok_or(ERR)?, &mut out, &ERR)?;
}
let mut list: Vec<&UnitSpec<'_>> = spec.units.iter().collect();
list.sort_by(|a, b| a.id.as_bytes().cmp(b.id.as_bytes()));
if list.windows(2).any(|w| matches!(w, [a, b] if a.id == b.id)) {
return Err(ERR);
}
out.extend_from_slice(&u16::try_from(list.len()).map_err(|_| ERR)?.to_le_bytes());
let mut index = Vec::with_capacity(list.len() * 4);
let mut records = Vec::new();
for u in list {
if u.id.is_empty() || u.widths.len() != widths.len() {
return Err(ERR);
}
index.extend_from_slice(&u32::try_from(records.len()).map_err(|_| ERR)?.to_le_bytes());
str8(u.id, &mut records, &ERR)?;
let mut prev: Option<Vec<u8>> = None;
for w in &widths {
let at = spec.widths.iter().position(|x| x == w).ok_or(ERR)?;
let b = block(u.widths.get(at).ok_or(ERR)?, spec.names)?;
if prev.as_ref() == Some(&b) {
records.push(B_SAME);
} else {
records.extend_from_slice(&b);
}
prev = Some(b);
}
}
out.extend_from_slice(&index);
out.extend_from_slice(&records);
Ok(out)
}