use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::{float2tuple, FloatValue};
use crate::strnum::ParsedNumber;
use num_bigint::BigInt;
use num_traits::{Signed, ToPrimitive, Zero};
use std::collections::HashMap;
use std::str::FromStr;
const ZERO: &str = "zero";
const NEGWORD: &str = "minus ";
const ONES: [&str; 10] = [
"", "tahi", "rua", "toru", "whā", "rima", "ono", "whitu", "waru", "iwa",
];
const TENS: [&str; 10] = [
"",
"tekau",
"rua tekau",
"toru tekau",
"whā tekau",
"rima tekau",
"ono tekau",
"whitu tekau",
"waru tekau",
"iwa tekau",
];
const HUNDRED: &str = "rau";
const THOUSAND: &str = "mano";
const MILLION: &str = "miriona";
const POINTWORD: &str = "point";
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(10u32) {
return ONES[number.to_usize().unwrap()].to_string();
}
if number < &BigInt::from(100u32) {
let v = number.to_u32().unwrap();
let tens_val = (v / 10) as usize;
let ones_val = (v % 10) as usize;
return if ones_val == 0 {
TENS[tens_val].to_string()
} else {
format!("{} {}", TENS[tens_val], ONES[ones_val])
};
}
if number < &BigInt::from(1_000u32) {
let v = number.to_u32().unwrap();
let hundreds_val = (v / 100) as usize;
let remainder = v % 100;
let mut result = format!("{} {}", ONES[hundreds_val], HUNDRED);
if remainder != 0 {
result.push(' ');
result.push_str(&int_to_word(&BigInt::from(remainder)));
}
return result;
}
if number < &BigInt::from(1_000_000u32) {
let v = number.to_u32().unwrap();
let thousands_val = v / 1_000;
let remainder = v % 1_000;
let mut result = format!("{} {}", int_to_word(&BigInt::from(thousands_val)), THOUSAND);
if remainder != 0 {
result.push(' ');
result.push_str(&int_to_word(&BigInt::from(remainder)));
}
return result;
}
if number < &BigInt::from(1_000_000_000u32) {
let v = number.to_u32().unwrap();
let millions_val = v / 1_000_000;
let remainder = v % 1_000_000;
let mut result = format!("{} {}", int_to_word(&BigInt::from(millions_val)), MILLION);
if remainder != 0 {
result.push(' ');
result.push_str(&int_to_word(&BigInt::from(remainder)));
}
return result;
}
number.to_string()
}
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)
}
}
const FALLBACK_CURRENCY: &str = "NZD";
pub struct LangMi {
forms: HashMap<&'static str, CurrencyForms>,
}
impl LangMi {
pub fn new() -> Self {
let mut forms = HashMap::with_capacity(3);
forms.insert(
"NZD",
CurrencyForms::new(&["tāra", "tāra"], &["hēneti", "hēneti"]),
);
forms.insert(
"USD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
forms.insert(
"EUR",
CurrencyForms::new(&["euro", "euros"], &["cent", "cents"]),
);
LangMi { forms }
}
}
impl Default for LangMi {
fn default() -> Self {
Self::new()
}
}
impl Lang for LangMi {
fn default_currency(&self) -> &str {
"NZD"
}
fn default_separator(&self) -> &str {
" "
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"point"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
let (ret, n) = if value.is_negative() {
(NEGWORD, value.abs())
} else {
("", value.clone())
};
Ok(format!("{}{}", ret, int_to_word(&n)).trim().to_string())
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
if let Some(v) = value.to_i32() {
match v {
1 => return Ok("tuatahi".to_string()),
2 => return Ok("tuarua".to_string()),
3 => return Ok("tuatoru".to_string()),
4 => return Ok("tuawhā".to_string()),
5 => return Ok("tuarima".to_string()),
_ => {}
}
}
Ok(format!("tua {}", 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 let Some(v) = value.as_whole_int().as_ref().and_then(|i| i.to_i32()) {
match v {
1 => return Ok("tuatahi".to_string()),
2 => return Ok("tuarua".to_string()),
3 => return Ok("tuatoru".to_string()),
4 => return Ok("tuawhā".to_string()),
5 => return Ok("tuarima".to_string()),
_ => {}
}
}
Ok(format!("tua {}", 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 (pre, post) = float2tuple(value);
let left = pre.abs();
if precision == 0 {
let mut ret = int_to_word(&left);
if is_negative {
ret = format!("{}{}", NEGWORD, ret);
}
return Ok(ret.trim().to_string());
}
let post_str = post.to_string();
let post_str = format!(
"{}{}",
"0".repeat((precision as usize).saturating_sub(post_str.len())),
post_str
);
let mut ret = format!("{} {}", int_to_word(&left), POINTWORD);
for ch in post_str.chars().take(precision as usize) {
let d = ch.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("non-digit {:?} in fractional part", ch))
})?;
ret.push(' ');
ret.push_str(&int_to_word(&BigInt::from(d)));
}
if is_negative {
ret = format!("{}{}", NEGWORD, ret);
}
Ok(ret.trim().to_string())
}
fn lang_name(&self) -> &str {
"Num2Word_MI"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.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 _ = adjective;
let separator = if separator == "," { " " } else { separator };
let is_negative = val.is_negative();
let s = match val {
CurrencyValue::Int(i) => {
if is_negative { i.abs() } else { i.clone() }.to_string()
}
CurrencyValue::Decimal { value: d, .. } => {
if is_negative { d.abs() } else { d.clone() }.to_string()
}
};
let mut parts = s.split('.');
let p0 = parts.next().unwrap_or("");
let p1 = parts.next();
let left = if p0.is_empty() {
BigInt::zero()
} else {
BigInt::from_str(p0).map_err(|_| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", p0))
})?
};
let right = match p1 {
Some(frac) if !frac.is_empty() => {
let mut f: String = frac.chars().take(2).collect();
while f.chars().count() < 2 {
f.push('0'); }
BigInt::from_str(&f).map_err(|_| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", f))
})?
}
_ => BigInt::zero(),
};
let forms = self.forms.get(currency).unwrap_or_else(|| {
self.forms
.get(FALLBACK_CURRENCY)
.expect("new() always inserts NZD")
});
let (cr1, cr2) = (&forms.unit, &forms.subunit);
let one = BigInt::from(1);
let mut result = format!(
"{} {}",
int_to_word(&left),
if left != one { &cr1[1] } else { &cr1[0] }
);
if cents && !right.is_zero() {
result.push_str(separator);
result.push_str(&format!(
"{} {}",
int_to_word(&right),
if right != one { &cr2[1] } else { &cr2[0] }
));
}
if is_negative {
result = format!("{}{}", NEGWORD, result);
}
Ok(result.trim().to_string())
}
}