use alloc::string::{String, ToString};
use crate::{
std::{self, fmt},
Currency,
};
pub type ISOCode = Currency;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct BaseValue(u16);
impl BaseValue {
pub const LEN: usize = 3;
pub const fn default() -> Self {
Self(0)
}
}
impl From<BaseValue> for u16 {
fn from(b: BaseValue) -> Self {
b.0
}
}
impl From<&BaseValue> for u16 {
fn from(b: &BaseValue) -> Self {
(*b).into()
}
}
impl From<BaseValue> for f32 {
fn from(b: BaseValue) -> Self {
b.0 as f32
}
}
impl From<&BaseValue> for f32 {
fn from(b: &BaseValue) -> Self {
(*b).into()
}
}
impl From<&[u8]> for BaseValue {
fn from(b: &[u8]) -> Self {
if b.len() < Self::LEN {
Self(0)
} else {
let val = std::str::from_utf8(b[..Self::LEN].as_ref())
.unwrap_or("0")
.parse::<u16>()
.unwrap_or(0);
Self(val)
}
}
}
impl<const N: usize> From<[u8; N]> for BaseValue {
fn from(b: [u8; N]) -> Self {
b.as_ref().into()
}
}
impl<const N: usize> From<&[u8; N]> for BaseValue {
fn from(b: &[u8; N]) -> Self {
b.as_ref().into()
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Sign {
#[default]
Positive,
Negative,
}
impl Sign {
pub const LEN: usize = 1;
pub const fn default() -> Self {
Self::Positive
}
}
impl From<u8> for Sign {
fn from(b: u8) -> Self {
match b {
b'+' => Self::Positive,
b'-' => Self::Negative,
_ => Self::Positive,
}
}
}
impl From<Sign> for &'static str {
fn from(sign: Sign) -> Self {
match sign {
Sign::Negative => "-",
Sign::Positive => "+",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Exponent(u8);
impl Exponent {
pub const LEN: usize = 2;
pub const fn new() -> Self {
Self(1)
}
}
impl Default for Exponent {
fn default() -> Self {
Self::new()
}
}
impl From<u8> for Exponent {
fn from(b: u8) -> Self {
Self(b)
}
}
impl From<Exponent> for u8 {
fn from(e: Exponent) -> Self {
e.0
}
}
impl From<&Exponent> for u8 {
fn from(e: &Exponent) -> Self {
(*e).into()
}
}
impl From<Exponent> for f32 {
fn from(e: Exponent) -> Self {
e.0 as f32
}
}
impl From<&Exponent> for f32 {
fn from(e: &Exponent) -> Self {
(*e).into()
}
}
impl From<&[u8]> for Exponent {
fn from(b: &[u8]) -> Self {
if b.len() < Exponent::LEN {
Exponent(1)
} else {
let exp = std::str::from_utf8(b[..Exponent::LEN].as_ref())
.unwrap_or("1")
.parse::<u8>()
.unwrap_or(1);
Self(exp)
}
}
}
impl<const N: usize> From<[u8; N]> for Exponent {
fn from(b: [u8; N]) -> Self {
b.as_ref().into()
}
}
impl<const N: usize> From<&[u8; N]> for Exponent {
fn from(b: &[u8; N]) -> Self {
b.as_ref().into()
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum BanknoteOrientation {
#[default]
RightEdgeFaceUp = 0x00,
RightEdgeFaceDown = 0x01,
LeftEdgeFaceUp = 0x02,
LeftEdgeFaceDown = 0x03,
}
impl BanknoteOrientation {
pub const LEN: usize = 1;
pub const MASK: u8 = 0b11;
pub const fn default() -> Self {
Self::RightEdgeFaceUp
}
}
impl From<u8> for BanknoteOrientation {
fn from(b: u8) -> Self {
match b & Self::MASK {
0x00 => Self::RightEdgeFaceUp,
0x01 => Self::RightEdgeFaceDown,
0x02 => Self::LeftEdgeFaceUp,
0x03 => Self::LeftEdgeFaceDown,
_ => Self::default(),
}
}
}
impl From<BanknoteOrientation> for &'static str {
fn from(b: BanknoteOrientation) -> Self {
match b {
BanknoteOrientation::RightEdgeFaceUp => "Right edge face up",
BanknoteOrientation::RightEdgeFaceDown => "Right edge face down",
BanknoteOrientation::LeftEdgeFaceUp => "Left edge face up",
BanknoteOrientation::LeftEdgeFaceDown => "Left edge face down",
}
}
}
impl From<&BanknoteOrientation> for &'static str {
fn from(b: &BanknoteOrientation) -> Self {
(*b).into()
}
}
impl fmt::Display for BanknoteOrientation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bstr: &'static str = self.into();
write!(f, "{bstr}")
}
}
macro_rules! ascii_tuple_struct {
($name:ident, $base:tt, $doc:tt) => {
#[doc = $doc]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct $name($base);
impl $name {
pub const LEN: usize = std::mem::size_of::<$base>();
pub const fn new() -> Self {
Self(0)
}
pub fn to_string(&self) -> String {
std::str::from_utf8(&[self.0]).unwrap_or("").to_string()
}
}
impl From<$base> for $name {
fn from(b: $base) -> Self {
Self(b)
}
}
impl From<$name> for String {
fn from(n: $name) -> String {
n.to_string()
}
}
impl From<&$name> for String {
fn from(n: &$name) -> String {
(*n).into()
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string())
}
}
};
}
ascii_tuple_struct!(
NoteType,
u8,
r"
An ASCII letter that documents the note type.
This corresponds to the data in the variant identity card.
"
);
ascii_tuple_struct!(
NoteSeries,
u8,
r"
An ASCII letter that documents the note series.
This corresponds to the data in the variant identity card.
"
);
ascii_tuple_struct!(
NoteCompatibility,
u8,
r"
An ASCII letter that documents the revision of the recognition core used.
This corresponds to the data in the variant identity card.
"
);
ascii_tuple_struct!(
NoteVersion,
u8,
r"
An ASCII letter that documents the version of the note's recognition criteria.
This corresponds to the data in the variant identity card.
"
);
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum BanknoteClassification {
#[default]
DisabledOrNotSupported = 0x00,
Unidentified = 0x01,
SuspectedCounterfeit = 0x02,
SuspectedZero = 0x03,
Genuine = 0x04,
}
impl BanknoteClassification {
pub const fn new() -> Self {
Self::DisabledOrNotSupported
}
}
impl From<u8> for BanknoteClassification {
fn from(b: u8) -> Self {
match b {
0x00 => Self::DisabledOrNotSupported,
0x01 => Self::Unidentified,
0x02 => Self::SuspectedCounterfeit,
0x03 => Self::SuspectedZero,
0x04 => Self::Genuine,
_ => {
log::trace!("Unknown banknote classification: 0x{b:x}");
Self::default()
}
}
}
}
impl From<BanknoteClassification> for &'static str {
fn from(b: BanknoteClassification) -> Self {
match b {
BanknoteClassification::DisabledOrNotSupported => "Disabled or not supported",
BanknoteClassification::Unidentified => "Unidentified",
BanknoteClassification::SuspectedCounterfeit => "Suspected counterfeit",
BanknoteClassification::SuspectedZero => "Suspected zero",
BanknoteClassification::Genuine => "Genuine",
}
}
}
impl From<&BanknoteClassification> for &'static str {
fn from(b: &BanknoteClassification) -> Self {
(*b).into()
}
}
impl fmt::Display for BanknoteClassification {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", <&'static str>::from(self))
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Banknote {
pub(crate) value: f32,
pub(crate) iso_code: ISOCode,
pub(crate) note_type: NoteType,
pub(crate) note_series: NoteSeries,
pub(crate) note_compatibility: NoteCompatibility,
pub(crate) note_version: NoteVersion,
pub(crate) banknote_classification: BanknoteClassification,
}
impl Banknote {
pub const fn new(
value: f32,
iso_code: ISOCode,
note_type: NoteType,
note_series: NoteSeries,
note_compatibility: NoteCompatibility,
note_version: NoteVersion,
banknote_classification: BanknoteClassification,
) -> Self {
Self {
value,
iso_code,
note_type,
note_series,
note_compatibility,
note_version,
banknote_classification,
}
}
pub const fn null() -> Self {
Self {
value: 0.0,
iso_code: ISOCode::new(),
note_type: NoteType::new(),
note_series: NoteSeries::new(),
note_compatibility: NoteCompatibility::new(),
note_version: NoteVersion::new(),
banknote_classification: BanknoteClassification::new(),
}
}
pub fn value(&self) -> f32 {
self.value
}
pub fn set_value(&mut self, value: u32) {
self.value = value as f32;
}
pub fn with_value(mut self, value: u32) -> Self {
self.set_value(value);
self
}
pub fn iso_code(&self) -> ISOCode {
self.iso_code
}
pub fn set_iso_code(&mut self, iso_code: ISOCode) {
self.iso_code = iso_code;
}
pub fn note_type(&self) -> NoteType {
self.note_type
}
pub fn set_note_type(&mut self, note_type: NoteType) {
self.note_type = note_type;
}
pub fn note_series(&self) -> NoteSeries {
self.note_series
}
pub fn set_note_series(&mut self, note_series: NoteSeries) {
self.note_series = note_series;
}
pub fn note_compatibility(&self) -> NoteCompatibility {
self.note_compatibility
}
pub fn set_note_compatibility(&mut self, note_compatibility: NoteCompatibility) {
self.note_compatibility = note_compatibility;
}
pub fn note_version(&self) -> NoteVersion {
self.note_version
}
pub fn set_note_version(&mut self, note_version: NoteVersion) {
self.note_version = note_version;
}
pub fn banknote_classification(&self) -> BanknoteClassification {
self.banknote_classification
}
pub fn set_banknote_classification(&mut self, banknote_classification: BanknoteClassification) {
self.banknote_classification = banknote_classification;
}
}
impl fmt::Display for Banknote {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Value: {} ISO Code: {} Note Type: {} Note Series: {} Note Compatibility: {} Note Version: {} Banknote Classification: {}",
self.value as u64,
self.iso_code,
self.note_type,
self.note_series,
self.note_compatibility,
self.note_version,
self.banknote_classification,
)
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct NoteTableItem {
pub(crate) note_index: usize,
pub(crate) banknote: Banknote,
}
impl NoteTableItem {
pub const fn new(note_index: usize, banknote: Banknote) -> Self {
Self {
note_index,
banknote,
}
}
pub fn is_null(&self) -> bool {
self == &Self::default()
}
pub fn note_index(&self) -> usize {
self.note_index
}
pub fn set_note_index(&mut self, note_index: usize) {
self.note_index = note_index;
}
pub fn banknote(&self) -> &Banknote {
&self.banknote
}
pub fn banknote_mut(&mut self) -> &mut Banknote {
&mut self.banknote
}
pub fn set_banknote(&mut self, banknote: Banknote) {
self.banknote = banknote;
}
}
impl fmt::Display for NoteTableItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Index: {} {}", self.note_index, self.banknote)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_iso_code() {
let iso_aud = ISOCode::AUD;
let iso_amd = ISOCode::AMD;
let iso_cad = ISOCode::CAD;
let iso_eur = ISOCode::EUR;
let iso_gbp = ISOCode::GBP;
let iso_mxn = ISOCode::MXN;
let iso_cny = ISOCode::CNY;
let iso_usd = ISOCode::USD;
let iso_xxx = ISOCode::XXX;
assert_eq!(<&str>::from(iso_aud), "AUD");
assert_eq!(<&str>::from(iso_amd), "AMD");
assert_eq!(<&str>::from(iso_cad), "CAD");
assert_eq!(<&str>::from(iso_eur), "EUR");
assert_eq!(<&str>::from(iso_gbp), "GBP");
assert_eq!(<&str>::from(iso_mxn), "MXN");
assert_eq!(<&str>::from(iso_cny), "CNY");
assert_eq!(<&str>::from(iso_usd), "USD");
assert_eq!(<&str>::from(iso_xxx), "XXX");
assert_eq!(ISOCode::from("AUD"), iso_aud);
assert_eq!(ISOCode::from("AMD"), iso_amd);
assert_eq!(ISOCode::from("CAD"), iso_cad);
assert_eq!(ISOCode::from("EUR"), iso_eur);
assert_eq!(ISOCode::from("GBP"), iso_gbp);
assert_eq!(ISOCode::from("MXN"), iso_mxn);
assert_eq!(ISOCode::from("CNY"), iso_cny);
assert_eq!(ISOCode::from("USD"), iso_usd);
assert_eq!(ISOCode::from("XXX"), iso_xxx);
assert_eq!(ISOCode::from(""), iso_xxx);
assert_eq!(ISOCode::from(b"AUD"), iso_aud);
assert_eq!(ISOCode::from(b"AMD"), iso_amd);
assert_eq!(ISOCode::from(b"CAD"), iso_cad);
assert_eq!(ISOCode::from(b"EUR"), iso_eur);
assert_eq!(ISOCode::from(b"GBP"), iso_gbp);
assert_eq!(ISOCode::from(b"MXN"), iso_mxn);
assert_eq!(ISOCode::from(b"CNY"), iso_cny);
assert_eq!(ISOCode::from(b"USD"), iso_usd);
assert_eq!(ISOCode::from(b"XXX"), iso_xxx);
assert_eq!(ISOCode::from(b""), iso_xxx);
}
#[test]
fn test_base_value() {
let base_value = BaseValue(42);
assert_eq!(u16::from(base_value), 42);
assert_eq!(f32::from(base_value), 42.0);
assert_eq!(BaseValue::from(b"042"), base_value);
for i in 0..=u8::MAX {
assert_eq!(BaseValue::from([i]), BaseValue::default());
for j in 0..=u8::MAX {
assert_eq!(BaseValue::from([i, j]), BaseValue::default());
}
}
assert_eq!(BaseValue::from(b"042f"), base_value);
}
#[test]
fn test_sign() {
let sign_pos = Sign::Positive;
let sign_neg = Sign::Negative;
assert_eq!(Sign::from(b'+'), sign_pos);
assert_eq!(Sign::from(b'-'), sign_neg);
for b in 0..=u8::MAX {
if b != b'-' {
assert_eq!(Sign::from(b), sign_pos);
}
}
assert_eq!(<&'static str>::from(sign_pos), "+");
assert_eq!(<&'static str>::from(sign_neg), "-");
}
#[test]
fn test_exponent() {
let exp_max = Exponent(99);
let exp_def = Exponent(1);
let exp_min = Exponent(0);
assert_eq!(Exponent::default(), exp_def);
assert_eq!(Exponent::from(b"99"), exp_max);
assert_eq!(Exponent::from(b"00"), exp_min);
assert_eq!(Exponent::from([]), exp_def);
for i in 0..=u8::MAX {
assert_eq!(Exponent::from([i]), exp_def);
for j in 0..=u8::MAX {
if i == b'+' && j.is_ascii_digit() {
assert_eq!(
Exponent::from([i, j]),
Exponent(std::str::from_utf8(&[j]).unwrap().parse::<u8>().unwrap())
);
} else if !i.is_ascii_digit() || !j.is_ascii_digit() {
assert_eq!(
Exponent::from([i, j]),
exp_def,
"i: {i}, j: {j}, string({})",
std::str::from_utf8(&[i, j]).unwrap()
);
}
}
}
}
#[test]
fn test_banknote_orientation() {
assert_eq!(
BanknoteOrientation::from(0x00),
BanknoteOrientation::RightEdgeFaceUp
);
assert_eq!(
BanknoteOrientation::from(0x01),
BanknoteOrientation::RightEdgeFaceDown
);
assert_eq!(
BanknoteOrientation::from(0x02),
BanknoteOrientation::LeftEdgeFaceUp
);
assert_eq!(
BanknoteOrientation::from(0x03),
BanknoteOrientation::LeftEdgeFaceDown
);
for i in 0x04..=u8::MAX {
assert_eq!(
BanknoteOrientation::from(i),
BanknoteOrientation::from(i & BanknoteOrientation::MASK)
);
}
}
#[test]
fn test_banknote_classification() {
assert_eq!(
BanknoteClassification::from(0x00),
BanknoteClassification::DisabledOrNotSupported
);
assert_eq!(
BanknoteClassification::from(0x01),
BanknoteClassification::Unidentified
);
assert_eq!(
BanknoteClassification::from(0x02),
BanknoteClassification::SuspectedCounterfeit
);
assert_eq!(
BanknoteClassification::from(0x03),
BanknoteClassification::SuspectedZero
);
assert_eq!(
BanknoteClassification::from(0x04),
BanknoteClassification::Genuine
);
for i in 0x05..=u8::MAX {
assert_eq!(
BanknoteClassification::from(i),
BanknoteClassification::default()
);
}
}
#[test]
fn test_ascii_tuples() {
let ascii_table = [
b' ', b'!', b'"', b'#', b'$', b'%', b'&', b'\'', b'(', b')', b'*', b'+', b',', b'-',
b'.', b'/', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';',
b'<', b'=', b'>', b'?', b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I',
b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W',
b'X', b'Y', b'Z', b'[', b'\\', b']', b'^', b'_', b'`', b'a', b'b', b'c', b'd', b'e',
b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's',
b't', b'u', b'v', b'w', b'x', b'y', b'z', b'{', b'|', b'}', b'~',
];
for c in 0..=u8::MAX {
if (32..=126).contains(&c) {
let ascii_index = (c - 32) as usize;
let ascii_val = ascii_table[ascii_index];
assert_eq!(NoteType::from(c).to_string().as_bytes()[0], ascii_val);
assert_eq!(NoteSeries::from(c).to_string().as_bytes()[0], ascii_val);
assert_eq!(
NoteCompatibility::from(c).to_string().as_bytes()[0],
ascii_val
);
assert_eq!(NoteVersion::from(c).to_string().as_bytes()[0], ascii_val);
} else if c < 128 {
assert!(!NoteType::from(c).to_string().is_empty());
assert!(!NoteSeries::from(c).to_string().is_empty());
assert!(!NoteCompatibility::from(c).to_string().is_empty());
assert!(!NoteVersion::from(c).to_string().is_empty());
} else {
assert!(NoteType::from(c).to_string().is_empty());
assert!(NoteSeries::from(c).to_string().is_empty());
assert!(NoteCompatibility::from(c).to_string().is_empty());
assert!(NoteVersion::from(c).to_string().is_empty());
}
}
}
}