use crate::Currency;
use crate::{BaseMoney, Decimal};
const ESCAPE_SYMBOL: char = '\\';
const AMOUNT_FORMAT_SYMBOL: char = 'a';
const CODE_FORMAT_SYMBOL: char = 'c';
const SYMBOL_FORMAT_SYMBOL: char = 's';
const MINOR_FORMAT_SYMBOL: char = 'm';
const NEGATIVE_FORMAT_SYMBOL: char = 'n';
pub(crate) static FORMAT_SYMBOLS: &[char] = &[
'a', 'c', 's', 'm', 'n', ];
pub(crate) const CODE_FORMAT: &str = "c na"; pub(crate) const SYMBOL_FORMAT: &str = "nsa";
pub(crate) const CODE_FORMAT_MINOR: &str = "c na m"; pub(crate) const SYMBOL_FORMAT_MINOR: &str = "nsa m";
pub(crate) fn format<C: Currency>(money: impl BaseMoney<C>, format_str: &str) -> String {
format_with_separator(
money,
format_str,
C::THOUSAND_SEPARATOR,
C::DECIMAL_SEPARATOR,
)
}
pub(crate) fn format_128_abs(num: i128, thousand_separator: &str) -> String {
let abs_num = num.abs();
let num_str = abs_num.to_string();
let mut result = String::new();
let len = num_str.len();
for (i, ch) in num_str.chars().enumerate() {
if i > 0 && (len - i).is_multiple_of(3) {
result.push_str(thousand_separator);
}
result.push(ch);
}
result
}
pub(crate) fn format_decimal_abs(
decimal: Decimal,
thousand_separator: &str,
decimal_separator: &str,
minor_unit: u16,
) -> String {
let abs_decimal = decimal.abs();
let decimal_str = abs_decimal.to_string();
let parts: Vec<&str> = decimal_str.split('.').collect();
let integer_part = parts[0];
let fractional_part = parts.get(1);
let mut result = String::new();
let len = integer_part.len();
for (i, ch) in integer_part.chars().enumerate() {
if i > 0 && (len - i).is_multiple_of(3) {
result.push_str(thousand_separator);
}
result.push(ch);
}
if let Some(frac) = fractional_part {
result.push_str(decimal_separator);
if frac.len() >= minor_unit.into() {
result.push_str(frac);
} else {
result.push_str(frac);
let frac_len = frac.len();
let minor_unit_len: usize = minor_unit.into();
let remaining_frac_len = minor_unit_len - frac_len;
result.push_str(&"0".repeat(remaining_frac_len));
}
} else if minor_unit > 0 {
result.push_str(decimal_separator);
result.push_str(&"0".repeat(minor_unit.into()));
}
result
}
pub(crate) fn format_with_separator<C: Currency>(
money: impl BaseMoney<C>,
format_str: &str,
thousand_separator: &str,
decimal_separator: &str,
) -> String {
let is_negative = money.is_negative();
let display_amount = if contains_active_format_symbol(format_str, MINOR_FORMAT_SYMBOL) {
if let Ok(minor_amount) = money.minor_amount() {
format_128_abs(minor_amount, thousand_separator)
} else {
"OVERFLOWED_AMOUNT".into()
}
} else {
format_decimal_abs(
money.amount(),
thousand_separator,
decimal_separator,
C::MINOR_UNIT,
)
};
format_with_amount::<C>(&display_amount, is_negative, format_str)
}
fn contains_active_format_symbol(format_str: &str, symbol: char) -> bool {
let mut chars = format_str.chars().peekable();
while let Some(ch) = chars.next() {
if ch == ESCAPE_SYMBOL {
if let Some(&next_ch) = chars.peek() {
if next_ch == '{' {
chars.next(); for inner_ch in chars.by_ref() {
if inner_ch == '}' {
break;
}
}
} else {
chars.next();
}
}
} else if ch == symbol {
return true;
}
}
false
}
pub(crate) fn format_with_amount<C: Currency>(
display_amount: &str,
is_negative: bool,
format_str: &str,
) -> String {
let mut chars = format_str.chars().peekable();
let mut result = String::new();
while let Some(ch) = chars.next() {
if ch == ESCAPE_SYMBOL {
if let Some(&next_ch) = chars.peek() {
if next_ch == '{' {
chars.next(); for inner_ch in chars.by_ref() {
if inner_ch == '}' {
break;
}
result.push(inner_ch);
}
continue;
} else if FORMAT_SYMBOLS.contains(&next_ch) || next_ch == ESCAPE_SYMBOL {
chars.next();
result.push(next_ch);
continue;
} else {
result.push(ch);
}
} else {
result.push(ch);
}
} else {
match ch {
AMOUNT_FORMAT_SYMBOL => result.push_str(display_amount),
CODE_FORMAT_SYMBOL => result.push_str(C::CODE),
SYMBOL_FORMAT_SYMBOL => result.push_str(C::SYMBOL),
MINOR_FORMAT_SYMBOL => result.push_str(C::MINOR_UNIT_SYMBOL),
NEGATIVE_FORMAT_SYMBOL => {
if is_negative {
result.push('-');
}
}
' ' => result.push(' '),
_ => result.push(ch),
}
}
}
result
}