use crate::base::{Lang, N2WError, Result};
use crate::floatpath::FloatValue;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::{Signed, ToPrimitive};
const MINUS_PREFIX_WORD: &str = "minus ";
const FLOAT_INFIX_WORD: &str = " coma ";
const ZERO: &str = "nola";
const CARDINAL_WORDS: [&str; 20] = [
ZERO,
"egn",
"dus",
"tres",
"quater",
"tschentg",
"sis",
"seat",
"otg",
"nov",
"diesch",
"endesch",
"dudesch",
"tredesch",
"quitordesch",
"quendesch",
"sedesch",
"gisseat",
"schotg",
"schenev",
];
const ORDINAL_WORDS: [&str; 21] = [
ZERO,
"amprem",
"savund",
"tearz",
"quart",
"tschentgavel",
"sisavel",
"seatavel",
"otgavel",
"novavel",
"dieschavel",
"endeschavel",
"dudeschavel",
"tredeschavel",
"quitordeschavel",
"quendeschavel",
"sedeschavel",
"gisseatavel",
"schotgavel",
"schenevavel",
"veintgavel",
];
fn str_tens(tens: usize) -> Option<&'static str> {
Some(match tens {
2 => "veintga",
3 => "trainta",
4 => "curànta",
5 => "tschuncànta",
6 => "sissànta",
7 => "satànta",
8 => "otgànta",
9 => "novànta",
_ => return None,
})
}
const EXPONENT_PREFIXES: [&str; 11] = [
ZERO, "m", "b", "tr", "quadr", "quint", "sest", "sett", "ott", "nov", "dec",
];
fn phonetic_contraction(string: &str) -> String {
string
.replace("aegn", "egn") .replace("aotg", "otg") .replace("veintga_", "veintg") .replace('_', "")
}
fn adapt_hundred(string: &str) -> String {
string
.replace("dustschient", "dutschient")
.replace("trestschient", "tretschient")
.replace("aend", "adend")
.replace("aegn", "adegn")
.replace("aotg", "adotg")
}
fn adapt_thousand(string: &str) -> String {
string
.replace("dusmeli", "dumeli")
.replace("tresmeli", "tremeli")
.replace("aend", "adend")
.replace("aegn", "adegn")
.replace("aotg", "adotg")
}
fn adapt_milliarda(string: &str) -> String {
let padded = format!(" {} ", string);
padded
.replace(" egn miliarda ", " egna miliarda ")
.replace("dus miliardas", "duas miliardas")
.replace(" a end", " ad end")
.replace(" a egn", " ad egn")
.replace(" a otg", " ad otg")
}
fn exponent_length_to_string(exponent_length: usize) -> Result<String> {
let prefix = EXPONENT_PREFIXES.get(exponent_length / 6).ok_or_else(|| {
N2WError::Index(format!(
"list index out of range: EXPONENT_PREFIXES[{}]",
exponent_length / 6
))
})?;
if exponent_length % 6 == 0 {
Ok(format!("{}iliùn", prefix))
} else {
Ok(format!("{}iliarda", prefix))
}
}
fn omitt_if_zero(number_to_string: &str) -> String {
if number_to_string == ZERO {
String::new()
} else {
number_to_string.to_string()
}
}
fn empty_if_zero(number_to_string: &str) -> String {
if number_to_string == ZERO {
"_".to_string()
} else {
number_to_string.to_string()
}
}
fn drop_last(s: &str, n: usize) -> String {
let total = s.chars().count();
s.chars().take(total.saturating_sub(n)).collect()
}
fn parse_digits(s: &str) -> BigInt {
BigInt::parse_bytes(s.as_bytes(), 10).expect("slice of str(BigInt) is all decimal digits")
}
fn tens_to_cardinal(number: u32) -> String {
let tens = (number / 10) as usize;
let units = (number % 10) as usize;
let prefix = match str_tens(tens) {
Some(p) => p.to_string(),
None => drop_last(CARDINAL_WORDS[tens], 1) + "ànta",
};
let postfix = empty_if_zero(CARDINAL_WORDS[units]);
phonetic_contraction(&(prefix + &postfix))
}
fn hundreds_to_cardinal(number: u32) -> Result<String> {
let hundreds = (number / 100) as usize;
let tens = number % 100;
let mut prefix = "tschient".to_string();
if hundreds != 1 {
prefix = format!("{}{}", CARDINAL_WORDS[hundreds], prefix);
}
let postfix = omitt_if_zero(&to_cardinal(&BigInt::from(tens))?);
let infix = if (tens > 0 && tens <= 13) || matches!(tens, 15 | 16 | 20 | 30) {
"a"
} else {
""
};
Ok(adapt_hundred(&format!("{}{}{}", prefix, infix, postfix)))
}
fn thousands_to_cardinal(number: u32) -> Result<String> {
let thousands = number / 1000;
let hundreds = number % 1000;
let prefix = if thousands != 1 {
format!("{}meli", to_cardinal(&BigInt::from(thousands))?)
} else {
"meli".to_string()
};
let postfix = omitt_if_zero(&to_cardinal(&BigInt::from(hundreds))?);
let infix = if hundreds <= 100 && !postfix.is_empty() {
"a"
} else {
""
};
Ok(adapt_thousand(&format!("{}{}{}", prefix, infix, postfix)))
}
fn big_number_to_cardinal(number: &BigInt) -> Result<String> {
let s = number.to_string();
let digits: Vec<char> = s.chars().collect();
let length = digits.len();
if length >= 66 {
return Err(N2WError::NotImplemented("The given number is too large.".into()));
}
let predigits = if length % 3 != 0 { length % 3 } else { 3 };
let multiplier: String = digits[..predigits].iter().collect();
let exponent: String = digits[predigits..].iter().collect();
let mut infix = exponent_length_to_string(exponent.chars().count())?;
let prefix = if multiplier == "1" {
"egn ".to_string()
} else {
let p = to_cardinal(&parse_digits(&multiplier))?;
infix = format!(" {}s", infix);
p
};
let postfix = if exponent.chars().any(|c| c != '0') {
let p = to_cardinal(&parse_digits(&exponent))?;
if exponent.starts_with("000") {
infix.push_str(" a ");
} else {
infix.push(' ');
}
p
} else {
String::new()
};
Ok(adapt_milliarda(&format!("{}{}{}", prefix, infix, postfix))
.trim()
.to_string())
}
fn to_cardinal(number: &BigInt) -> Result<String> {
if number.is_negative() {
return Ok(format!("{}{}", MINUS_PREFIX_WORD, to_cardinal(&-number)?));
}
if *number < BigInt::from(20u32) {
Ok(CARDINAL_WORDS[number.to_usize().expect("< 20")].to_string())
} else if *number < BigInt::from(100u32) {
Ok(tens_to_cardinal(number.to_u32().expect("< 100")))
} else if *number < BigInt::from(1000u32) {
hundreds_to_cardinal(number.to_u32().expect("< 1000"))
} else if *number < BigInt::from(1_000_000u32) {
thousands_to_cardinal(number.to_u32().expect("< 10**6"))
} else {
big_number_to_cardinal(number)
}
}
fn to_ordinal(number: &BigInt) -> Result<String> {
if number.is_negative() {
return Ok(format!("{}{}", MINUS_PREFIX_WORD, to_ordinal(&-number)?));
}
if *number <= BigInt::from(20u32) {
return Ok(ORDINAL_WORDS[number.to_usize().expect("<= 20")].to_string());
}
let cardinal = to_cardinal(number)?;
let suffix = if cardinal.chars().next_back() == Some('a') {
"vel"
} else {
"avel"
};
Ok(cardinal + suffix)
}
fn f64_trunc_to_bigint(f: f64) -> Result<BigInt> {
format!("{:.0}", f.trunc())
.parse::<BigInt>()
.map_err(|e| N2WError::Value(e.to_string()))
}
fn float_repr_has_point(f: f64) -> bool {
f.is_finite() && (f == 0.0 || (f.abs() >= 1e-4 && f.abs() < 1e16))
}
fn rm_float_f64(value: f64, precision: u32) -> Result<String> {
if value < 0.0 {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
rm_float_f64(-value, precision)?
));
}
let prefix = to_cardinal(&f64_trunc_to_bigint(value)?)?;
if !float_repr_has_point(value) {
return Err(N2WError::Index("list index out of range".into()));
}
let s = format!("{:.*}", precision as usize, value);
let right = match s.split_once('.') {
Some((_, r)) => r,
None => {
return Err(N2WError::Index("list index out of range".into()));
}
};
let mut parts: Vec<String> = Vec::with_capacity(right.chars().count());
for c in right.chars() {
let d = c.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", c))
})?;
parts.push(to_cardinal(&BigInt::from(d))?);
}
Ok(format!("{}{}{}", prefix, FLOAT_INFIX_WORD, parts.join(" ")))
}
fn rm_decimal(value: &BigDecimal, precision: u32) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
rm_decimal(&(-value.clone()), precision)?
));
}
if *value < BigDecimal::from(1_000_000) {
return Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".into(),
));
}
if precision == 0 {
let bigint = value.with_scale(0).as_bigint_and_exponent().0;
return to_cardinal(&bigint);
}
let int_digits = value
.with_scale(0)
.as_bigint_and_exponent()
.0
.to_string()
.len();
let length = int_digits + 1 + precision as usize; if length >= 66 {
return Err(N2WError::NotImplemented(
"The given number is too large.".into(),
));
}
Err(N2WError::Value(
"invalid literal for int() with base 10".into(),
))
}
fn rm_float_ordinal_f64(f: f64, precision: u32) -> Result<String> {
if f < 0.0 {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
rm_float_ordinal_f64(-f, precision)?
));
}
if f.fract() != 0.0 {
let prefix = to_ordinal(&f64_trunc_to_bigint(f)?)?;
if !float_repr_has_point(f) {
return Err(N2WError::Index("list index out of range".into()));
}
let s = format!("{:.*}", precision as usize, f);
let frac = s.split_once('.').map(|(_, x)| x).unwrap_or("");
let mut parts: Vec<String> = Vec::new();
for c in frac.chars() {
let d = c.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", c))
})?;
parts.push(to_cardinal(&BigInt::from(d))?);
}
return Ok(format!("{}{}{}", prefix, FLOAT_INFIX_WORD, parts.join(" ")));
}
if f <= 20.0 {
return Err(N2WError::Type(
"list indices must be integers or slices, not float".into(),
));
}
let cardinal = rm_float_f64(f, precision)?;
let suffix = if cardinal.ends_with('a') { "vel" } else { "avel" };
Ok(cardinal + suffix)
}
fn rm_decimal_ordinal(value: &BigDecimal, precision: u32) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
rm_decimal_ordinal(&(-value.clone()), precision)?
));
}
if !value.is_integer() {
let pre = value.with_scale(0).as_bigint_and_exponent().0;
let prefix = to_ordinal(&pre)?;
let s = crate::strnum::python_decimal_str(value);
let frac = match s.split_once('.') {
Some((_, f)) => f.to_string(),
None => return Err(N2WError::Index("list index out of range".into())),
};
let mut parts: Vec<String> = Vec::new();
for c in frac.chars() {
let d = c.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", c))
})?;
parts.push(to_cardinal(&BigInt::from(d))?);
}
return Ok(format!("{}{}{}", prefix, FLOAT_INFIX_WORD, parts.join(" ")));
}
if *value <= BigDecimal::from(20) {
return Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".into(),
));
}
let cardinal = rm_decimal(value, precision)?;
let suffix = if cardinal.ends_with('a') { "vel" } else { "avel" };
Ok(cardinal + suffix)
}
fn attribute_error(attr: &str) -> N2WError {
N2WError::Attribute(format!(
"'Num2Word_RM_SUTSILV' object has no attribute '{}'",
attr
))
}
pub struct LangRmSutsilv;
impl LangRmSutsilv {
pub fn new() -> Self {
LangRmSutsilv
}
}
impl Default for LangRmSutsilv {
fn default() -> Self {
LangRmSutsilv::new()
}
}
impl Lang for LangRmSutsilv {
fn to_currency(
&self,
_val: &crate::currency::CurrencyValue,
_currency: &str,
_cents: bool,
_separator: Option<&str>,
_adjective: bool,
) -> Result<String> {
Err(N2WError::Attribute(format!(
"'{}' object has no attribute 'to_currency'",
"Num2Word_RM_SUTSILV",
)))
}
fn to_cheque(&self, _val: &bigdecimal::BigDecimal, _currency: &str) -> Result<String> {
Err(N2WError::Attribute(format!(
"'{}' object has no attribute 'to_cheque'",
"Num2Word_RM_SUTSILV",
)))
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
to_cardinal(value)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
to_ordinal(value)
}
fn to_ordinal_num(&self, _value: &BigInt) -> Result<String> {
Err(attribute_error("to_ordinal_num"))
}
fn to_year(&self, _value: &BigInt) -> Result<String> {
Err(attribute_error("to_year"))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => rm_float_f64(*value, *precision),
FloatValue::Decimal { value, precision } => rm_decimal(value, *precision),
}
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => rm_float_f64(*value, *precision),
FloatValue::Decimal { value, precision } => rm_decimal(value, *precision),
}
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
match value {
FloatValue::Float { value, precision } => rm_float_ordinal_f64(*value, *precision),
FloatValue::Decimal { value, precision } => rm_decimal_ordinal(value, *precision),
}
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, _repr_str: &str) -> Result<String> {
Err(attribute_error("to_ordinal_num"))
}
fn year_float_entry(&self, _value: &FloatValue) -> Result<String> {
Err(attribute_error("to_year"))
}
fn str_to_number(&self, _s: &str) -> Result<crate::strnum::ParsedNumber> {
Err(attribute_error("str_to_number"))
}
fn neg_zero_decimal(&self, to: &str) -> Option<Result<String>> {
Some(match to {
"cardinal" | "ordinal" => Err(N2WError::Type(
"list indices must be integers or slices, not decimal.Decimal".into(),
)),
"ordinal_num" => Err(attribute_error("to_ordinal_num")),
"year" => Err(attribute_error("to_year")),
_ => return None,
})
}
fn to_fraction(&self, _numerator: &BigInt, _denominator: &BigInt) -> Result<String> {
Err(attribute_error("to_fraction"))
}
}