use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
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] = [
"", "un", "dous", "tres", "catro", "cinco", "seis", "sete", "oito", "nove",
];
const TENS: [&str; 10] = [
"", "dez", "vinte", "trinta", "corenta", "cincuenta", "sesenta", "setenta", "oitenta",
"noventa",
];
const HUNDRED: &str = "cento";
const THOUSAND: &str = "mil";
const MILLION: &str = "millón";
const LANG_NAME: &str = "Num2Word_GL";
const CURRENCY_FALLBACK_KEY: &str = "EUR";
const SEPARATOR_DEFAULT: &str = " ";
const SEPARATOR_UNSET: &str = ",";
pub struct LangGl {
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_fallback: CurrencyForms,
}
impl Default for LangGl {
fn default() -> Self {
Self::new()
}
}
impl LangGl {
pub fn new() -> Self {
let currency_forms: HashMap<&'static str, CurrencyForms> = [
(
"EUR",
CurrencyForms::new(&["euro", "euros"], &["céntimo", "céntimos"]),
),
(
"USD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
),
]
.into_iter()
.collect();
let currency_fallback = currency_forms
.get(CURRENCY_FALLBACK_KEY)
.expect("CURRENCY_FALLBACK_KEY is inserted directly above")
.clone();
LangGl {
currency_forms,
currency_fallback,
}
}
fn split_currency(&self, val: &BigDecimal) -> Result<(BigInt, BigInt)> {
let (int_val, scale) = val.as_bigint_and_exponent();
if scale < 0 {
return Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
val
)));
}
if scale == 0 {
return Ok((int_val, BigInt::zero()));
}
let scale = scale as usize;
let digits = int_val.to_string();
let padded = if digits.len() <= scale {
format!("{}{}", "0".repeat(scale + 1 - digits.len()), digits)
} else {
digits
};
let (int_part, frac_part) = padded.split_at(padded.len() - scale);
let left = BigInt::from_str(int_part).expect("int_part is ASCII digits");
let mut frac: String = frac_part.chars().take(2).collect();
while frac.len() < 2 {
frac.push('0');
}
let right = BigInt::from_str(&frac).expect("frac is two ASCII digits");
Ok((left, right))
}
fn int_to_word(&self, number: &BigInt) -> String {
if number.is_zero() {
return if ONES[0].is_empty() {
"zero".to_string()
} else {
ONES[0].to_string()
};
}
if number.is_negative() {
return format!("{}{}", NEGWORD, self.int_to_word(&number.abs()));
}
let ten = BigInt::from(10);
let hundred = BigInt::from(100);
let thousand = BigInt::from(1000);
let million = BigInt::from(1_000_000);
let billion = BigInt::from(1_000_000_000);
if number < &ten {
return ONES[idx(number)].to_string();
}
if number < &hundred {
let tens_val = number / &ten; let ones_val = number % &ten; if ones_val.is_zero() {
return TENS[idx(&tens_val)].to_string();
}
return format!("{} {}", TENS[idx(&tens_val)], ONES[idx(&ones_val)]);
}
if number < &thousand {
let hundreds_val = number / &hundred; let remainder = number % &hundred;
let mut result = format!("{} {}", ONES[idx(&hundreds_val)], HUNDRED);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&self.int_to_word(&remainder));
}
return result;
}
if number < &million {
let thousands_val = number / &thousand; let remainder = number % &thousand;
let mut result = format!("{} {}", self.int_to_word(&thousands_val), THOUSAND);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&self.int_to_word(&remainder));
}
return result;
}
if number < &billion {
let millions_val = number / &million; let remainder = number % &million;
let mut result = format!("{} {}", self.int_to_word(&millions_val), MILLION);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&self.int_to_word(&remainder));
}
return result;
}
number.to_string()
}
fn cardinal_from_py_str(&self, s: &str) -> Result<String> {
let n = s.trim();
let (neg, n) = match n.strip_prefix('-') {
Some(rest) => (true, rest),
None => (false, n),
};
let mut ret = String::new();
if neg {
ret.push_str(NEGWORD);
}
if let Some((left, right)) = n.split_once('.') {
let left_int = BigInt::from_str(left).map_err(|_| int_value_err(left))?;
ret.push_str(&self.int_to_word(&left_int));
ret.push(' ');
ret.push_str(self.pointword());
ret.push(' ');
for ch in right.chars() {
let d = ch
.to_digit(10)
.ok_or_else(|| int_value_err(&ch.to_string()))?;
ret.push_str(&self.int_to_word(&BigInt::from(d)));
ret.push(' ');
}
Ok(ret.trim().to_string())
} else {
let v = BigInt::from_str(n).map_err(|_| int_value_err(n))?;
ret.push_str(&self.int_to_word(&v));
Ok(ret.trim().to_string())
}
}
}
fn idx(n: &BigInt) -> usize {
n.to_usize().unwrap_or(0)
}
fn int_value_err(token: &str) -> N2WError {
N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
token
))
}
fn float_e_parts(fabs: f64) -> (String, i64) {
if fabs == 0.0 {
return ("0".to_string(), 0);
}
let s = format!("{:e}", fabs);
let (mant, exp) = s.split_once('e').expect("{:e} always contains 'e'");
let e0: i64 = exp.parse().expect("{:e} exponent is an integer");
let (ip, fp) = match mant.split_once('.') {
Some((i, f)) => (i, f),
None => (mant, ""),
};
let mut digits = String::with_capacity(ip.len() + fp.len());
digits.push_str(ip);
digits.push_str(fp);
let trimmed = digits.trim_end_matches('0');
let digits = if trimmed.is_empty() {
"0".to_string()
} else {
trimmed.to_string()
};
(digits, e0 + ip.len() as i64 - 1)
}
fn float_to_py_str(f: f64) -> Result<String> {
if !f.is_finite() {
return Err(int_value_err(&format!("{}", f)));
}
let neg = f.is_sign_negative();
let fabs = f.abs();
let (digits, e) = float_e_parts(fabs);
let decpt = e + 1;
if fabs != 0.0 && (decpt <= -4 || decpt > 16) {
return Err(int_value_err(&format!("{:e}", fabs)));
}
let dlen = digits.len() as i64;
let body = if decpt <= 0 {
format!("0.{}{}", "0".repeat((-decpt) as usize), digits)
} else if decpt >= dlen {
format!("{}{}.0", digits, "0".repeat((decpt - dlen) as usize))
} else {
let k = decpt as usize;
format!("{}.{}", &digits[..k], &digits[k..])
};
Ok(if neg { format!("-{}", body) } else { body })
}
fn decimal_to_py_str(d: &BigDecimal) -> Result<String> {
let (mantissa, scale) = d.as_bigint_and_exponent();
let neg = mantissa.is_negative();
let coeff = mantissa.abs().to_string(); let exp: i64 = -scale;
let adjexp = exp + coeff.len() as i64 - 1;
if exp > 0 || adjexp < -6 {
return Err(int_value_err(&format!("{}", d)));
}
let sign = if neg { "-" } else { "" };
if exp == 0 {
return Ok(format!("{}{}", sign, coeff));
}
let scale_u = scale as usize;
let padded = if coeff.len() <= scale_u {
format!("{}{}", "0".repeat(scale_u + 1 - coeff.len()), coeff)
} else {
coeff
};
let (ip, fp) = padded.split_at(padded.len() - scale_u);
Ok(format!("{}{}.{}", sign, ip, fp))
}
impl Lang for LangGl {
fn cardinal_float_entry(
&self,
value: &crate::floatpath::FloatValue,
precision_override: Option<u32>,
) -> crate::base::Result<String> {
self.to_cardinal_float(value, precision_override)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let cardinal = self.cardinal_float_entry(value, None)?;
Ok(format!("{}-o", cardinal))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("{}.", repr_str))
}
fn str_to_number(&self, s: &str) -> Result<crate::strnum::ParsedNumber> {
match crate::strnum::python_decimal_parse(s)? {
crate::strnum::ParsedNumber::Inf { .. } => Err(N2WError::Value(
"invalid literal for int() with base 10: 'Infinity'".into(),
)),
p => Ok(p),
}
}
fn default_currency(&self) -> &str {
"EUR"
}
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, self.int_to_word(&n)).trim().to_string())
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
let cardinal = self.to_cardinal(value)?;
Ok(format!("{}-o", cardinal))
}
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 to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let s = match value {
FloatValue::Float { value, .. } => float_to_py_str(*value)?,
FloatValue::Decimal { value, .. } => decimal_to_py_str(value)?,
};
self.cardinal_from_py_str(&s)
}
fn lang_name(&self) -> &str {
LANG_NAME
}
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 == SEPARATOR_UNSET {
SEPARATOR_DEFAULT
} else {
separator
};
let _ = adjective;
let (left, right, is_negative) = match val {
CurrencyValue::Int(v) => {
let is_negative = v < &BigInt::zero();
(v.abs(), BigInt::zero(), is_negative)
}
CurrencyValue::Decimal { value: d, .. } => {
let is_negative = d < &BigDecimal::zero();
let (left, right) = self.split_currency(&d.abs())?;
(left, right, is_negative)
}
};
let forms = self
.currency_forms
.get(currency)
.unwrap_or(&self.currency_fallback);
let cr1 = &forms.unit;
let cr2 = &forms.subunit;
let one = BigInt::from(1);
let left_str = self.int_to_word(&left);
let mut result = format!(
"{} {}",
left_str,
if left != one { &cr1[1] } else { &cr1[0] }
);
if cents && !right.is_zero() {
let cents_str = self.int_to_word(&right);
result.push_str(separator);
result.push_str(¢s_str);
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())
}
}