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;
use num_traits::{One, Signed, Zero};
use std::collections::HashMap;
const LA: &str = "\u{c32}";
const VA: &str = "\u{c35}";
const MODIFIERS: [&str; 17] = [
"\u{20}\u{c4d}\u{20}\u{20}", "\u{c3e}\u{20}", "\u{20}\u{c3f}\u{20}", "\u{20}\u{c40}\u{20}", "\u{20}\u{c41}\u{20}", "\u{20}\u{c42}\u{20}", "\u{20}\u{c43}\u{20}", "\u{20}\u{c44}\u{20}\u{20}", "\u{20}\u{c46}\u{20}", "\u{20}\u{c47}\u{20}", "\u{20}\u{c48}\u{20}", "\u{20}\u{c4a}", "\u{20}\u{c4b}\u{20}", "\u{20}\u{c4c}\u{20}", "\u{20}\u{c01}\u{20}", "\u{20}\u{c02}\u{20}", "\u{20}\u{c03}\u{20}", ];
const HIGH: [(u32, &str); 3] = [(7, "కోట్ల"), (5, "లక్ష"), (3, "వేయి")];
const CURRENCY_FORMS: [(&str, &[&str], &[&str]); 39] = [
("AED", &["dirham", "dirhams"], &["fils", "fils"]),
("AUD", &["dollar", "dollars"], &["cent", "cents"]),
("BHD", &["dinar", "dinars"], &["fils", "fils"]),
("BRL", &["real", "reais"], &["cent", "cents"]),
("BYN", &["rouble", "roubles"], &["kopek", "kopeks"]),
("CAD", &["dollar", "dollars"], &["cent", "cents"]),
("CHF", &["franc", "francs"], &["rappen", "rappen"]),
("CNY", &["yuan", "yuan"], &["fen", "fen"]),
("EEK", &["kroon", "kroons"], &["sent", "senti"]),
("EUR", &["euro", "euros"], &["cent", "cents"]),
("GBP", &["pound", "pounds"], &["penny", "pence"]),
("HKD", &["dollar", "dollars"], &["cent", "cents"]),
("HUF", &["forint", "forint"], &["fillér", "fillér"]),
("INR", &["rupee", "rupees"], &["paisa", "paise"]),
("IQD", &["dinar", "dinars"], &["fils", "fils"]),
("ISK", &["króna", "krónur"], &["aur", "aurar"]),
("JOD", &["dinar", "dinars"], &["fils", "fils"]),
("JPY", &["yen", "yen"], &["sen", "sen"]),
("KRW", &["won", "won"], &["jeon", "jeon"]),
("KWD", &["dinar", "dinars"], &["fils", "fils"]),
("LTL", &["litas", "litas"], &["cent", "cents"]),
("LVL", &["lat", "lats"], &["santim", "santims"]),
("LYD", &["dinar", "dinars"], &["dirham", "dirhams"]),
("MXN", &["peso", "pesos"], &["cent", "cents"]),
("NGN", &["naira", "naira"], &["kobo", "kobo"]),
("NOK", &["krone", "kroner"], &["øre", "øre"]),
("NZD", &["dollar", "dollars"], &["cent", "cents"]),
("OMR", &["rial", "rials"], &["baisa", "baisa"]),
("PLN", &["zloty", "zlotys", "zlotu"], &["grosz", "groszy"]),
("QAR", &["riyal", "riyals"], &["dirham", "dirhams"]),
(
"RON",
&["leu", "lei", "de lei"],
&["ban", "bani", "de bani"],
),
("RUB", &["rouble", "roubles"], &["kopek", "kopeks"]),
("SAR", &["riyal", "riyals"], &["halalah", "halalas"]),
("SEK", &["krona", "kronor"], &["öre", "öre"]),
("SGD", &["dollar", "dollars"], &["cent", "cents"]),
("TND", &["dinar", "dinars"], &["millime", "millimes"]),
("USD", &["dollar", "dollars"], &["cent", "cents"]),
("UZS", &["sum", "sums"], &["tiyin", "tiyins"]),
("ZAR", &["rand", "rand"], &["cent", "cents"]),
];
const LOW: [&str; 100] = [
"తొంభై తొమ్మిది",
"తొంభై ఎనిమిది",
"తొంభై ఏడు",
"తొంభై ఆరు",
"తొంభై అయిదు",
"తొంభై నాలుగు",
"తొంభై మూడు",
"తొంభై రెండు",
"తొంభై ఒకటి",
"తొంభై",
"ఎనభై తొమ్మిది",
"ఎనభై ఎనిమిది",
"ఎనభై ఏడు",
"ఎనభై ఆరు",
"ఎనభై అయిదు",
"ఎనభై నాలుగు",
"ఎనభై మూడు",
"ఎనభై రెండు",
"ఎనభై ఒకటి",
"ఎనభై",
"డెబ్బై తొమ్మిది",
"డెబ్బై ఎనిమిది",
"డెబ్బై ఏడు",
"డెబ్బై ఆరు",
"డెబ్బై అయిదు",
"డెబ్బై నాలుగు",
"డెబ్బై మూడు",
"డెబ్బై రెండు",
"డెబ్బై ఒకటి",
"డెబ్బై",
"అరవై తొమ్మిది",
"అరవై ఎనిమిది",
"అరవై ఏడు",
"అరవై ఆరు",
"అరవై అయిదు",
"అరవై నాలుగు",
"అరవై మూడు",
"అరవై రెండు",
"అరవై ఒకటి",
"అరవై",
"యాభై తొమ్మిది",
"యాభై ఎనిమిది",
"యాభై ఏడు",
"యాభై ఆరు",
"యాభై అయిదు",
"యాభై నాలుగు",
"యాభై మూడు",
"యాభై రెండు",
"యాభై ఒకటి",
"యాభై ", "నలభై తొమ్మిది",
"నలభై ఎనిమిది",
"నలభై ఏడు",
"నలభై ఆరు",
"నలభై అయిదు",
"నలభై నాలుగు",
"నలభై మూడు",
"నలభై రెండు",
"నలభై ఒకటి",
"నలభై",
"ముప్పై తొమ్మిది",
"ముప్పై ఎనిమిది",
"ముప్పై ఏడు",
"ముప్పై ఆరు",
"ముప్పై ఐదు", "ముప్పై నాలుగు",
"ముప్పై మూడు",
"ముప్పై రెండు",
"ముప్పై ఒకటి",
"ముప్పై",
"ఇరవై తొమ్మిది",
"ఇరవై ఎనిమిది",
"ఇరవై ఏడు",
"ఇరవై ఆరు",
"ఇరవై అయిదు",
"ఇరవై నాలుగు",
"ఇరవై మూడు",
"ఇరవై రెండు",
"ఇరవై ఒకటి",
"ఇరవై",
"పందొమ్మిది",
"పధ్ధెనిమిది",
"పదిహేడు",
"పదహారు",
"పదునయిదు",
"పధ్నాలుగు",
"పదమూడు",
"పన్నెండు",
"పదకొండు",
"పది",
"తొమ్మిది",
"ఎనిమిది",
"ఏడు",
"ఆరు",
"అయిదు",
"నాలుగు",
"మూడు",
"రెండు",
"ఒకటి",
"సున్న",
];
fn last_char_is_modifier(text: &str) -> bool {
match text.chars().next_back() {
Some(c) => {
let mut buf = [0u8; 4];
let last: &str = c.encode_utf8(&mut buf);
MODIFIERS.iter().any(|m| *m == last)
}
None => false,
}
}
pub struct LangTe {
cards: Cards,
maxval: BigInt,
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl Default for LangTe {
fn default() -> Self {
Self::new()
}
}
impl LangTe {
pub fn new() -> Self {
let mut cards = Cards::new();
for (n, word) in HIGH.iter() {
cards.insert(BigInt::from(10u8).pow(*n), *word);
}
set_mid_numwords(&mut cards, &[(100, "వంద")]);
set_low_numwords(&mut cards, &LOW);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let currency_forms = CURRENCY_FORMS
.iter()
.map(|(code, unit, subunit)| (*code, CurrencyForms::new(unit, subunit)))
.collect();
LangTe {
cards,
maxval,
currency_forms,
}
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.sign() == num_bigint::Sign::Minus {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
}
impl Lang for LangTe {
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 {
"(-) "
}
fn pointword(&self) -> &str {
"బిందువు "
}
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 {
if last_char_is_modifier(ltext) {
let mut trimmed = ltext.to_string();
trimmed.pop();
(format!("{} {}", trimmed, rtext), lnum + rnum)
} else {
(format!("{}{} {}", ltext, LA, 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 mut outwords = self.to_cardinal(value)?;
if last_char_is_modifier(&outwords) {
outwords.pop();
}
outwords.push_str(VA);
Ok(outwords)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let ord = self.to_ordinal(value)?;
let suffix: String = ord
.chars()
.next_back()
.map(String::from)
.unwrap_or_default();
Ok(format!("{}{}", value, suffix))
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let i = verify_ordinal_float(value, &ordinal_float_repr(value))?;
self.to_ordinal(&i)
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
let i = verify_ordinal_float(value, repr_str)?;
let ord = self.to_ordinal(&i)?;
let suffix: String = ord
.chars()
.next_back()
.map(String::from)
.unwrap_or_default();
Ok(format!("{}{}", repr_str, suffix))
}
fn lang_name(&self) -> &str {
"Num2Word_TE"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn currency_adjective(&self, code: &str) -> Option<&str> {
match code {
"AUD" => Some("Australian"),
"BYN" => Some("Belarusian"),
"CAD" => Some("Canadian"),
"EEK" => Some("Estonian"),
"HUF" => Some("Hungarian"),
"INR" => Some("Indian"),
"ISK" => Some("íslenskar"),
"JPY" => Some("Japanese"),
"KRW" => Some("Korean"),
"MXN" => Some("Mexican"),
"NOK" => Some("Norwegian"),
"RON" => Some("Romanian"),
"RUB" => Some("Russian"),
"SAR" => Some("Saudi"),
"USD" => Some("US"),
"UZS" => Some("Uzbekistan"),
_ => None,
}
}
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()))
}
}
fn verify_ordinal_float(value: &FloatValue, repr: &str) -> Result<BigInt> {
match value.as_whole_int() {
None => Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
repr
))),
Some(i) if i.is_negative() => Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
repr
))),
Some(i) => Ok(i),
}
}
fn ordinal_float_repr(value: &FloatValue) -> String {
match value {
FloatValue::Decimal { value, .. } => crate::strnum::python_decimal_str(value),
FloatValue::Float { value, .. } => {
if value.is_finite() && value.fract() == 0.0 && value.abs() < 1e16 {
format!("{:.1}", value)
} else {
value.to_string()
}
}
}
}