use crate::bytes::{Cur, u32_at};
use crate::format::locale_key;
use crate::number::{Forms, Pattern, Template, str8};
use crate::reader::Catalog;
pub(crate) const F_NARROW: u8 = 0x01;
pub(crate) const F_NAMES: u8 = 0x02;
pub(crate) const H_DIGITS: u8 = 0x0f;
pub(crate) const H_ROUNDING: u8 = 0x10;
pub(crate) const H_SYMBOL: u8 = 0x20;
pub(crate) const H_NARROW: u8 = 0x40;
pub(crate) const H_NAME: u8 = 0x80;
pub(crate) const E_PATTERN: u8 = 0x10;
pub(crate) const E_DECIMAL: u8 = 0x20;
pub(crate) const E_GROUP: u8 = 0x40;
const INDEX_STRIDE: usize = 7;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Edges {
pub first: bool,
pub last: bool,
}
impl Edges {
const fn from_bits(b: u8) -> Edges {
Edges {
first: b & 1 != 0,
last: b & 2 != 0,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Currencies<'a> {
flags: u8,
default_digits: u8,
name_patterns: Forms<'a>,
index: &'a [u8],
records: &'a [u8],
}
impl<'a> Currencies<'a> {
pub fn parse(entry: &'a [u8]) -> Option<Currencies<'a>> {
let mut c = Cur::new(entry, 0);
let flags = c.u8()?;
if flags & !(F_NARROW | F_NAMES) != 0 {
return None;
}
let default_digits = c.u8()?;
let name_patterns = if flags & F_NAMES != 0 {
Forms::read(&mut c, true)?
} else {
Forms::EMPTY
};
let lo = c.u8()?;
let hi = c.u8()?;
let n = usize::from(u16::from_le_bytes([lo, hi]));
let index = c.take(n.checked_mul(INDEX_STRIDE)?)?;
let records = entry.get(c.pos()..)?;
Some(Currencies {
flags,
default_digits,
name_patterns,
index,
records,
})
}
pub fn of(catalog: &'a Catalog) -> Option<Currencies<'a>> {
Currencies::parse(catalog.locale_entry(locale_key::CURRENCY_DATA)?)
}
pub const fn default_digits(&self) -> u8 {
self.default_digits
}
pub const fn narrow_carried(&self) -> bool {
self.flags & F_NARROW != 0
}
pub const fn names_carried(&self) -> bool {
self.flags & F_NAMES != 0
}
pub const fn len(&self) -> usize {
self.index.len() / INDEX_STRIDE
}
pub const fn is_empty(&self) -> bool {
self.index.is_empty()
}
pub fn name_pattern(&self, category: u8) -> Option<Template<'a>> {
self.name_patterns.get(category).map(Template::trusted)
}
fn code_at(&self, i: usize) -> Option<[u8; 3]> {
let at = i.checked_mul(INDEX_STRIDE)?;
match self.index.get(at..at.checked_add(3)?)? {
&[a, b, c] => Some([a, b, c]),
_ => None,
}
}
pub fn codes(&self) -> impl Iterator<Item = [u8; 3]> + '_ {
(0..self.len()).filter_map(|i| self.code_at(i))
}
pub fn get(&self, code: [u8; 3]) -> Option<Currency<'a>> {
let (mut lo, mut hi) = (0usize, self.len());
while lo < hi {
let mid = lo + (hi - lo) / 2;
let here = self.code_at(mid)?;
match here.cmp(&code) {
core::cmp::Ordering::Less => lo = mid + 1,
core::cmp::Ordering::Greater => hi = mid,
core::cmp::Ordering::Equal => {
let start = mid.checked_mul(INDEX_STRIDE)?;
let at = start.checked_add(3)?;
let off = u32_at(self.index, at)? as usize;
let text = core::str::from_utf8(self.index.get(start..at)?).ok()?;
return Currency::parse(text, self.records, off, self.flags);
}
}
}
None
}
pub fn is_valid(&self) -> bool {
let n = self.len();
let mut prev: Option<([u8; 3], usize)> = None;
for i in 0..n {
let Some(code) = self.code_at(i) else {
return false;
};
let Some(off) = i
.checked_mul(INDEX_STRIDE)
.and_then(|a| u32_at(self.index, a + 3))
else {
return false;
};
let off = off as usize;
if !code.iter().all(u8::is_ascii_uppercase)
|| prev.is_some_and(|(c, o)| code <= c || off <= o)
{
return false;
}
if let Some((_, o)) = prev
&& Currency::end(self.records, o, self.flags) != Some(off)
{
return false;
}
if i == 0 && off != 0 {
return false;
}
prev = Some((code, off));
}
match prev {
Some((_, o)) => Currency::end(self.records, o, self.flags) == Some(self.records.len()),
None => self.records.is_empty(),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Currency<'a> {
code: &'a str,
head: u8,
rounding: u32,
edges: u8,
symbol: &'a str,
narrow: Option<&'a str>,
pattern: Option<Pattern<'a>>,
decimal: Option<&'a str>,
group: Option<&'a str>,
name: Option<&'a str>,
names: Forms<'a>,
}
impl<'a> Currency<'a> {
fn read(code: &'a str, c: &mut Cur<'a>, flags: u8) -> Option<Currency<'a>> {
let head = c.u8()?;
let rounding = if head & H_ROUNDING != 0 {
c.varint()?
} else {
0
};
let edges = c.u8()?;
if edges & 0x80 != 0 {
return None;
}
let symbol = if head & H_SYMBOL != 0 { str8(c)? } else { code };
let narrow = match (flags & F_NARROW != 0, head & H_NARROW != 0) {
(true, true) => Some(str8(c)?),
(true, false) => Some(symbol),
(false, false) => None,
(false, true) => return None,
};
let pattern = if edges & E_PATTERN != 0 {
let len = c.u8()?;
Some(Pattern::parse(c.take(usize::from(len))?)?)
} else {
None
};
let decimal = if edges & E_DECIMAL != 0 {
Some(str8(c)?)
} else {
None
};
let group = if edges & E_GROUP != 0 {
Some(str8(c)?)
} else {
None
};
let (name, names) = match (flags & F_NAMES != 0, head & H_NAME != 0) {
(true, has) => {
let name = if has { str8(c)? } else { code };
(Some(name), Forms::read(c, false)?)
}
(false, false) => (None, Forms::EMPTY),
(false, true) => return None,
};
Some(Currency {
code,
head,
rounding,
edges,
symbol,
narrow,
pattern,
decimal,
group,
name,
names,
})
}
fn parse(code: &'a str, records: &'a [u8], off: usize, flags: u8) -> Option<Currency<'a>> {
let mut c = Cur::new(records, off);
Currency::read(code, &mut c, flags)
}
fn end(records: &[u8], off: usize, flags: u8) -> Option<usize> {
let mut c = Cur::new(records, off);
Currency::read("XXX", &mut c, flags)?;
Some(c.pos())
}
pub const fn code(&self) -> &'a str {
self.code
}
pub const fn fraction_digits(&self) -> u8 {
self.head & H_DIGITS
}
pub const fn rounding_increment(&self) -> u32 {
self.rounding
}
pub const fn symbol(&self) -> &'a str {
self.symbol
}
pub const fn narrow_symbol(&self) -> Option<&'a str> {
self.narrow
}
pub const fn name(&self) -> Option<&'a str> {
self.name
}
pub fn name_for(&self, category: u8) -> Option<&'a str> {
self.names.get(category).or(self.name)
}
pub const fn pattern(&self) -> Option<Pattern<'a>> {
self.pattern
}
pub const fn decimal(&self) -> Option<&'a str> {
self.decimal
}
pub const fn group(&self) -> Option<&'a str> {
self.group
}
pub const fn symbol_edges(&self) -> Edges {
Edges::from_bits(self.edges)
}
pub const fn narrow_edges(&self) -> Edges {
Edges::from_bits((self.edges >> 2) & 0x03)
}
}