use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::ParsedNumber;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::{Signed, ToPrimitive, Zero};
use std::collections::HashMap;
use std::str::FromStr;
const NEGWORD: &str = "minus ";
const ONES: [&str; 10] = [
"", "iray", "roa", "telo", "efatra", "dimy", "enina", "fito", "valo", "sivy",
];
const TENS: [&str; 10] = [
"",
"folo",
"roapolo",
"telopolo",
"efapolo",
"dimampolo",
"enimpolo",
"fitopolo",
"valopolo",
"sivifolo",
];
const HUNDRED: &str = "zato";
const THOUSAND: &str = "arivo";
const MILLION: &str = "tapitrisa";
const BILLION: u32 = 1_000_000_000;
const FALLBACK_CODE: &str = "MGA";
pub struct LangMg {
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl LangMg {
pub fn new() -> Self {
let mut currency_forms = HashMap::new();
currency_forms.insert(
"MGA",
CurrencyForms::new(&["ariary", "ariary"], &["iraimbilanja", "iraimbilanja"]),
);
currency_forms.insert(
"USD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
currency_forms.insert(
"EUR",
CurrencyForms::new(&["euro", "euros"], &["cent", "cents"]),
);
LangMg { currency_forms }
}
fn forms_or_fallback(&self, currency: &str) -> &CurrencyForms {
self.currency_forms.get(currency).unwrap_or_else(|| {
self.currency_forms
.get(FALLBACK_CODE)
.expect("MGA is inserted by new() and never removed")
})
}
}
impl Default for LangMg {
fn default() -> Self {
Self::new()
}
}
fn int_to_word(number: &BigInt) -> String {
if number.is_zero() {
return "zero".to_string();
}
if number.is_negative() {
return format!("{}{}", NEGWORD, int_to_word(&number.abs()));
}
if number >= &BigInt::from(BILLION) {
return number.to_string();
}
int_to_word_small(number.to_u32().expect("checked < 10^9 and >= 0 above"))
}
fn int_to_word_small(number: u32) -> String {
if number < 10 {
ONES[number as usize].to_string()
} else if number < 100 {
let tens_val = (number / 10) as usize;
let ones_val = (number % 10) as usize;
if ones_val == 0 {
TENS[tens_val].to_string()
} else {
format!("{} {}", TENS[tens_val], ONES[ones_val])
}
} else if number < 1_000 {
let hundreds_val = (number / 100) as usize;
let remainder = number % 100;
let mut result = format!("{} {}", ONES[hundreds_val], HUNDRED);
if remainder != 0 {
result.push(' ');
result.push_str(&int_to_word_small(remainder));
}
result
} else if number < 1_000_000 {
let thousands_val = number / 1_000;
let remainder = number % 1_000;
let mut result = format!("{} {}", int_to_word_small(thousands_val), THOUSAND);
if remainder != 0 {
result.push(' ');
result.push_str(&int_to_word_small(remainder));
}
result
} else {
let millions_val = number / 1_000_000;
let remainder = number % 1_000_000;
let mut result = format!("{} {}", int_to_word_small(millions_val), MILLION);
if remainder != 0 {
result.push(' ');
result.push_str(&int_to_word_small(remainder));
}
result
}
}
fn python_float_repr_abs(f: f64) -> String {
if f.is_nan() {
return "nan".to_string();
}
if f.is_infinite() {
return "inf".to_string();
}
if f != 0.0 && (f >= 1e16 || f < 1e-4) {
let s = format!("{:e}", f); let (mant, exp) = s.split_once('e').expect("LowerExp always emits an e");
let exp: i32 = exp.parse().expect("f64 exponent is a small integer");
return format!(
"{}e{}{:02}",
mant,
if exp < 0 { '-' } else { '+' },
exp.abs()
);
}
let s = format!("{}", f);
if s.contains('.') {
s
} else {
format!("{}.0", s)
}
}
impl Lang for LangMg {
fn default_currency(&self) -> &str {
"MGA"
}
fn default_separator(&self) -> &str {
" "
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"point"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
let n = value.to_string();
let (ret, magnitude_str) = match n.strip_prefix('-') {
Some(rest) => (NEGWORD, rest),
None => ("", n.as_str()),
};
let magnitude: BigInt = magnitude_str
.parse()
.expect("decimal rendering of a BigInt minus its sign is always parseable");
Ok(format!("{}{}", ret, int_to_word(&magnitude))
.trim()
.to_string())
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
if value == &BigInt::from(1u32) {
return Ok("voalohany".to_string());
}
Ok(format!("faha-{}", self.to_cardinal(value)?))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}.", value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
self.to_cardinal(value)
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
let n = match value {
FloatValue::Float { value: f, .. } => python_float_repr_abs(f.abs()),
FloatValue::Decimal { value: d, .. } => {
let s = crate::strnum::python_decimal_str(d);
s.strip_prefix('-').map(str::to_string).unwrap_or(s)
}
};
match n.split_once('.') {
Some((left, right)) => {
if !left.bytes().all(|b| b.is_ascii_digit()) || left.is_empty() {
return Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
left
)));
}
if let Some(bad) = right.chars().find(|c| !c.is_ascii_digit()) {
return Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
bad
)));
}
self.to_cardinal_float(value, precision_override)
}
None => {
if n.is_empty() || !n.bytes().all(|b| b.is_ascii_digit()) {
return Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
n
)));
}
let magnitude: BigInt = n.parse().expect("all-ASCII-digit string parses");
let ret = if value.is_negative() { NEGWORD } else { "" };
Ok(format!("{}{}", ret, int_to_word(&magnitude))
.trim()
.to_string())
}
}
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
if value.as_whole_int() == Some(BigInt::from(1u32)) {
return Ok("voalohany".to_string());
}
Ok(format!("faha-{}", self.cardinal_float_entry(value, None)?))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("{}.", repr_str))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
self.cardinal_float_entry(value, None)
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
match crate::strnum::python_decimal_parse(s)? {
ParsedNumber::Inf { .. } => Err(N2WError::Value(
"invalid literal for int() with base 10: 'Infinity'".into(),
)),
ParsedNumber::NaN => Err(N2WError::Value(
"invalid literal for int() with base 10: 'NaN'".into(),
)),
other => Ok(other),
}
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let precision = value.precision();
let is_negative = value.is_negative();
let (int_left, frac): (BigInt, String) = match value {
FloatValue::Float { value: f, .. } => {
let s = format!("{:.*}", precision as usize, f.abs());
match s.split_once('.') {
Some((i, fr)) => (
BigInt::from_str(i).unwrap_or_else(|_| BigInt::zero()),
fr.to_string(),
),
None => (
BigInt::from_str(&s).unwrap_or_else(|_| BigInt::zero()),
String::new(),
),
}
}
FloatValue::Decimal { value: d, .. } => {
let (mant, _scale) =
d.abs().with_scale(precision as i64).as_bigint_and_exponent();
let digits = mant.to_string(); let p = precision as usize;
if digits.len() > p {
let (i, fr) = digits.split_at(digits.len() - p);
(
BigInt::from_str(i).unwrap_or_else(|_| BigInt::zero()),
fr.to_string(),
)
} else {
let mut fr = "0".repeat(p - digits.len());
fr.push_str(&digits);
(BigInt::zero(), fr)
}
}
};
let mut ret = String::new();
if is_negative {
ret.push_str(NEGWORD); }
ret.push_str(&int_to_word(&int_left));
if precision > 0 {
ret.push(' ');
ret.push_str(self.pointword()); for ch in frac.chars() {
ret.push(' ');
let digit = ch.to_digit(10).ok_or_else(|| {
N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
ch
))
})?;
ret.push_str(&int_to_word(&BigInt::from(digit)));
}
}
Ok(ret.trim().to_string())
}
fn lang_name(&self) -> &str {
"Num2Word_MG"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: Option<&str>,
adjective: bool,
) -> Result<String> {
let separator = separator.unwrap_or(self.default_separator());
let separator = if separator == "," { " " } else { separator };
let _ = adjective;
let is_negative = val.is_negative();
let (left, right) = match val {
CurrencyValue::Int(v) => (v.abs(), BigInt::zero()),
CurrencyValue::Decimal { value: d, .. } => {
let d = d.abs();
let whole = d.with_scale(0);
let left = whole.as_bigint_and_exponent().0;
let frac = &d - &whole;
let right = (frac * BigDecimal::from(100))
.with_scale(0)
.as_bigint_and_exponent()
.0;
(left, right)
}
};
let forms = self.forms_or_fallback(currency);
let one = BigInt::from(1u32);
let left_str = int_to_word(&left);
let unit = &forms.unit[if left == one { 0 } else { 1 }];
let mut result = format!("{} {}", left_str, unit);
if cents && !right.is_zero() {
let cents_str = int_to_word(&right);
let subunit = &forms.subunit[if right == one { 0 } else { 1 }];
result.push_str(separator);
result.push_str(¢s_str);
result.push(' ');
result.push_str(subunit);
}
if is_negative {
result = format!("{}{}", NEGWORD, result);
}
Ok(result.trim().to_string())
}
}