use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
use std::str::FromStr;
const NEGWORD: &str = "minus ";
const ONES: [&str; 10] = [
"", "moko", "míbalé", "mísáto", "mínei", "mítáno", "motóbá", "sambo", "mwambe", "libwá",
];
const TENS: [&str; 10] = [
"",
"zómi",
"ntuku míbalé",
"ntuku mísáto",
"ntuku mínei",
"ntuku mítáno",
"ntuku motóbá",
"ntuku sambo",
"ntuku mwambe",
"ntuku libwá",
];
const HUNDRED: &str = "nkama";
const THOUSAND: &str = "nkóto";
const MILLION: &str = "milio";
const POINTWORD: &str = "point";
const DEFAULT_SEPARATOR: &str = " ";
const BASE_DEFAULT_SEPARATOR: &str = ",";
const ZERO_WORD: &str = "zero";
fn idx(n: &BigInt) -> usize {
n.to_usize().unwrap_or(0)
}
fn int_to_word(n: &BigInt) -> String {
if n.is_zero() {
return ZERO_WORD.to_string();
}
if n.is_negative() {
return format!("{}{}", NEGWORD, int_to_word(&n.abs()));
}
let ten = BigInt::from(10u32);
let hundred = BigInt::from(100u32);
let thousand = BigInt::from(1000u32);
let million = BigInt::from(1_000_000u32);
let billion = BigInt::from(1_000_000_000u32);
if n < &ten {
return ONES[idx(n)].to_string();
}
if n < &hundred {
let (tens_val, ones_val) = n.div_rem(&ten);
if ones_val.is_zero() {
return TENS[idx(&tens_val)].to_string();
}
return format!("{} {}", TENS[idx(&tens_val)], ONES[idx(&ones_val)]);
}
if n < &thousand {
let (hundreds_val, remainder) = n.div_rem(&hundred);
let mut result = format!("{} {}", ONES[idx(&hundreds_val)], HUNDRED);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&int_to_word(&remainder));
}
return result;
}
if n < &million {
let (thousands_val, remainder) = n.div_rem(&thousand);
let mut result = format!("{} {}", int_to_word(&thousands_val), THOUSAND);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&int_to_word(&remainder));
}
return result;
}
if n < &billion {
let (millions_val, remainder) = n.div_rem(&million);
let mut result = format!("{} {}", int_to_word(&millions_val), MILLION);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&int_to_word(&remainder));
}
return result;
}
n.to_string()
}
fn parse_int(s: &str) -> Result<BigInt> {
BigInt::from_str(s)
.map_err(|_| N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s)))
}
fn parse_digit(ch: char) -> Result<BigInt> {
match ch.to_digit(10) {
Some(d) => Ok(BigInt::from(d)),
None => Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
ch
))),
}
}
fn float_to_str(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, .. } => python_repr_f64(*value),
FloatValue::Decimal { value, .. } => python_str_decimal(value),
}
}
fn python_repr_f64(f: f64) -> String {
if f.is_nan() {
return "nan".to_string();
}
if f.is_infinite() {
return if f < 0.0 { "-inf" } else { "inf" }.to_string();
}
let neg = f.is_sign_negative();
let s = format!("{:e}", f.abs()); let (mantissa, exp_s) = match s.split_once('e') {
Some(parts) => parts,
None => return s, };
let exp: i32 = exp_s.parse().unwrap_or(0);
let digits: String = mantissa.chars().filter(|c| c.is_ascii_digit()).collect();
let ndigits = digits.len() as i32;
let decpt = exp + 1;
let body = if decpt <= -4 || decpt > 16 {
let mut m = String::new();
m.push_str(&digits[..1]);
if ndigits > 1 {
m.push('.');
m.push_str(&digits[1..]);
}
let e = decpt - 1;
let (esign, eabs) = if e < 0 { ('-', -e) } else { ('+', e) };
format!("{}e{}{:02}", m, esign, eabs)
} else if decpt <= 0 {
format!("0.{}{}", "0".repeat((-decpt) as usize), digits)
} else if decpt >= ndigits {
format!("{}{}.0", digits, "0".repeat((decpt - ndigits) as usize))
} else {
let dp = decpt as usize;
format!("{}.{}", &digits[..dp], &digits[dp..])
};
if neg {
format!("-{}", body)
} else {
body
}
}
fn python_str_decimal(bd: &BigDecimal) -> String {
let (coeff, scale) = bd.as_bigint_and_exponent();
let py_exp: i64 = -scale; let neg = coeff.is_negative(); let int_str = coeff.abs().to_string(); let ndigits = int_str.len() as i64;
let leftdigits = py_exp + ndigits;
let dotplace: i64 = if py_exp <= 0 && leftdigits > -6 {
leftdigits
} else {
1
};
let (intpart, fracpart) = if dotplace <= 0 {
(
"0".to_string(),
format!(".{}{}", "0".repeat((-dotplace) as usize), int_str),
)
} else if dotplace >= ndigits {
(
format!("{}{}", int_str, "0".repeat((dotplace - ndigits) as usize)),
String::new(),
)
} else {
let dp = dotplace as usize;
(int_str[..dp].to_string(), format!(".{}", &int_str[dp..]))
};
let exp = if leftdigits == dotplace {
String::new()
} else {
format!("E{:+}", leftdigits - dotplace)
};
let sign = if neg { "-" } else { "" };
format!("{}{}{}{}", sign, intpart, fracpart, exp)
}
fn cardinal_from_str(s: &str) -> Result<String> {
let n_full = s.trim();
let (n, ret) = match n_full.strip_prefix('-') {
Some(rest) => (rest, NEGWORD),
None => (n_full, ""),
};
if let Some((left, right)) = n.split_once('.') {
let left_int = parse_int(left)?;
let mut out = String::from(ret);
out.push_str(&int_to_word(&left_int));
out.push(' ');
out.push_str(POINTWORD);
out.push(' ');
for ch in right.chars() {
let d = parse_digit(ch)?;
out.push_str(&int_to_word(&d));
out.push(' ');
}
Ok(out.trim().to_string())
} else {
let ni = parse_int(n)?;
Ok(format!("{}{}", ret, int_to_word(&ni)).trim().to_string())
}
}
pub struct LangLn {
currency_forms: HashMap<&'static str, CurrencyForms>,
fallback_forms: CurrencyForms,
}
impl Default for LangLn {
fn default() -> Self {
Self::new()
}
}
impl LangLn {
pub fn new() -> Self {
let mut currency_forms = HashMap::new();
currency_forms.insert(
"CDF",
CurrencyForms::new(&["faranga", "faranga"], &["santimi", "santimi"]),
);
currency_forms.insert(
"USD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
currency_forms.insert(
"EUR",
CurrencyForms::new(&["euro", "euros"], &["cent", "cents"]),
);
let fallback_forms = currency_forms
.get("CDF")
.expect("CURRENCY_FORMS[\"CDF\"] is inserted directly above")
.clone();
LangLn {
currency_forms,
fallback_forms,
}
}
}
impl Lang for LangLn {
fn default_currency(&self) -> &str {
"CDF"
}
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, magnitude) = if value.is_negative() {
(NEGWORD, value.abs())
} else {
("", value.clone())
};
Ok(format!("{}{}", ret, int_to_word(&magnitude))
.trim()
.to_string())
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}-e", self.to_cardinal(value)?))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}.", value))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
cardinal_from_str(&float_to_str(value))
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
self.to_cardinal_float(value, precision_override)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
Ok(format!("{}-e", self.to_cardinal_float(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.to_cardinal_float(value, None)
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
let parsed = python_decimal_parse(s)?;
Ok(parsed)
}
fn inf_result(&self, negative: bool, to: &str) -> Result<String> {
match to {
"ordinal_num" => Ok(format!(
"{}.",
if negative { "-Infinity" } else { "Infinity" }
)),
_ => Err(parse_int("Infinity").unwrap_err()),
}
}
fn nan_result(&self, to: &str) -> Result<String> {
match to {
"ordinal_num" => Ok("NaN.".to_string()),
_ => Err(parse_int("NaN").unwrap_err()),
}
}
fn lang_name(&self) -> &str {
"Num2Word_LN"
}
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 _ = adjective;
let separator = if separator == BASE_DEFAULT_SEPARATOR {
DEFAULT_SEPARATOR
} else {
separator
};
let is_negative = val.is_negative();
let s = match val {
CurrencyValue::Int(v) => {
if is_negative {
v.abs().to_string()
} else {
v.to_string()
}
}
CurrencyValue::Decimal { value: d, .. } => {
if is_negative {
d.abs().to_string()
} else {
d.to_string()
}
}
};
let mut parts = s.split('.');
let part0 = parts.next().unwrap_or("");
let part1 = parts.next();
let left = if part0.is_empty() {
BigInt::zero()
} else {
BigInt::from_str(part0).map_err(|e| N2WError::Value(e.to_string()))?
};
let right = match part1 {
Some(f) if !f.is_empty() => {
let mut two: String = f.chars().take(2).collect();
while two.chars().count() < 2 {
two.push('0');
}
BigInt::from_str(&two).map_err(|e| N2WError::Value(e.to_string()))?
}
_ => BigInt::zero(),
};
let forms = self
.currency_forms
.get(currency)
.unwrap_or(&self.fallback_forms);
let cr1 = &forms.unit;
let cr2 = &forms.subunit;
let one = BigInt::one();
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(&int_to_word(&right));
result.push(' ');
result.push_str(if right != one { &cr2[1] } else { &cr2[0] });
}
if is_negative {
result = format!("{}{}", NEGWORD, result);
}
Ok(result.trim().to_string())
}
}