use crate::base::{set_low_numwords, set_mid_numwords, Cards, Lang, N2WError, Result};
use crate::currency::CurrencyForms;
use crate::floatpath::FloatValue;
use num_bigint::{BigInt, Sign};
use num_traits::{One, Signed, Zero};
use std::collections::HashMap;
fn py_float_str(f: f64) -> String {
if f.is_nan() {
return "nan".to_string();
}
if f.is_infinite() {
return if f.is_sign_positive() {
"inf".to_string()
} else {
"-inf".to_string()
};
}
let a = f.abs();
if a != 0.0 && (a >= 1e16 || a < 1e-4) {
let s = format!("{:e}", f);
if let Some((m, e)) = s.split_once('e') {
let (sign, digits) = match e.strip_prefix('-') {
Some(d) => ("-", d.to_string()),
None => ("+", e.to_string()),
};
let digits = if digits.len() < 2 {
format!("0{}", digits)
} else {
digits
};
return format!("{}e{}{}", m, sign, digits);
}
s
} else if f.fract() == 0.0 {
format!("{:.1}", f)
} else {
format!("{}", f)
}
}
fn py_num_str(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, .. } => py_float_str(*value),
FloatValue::Decimal { value, .. } => crate::strnum::python_decimal_str(value),
}
}
fn pow10(n: u32) -> BigInt {
BigInt::from(10u8).pow(n)
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert(
"AUD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"BYN",
CurrencyForms::new(&["rouble", "roubles"], &["kopek", "kopeks"]),
);
m.insert(
"CAD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"EEK",
CurrencyForms::new(&["kroon", "kroons"], &["sent", "senti"]),
);
m.insert(
"EUR",
CurrencyForms::new(&["euro", "euro"], &["cent", "cents"]),
);
m.insert(
"GBP",
CurrencyForms::new(&["pound sterling", "pounds sterling"], &["penny", "pence"]),
);
m.insert(
"LTL",
CurrencyForms::new(&["litas", "litas"], &["cent", "cents"]),
);
m.insert(
"LVL",
CurrencyForms::new(&["lat", "lats"], &["santim", "santims"]),
);
m.insert(
"USD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"RUB",
CurrencyForms::new(&["rouble", "roubles"], &["kopek", "kopeks"]),
);
m.insert(
"SEK",
CurrencyForms::new(&["krona", "kronor"], &["öre", "öre"]),
);
m.insert(
"NOK",
CurrencyForms::new(&["krone", "kroner"], &["øre", "øre"]),
);
m.insert(
"PLN",
CurrencyForms::new(&["zloty", "zlotys", "zlotu"], &["grosz", "groszy"]),
);
m.insert(
"MXN",
CurrencyForms::new(&["peso", "pesos"], &["cent", "cents"]),
);
m.insert(
"RON",
CurrencyForms::new(&["leu", "lei", "de lei"], &["ban", "bani", "de bani"]),
);
m.insert(
"INR",
CurrencyForms::new(&["rupee", "rupees"], &["paisa", "paise"]),
);
m.insert(
"HUF",
CurrencyForms::new(&["forint", "forint"], &["fillér", "fillér"]),
);
m.insert(
"ISK",
CurrencyForms::new(&["króna", "krónur"], &["aur", "aurar"]),
);
m.insert(
"UZS",
CurrencyForms::new(&["sum", "sums"], &["tiyin", "tiyins"]),
);
m.insert(
"SAR",
CurrencyForms::new(&["saudi riyal", "saudi riyals"], &["halalah", "halalas"]),
);
m.insert("JPY", CurrencyForms::new(&["yen", "yen"], &["sen", "sen"]));
m.insert(
"KRW",
CurrencyForms::new(&["won", "won"], &["jeon", "jeon"]),
);
m.insert(
"EUR",
CurrencyForms::new(&["euro", "euros"], &["cent", "cents"]),
);
m.insert(
"USD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"GBP",
CurrencyForms::new(&["pound", "pounds"], &["penny", "pence"]),
);
m.insert(
"NGN",
CurrencyForms::new(&["naira", "naira"], &["kobo", "kobo"]),
);
m.insert(
"AUD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"CAD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"NZD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"HKD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"SGD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
m.insert(
"CHF",
CurrencyForms::new(&["franc", "francs"], &["rappen", "rappen"]),
);
m.insert(
"AED",
CurrencyForms::new(&["dirham", "dirhams"], &["fils", "fils"]),
);
m.insert("JPY", CurrencyForms::new(&["yen", "yen"], &["sen", "sen"]));
m.insert(
"CNY",
CurrencyForms::new(&["yuan", "yuan"], &["fen", "fen"]),
);
m.insert(
"INR",
CurrencyForms::new(&["rupee", "rupees"], &["paisa", "paise"]),
);
m.insert(
"KRW",
CurrencyForms::new(&["won", "won"], &["jeon", "jeon"]),
);
m.insert(
"MXN",
CurrencyForms::new(&["peso", "pesos"], &["cent", "cents"]),
);
m.insert(
"BRL",
CurrencyForms::new(&["real", "reais"], &["cent", "cents"]),
);
m.insert(
"ZAR",
CurrencyForms::new(&["rand", "rand"], &["cent", "cents"]),
);
m.insert(
"SAR",
CurrencyForms::new(&["riyal", "riyals"], &["halalah", "halalas"]),
);
m.insert(
"QAR",
CurrencyForms::new(&["riyal", "riyals"], &["dirham", "dirhams"]),
);
m.insert(
"KWD",
CurrencyForms::new(&["dinar", "dinars"], &["fils", "fils"]),
);
m.insert(
"BHD",
CurrencyForms::new(&["dinar", "dinars"], &["fils", "fils"]),
);
m.insert(
"OMR",
CurrencyForms::new(&["rial", "rials"], &["baisa", "baisa"]),
);
m.insert(
"JOD",
CurrencyForms::new(&["dinar", "dinars"], &["fils", "fils"]),
);
m.insert(
"TND",
CurrencyForms::new(&["dinar", "dinars"], &["millime", "millimes"]),
);
m.insert(
"LYD",
CurrencyForms::new(&["dinar", "dinars"], &["dirham", "dirhams"]),
);
m.insert(
"IQD",
CurrencyForms::new(&["dinar", "dinars"], &["fils", "fils"]),
);
m
}
pub struct LangEnNp {
cards: Cards,
maxval: BigInt,
ords: HashMap<&'static str, &'static str>,
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl Default for LangEnNp {
fn default() -> Self {
Self::new()
}
}
impl LangEnNp {
pub fn new() -> Self {
let mut cards = Cards::new();
cards.insert(pow10(17), "shankha");
cards.insert(pow10(15), "padam");
cards.insert(pow10(13), "neel");
cards.insert(pow10(11), "kharba");
cards.insert(pow10(9), "arba");
cards.insert(pow10(7), "crore");
cards.insert(pow10(5), "lakh");
set_mid_numwords(
&mut cards,
&[
(1000, "thousand"),
(100, "hundred"),
(90, "ninety"),
(80, "eighty"),
(70, "seventy"),
(60, "sixty"),
(50, "fifty"),
(40, "forty"),
(30, "thirty"),
],
);
set_low_numwords(
&mut cards,
&[
"twenty",
"nineteen",
"eighteen",
"seventeen",
"sixteen",
"fifteen",
"fourteen",
"thirteen",
"twelve",
"eleven",
"ten",
"nine",
"eight",
"seven",
"six",
"five",
"four",
"three",
"two",
"one",
"zero",
],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let ords: HashMap<&str, &str> = [
("one", "first"),
("two", "second"),
("three", "third"),
("four", "fourth"),
("five", "fifth"),
("six", "sixth"),
("seven", "seventh"),
("eight", "eighth"),
("nine", "ninth"),
("ten", "tenth"),
("eleven", "eleventh"),
("twelve", "twelfth"),
("twenty", "twentieth"),
("thirty", "thirtieth"),
("forty", "fortieth"),
("fifty", "fiftieth"),
("sixty", "sixtieth"),
("seventy", "seventieth"),
("eighty", "eightieth"),
("ninety", "ninetieth"),
("hundred", "hundredth"),
("thousand", "thousandth"),
("million", "millionth"),
("billion", "billionth"),
]
.into_iter()
.collect();
LangEnNp {
cards,
maxval,
ords,
exclude_title: vec!["and".into(), "point".into(), "minus".into()],
currency_forms: build_currency_forms(),
}
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.sign() == Sign::Minus {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
fn verify_ordinal_float(&self, value: &FloatValue) -> Result<BigInt> {
match value.as_whole_int() {
Some(i) => {
if i.sign() == Sign::Minus {
Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
py_num_str(value)
)))
} else {
Ok(i)
}
}
None => Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
py_num_str(value)
))),
}
}
}
impl Lang for LangEnNp {
fn default_currency(&self) -> &str {
"EUR"
}
fn default_separator(&self) -> &str {
","
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
"minus "
}
fn pointword(&self) -> &str {
"point"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
let (ltext, lnum) = l;
let (rtext, rnum) = r;
let hundred = BigInt::from(100);
if lnum.is_one() && rnum < &hundred {
(rtext.to_string(), rnum.clone())
} else if &hundred > lnum && lnum > rnum {
(format!("{}-{}", ltext, rtext), lnum + rnum)
} else if lnum >= &hundred && &hundred > rnum {
(format!("{} and {}", ltext, rtext), lnum + rnum)
} else if rnum > lnum {
(format!("{} {}", ltext, rtext), lnum * rnum)
} else {
(format!("{}, {}", ltext, rtext), lnum + rnum)
}
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let cardinal = self.to_cardinal(value)?;
let mut outwords: Vec<String> = cardinal.split(' ').map(|s| s.to_string()).collect();
let last = outwords.last().unwrap().clone();
let mut lastwords: Vec<String> = last.split('-').map(|s| s.to_string()).collect();
let lastword = lastwords.last().unwrap().to_lowercase();
let newlast = match self.ords.get(lastword.as_str()) {
Some(o) => o.to_string(),
None => {
if lastword.ends_with('y') {
format!("{}ieth", &lastword[..lastword.len() - 1])
} else {
format!("{}th", lastword)
}
}
};
let n = lastwords.len();
lastwords[n - 1] = self.title(&newlast);
let n2 = outwords.len();
outwords[n2 - 1] = lastwords.join("-");
Ok(outwords.join(" "))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let ord = self.to_ordinal(value)?;
let suffix: String = {
let mut tail: Vec<char> = ord.chars().rev().take(2).collect();
tail.reverse();
tail.into_iter().collect()
};
Ok(format!("{}{}", value, suffix))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
let mut val = value.clone();
let mut suffix: Option<&str> = None;
if val.sign() == Sign::Minus {
val = -val;
suffix = Some("BC");
}
let hundred = BigInt::from(100);
let ten = BigInt::from(10);
let high = &val / &hundred;
let low = &val % &hundred;
let valtext =
if high.is_zero() || ((&high % &ten).is_zero() && low < ten) || high >= hundred {
self.to_cardinal(&val)?
} else {
let hightext = self.to_cardinal(&high)?;
let lowtext = if low.is_zero() {
"hundred".to_string()
} else if low < ten {
format!("oh-{}", self.to_cardinal(&low)?)
} else {
self.to_cardinal(&low)?
};
format!("{} {}", hightext, lowtext)
};
Ok(match suffix {
Some(s) => format!("{} {}", valtext, s),
None => valtext,
})
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let i = self.verify_ordinal_float(value)?;
self.to_ordinal(&i)
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
let i = self.verify_ordinal_float(value)?;
let ord = self.to_ordinal(&i)?;
let suffix: String = {
let chars: Vec<char> = ord.chars().collect();
let start = chars.len().saturating_sub(2);
chars[start..].iter().collect()
};
Ok(format!("{}{}", repr_str, suffix))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
match value {
FloatValue::Float { value: f, .. } => {
if !(f.is_finite() && f.fract() == 0.0) {
return Err(N2WError::Type(format!(
"to='year' expects an integer; got non-integer float {}",
py_float_str(*f)
)));
}
let i = value
.as_whole_int()
.ok_or_else(|| N2WError::Type("unreachable: whole float".into()))?;
self.to_year(&i)
}
FloatValue::Decimal { value: d, .. } => {
let i = d.with_scale(0).as_bigint_and_exponent().0;
self.to_year(&i)
}
}
}
fn to_fraction(&self, numerator: &BigInt, denominator: &BigInt) -> Result<String> {
if denominator.is_zero() {
return Err(N2WError::ZeroDivision(
"denominator must not be zero".into(),
));
}
if denominator.is_one() || numerator.is_zero() {
return self.to_cardinal(numerator);
}
let is_negative = numerator.is_negative() ^ denominator.is_negative();
let abs_n = numerator.abs();
let abs_d = denominator.abs();
let den_word = if abs_d == BigInt::from(2) {
(if abs_n.is_one() { "half" } else { "halves" }).to_string()
} else if abs_d == BigInt::from(4) {
(if abs_n.is_one() {
"quarter"
} else {
"quarters"
})
.to_string()
} else {
let mut w = self.to_ordinal(&abs_d)?;
if !abs_n.is_one() {
w.push('s');
}
w
};
let sign = if is_negative { "minus " } else { "" };
Ok(format!(
"{}{} {}",
sign,
self.to_cardinal(&abs_n)?,
den_word
))
}
fn lang_name(&self) -> &str {
"Num2Word_EN_NP"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn currency_adjective(&self, code: &str) -> Option<&str> {
Some(match code {
"AUD" => "Australian",
"BYN" => "Belarusian",
"CAD" => "Canadian",
"EEK" => "Estonian",
"USD" => "US",
"RUB" => "Russian",
"NOK" => "Norwegian",
"MXN" => "Mexican",
"RON" => "Romanian",
"INR" => "Indian",
"HUF" => "Hungarian",
"ISK" => "íslenskar",
"UZS" => "Uzbekistan",
"SAR" => "Saudi",
"JPY" => "Japanese",
"KRW" => "Korean",
_ => return None,
})
}
fn currency_precision(&self, code: &str) -> i64 {
match code {
"BHD" | "KWD" | "OMR" | "JOD" | "TND" | "LYD" | "IQD" => 1000,
_ => 100,
}
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
let form = if n.is_one() { 0 } else { 1 };
forms
.get(form)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
}