use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, python_decimal_str, ParsedNumber};
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] = [
"",
"'ekahi",
"'elua",
"'ekolu",
"'ehā",
"'elima",
"'eono",
"'ehiku",
"'ewalu",
"'eiwa",
];
const TENS: [&str; 10] = [
"",
"'umi",
"iwakālua",
"kanakolu",
"kanahā",
"kanalima",
"kanaono",
"kanahiku",
"kanawalu",
"kanaiwa",
];
const HUNDRED: &str = "haneli";
const THOUSAND: &str = "kaukani";
const MILLION: &str = "miliona";
fn ten() -> BigInt {
BigInt::from(10)
}
fn one_hundred() -> BigInt {
BigInt::from(100)
}
fn one_thousand() -> BigInt {
BigInt::from(1_000)
}
fn one_million() -> BigInt {
BigInt::from(1_000_000)
}
fn one_billion() -> BigInt {
BigInt::from(1_000_000_000)
}
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 < &ten() {
let i = number.to_usize().expect("bounded < 10");
return ONES[i].to_string();
}
if number < &one_hundred() {
let n = number.to_usize().expect("bounded < 100");
let tens_val = n / 10; let ones_val = n % 10; if ones_val == 0 {
return TENS[tens_val].to_string();
}
return format!("{} {}", TENS[tens_val], ONES[ones_val]);
}
if number < &one_thousand() {
let n = number.to_usize().expect("bounded < 1000");
let hundreds_val = n / 100; let remainder = n % 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 < &one_million() {
let n = number.to_u64().expect("bounded < 10^6");
let thousands_val = n / 1_000; let remainder = n % 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 < &one_billion() {
let n = number.to_u64().expect("bounded < 10^9");
let millions_val = n / 1_000_000; let remainder = n % 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 sci_float_value_error(f: f64) -> Option<N2WError> {
if !f.is_finite() {
let lit = if f.is_nan() { "nan" } else { "inf" };
return Some(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
lit
)));
}
let a = f.abs();
if a != 0.0 && (a >= 1e16 || a < 1e-4) {
let s = format!("{:e}", a);
let (m, e) = s.split_once('e').expect("{:e} always contains an 'e'");
let exp: i32 = e.parse().expect("{:e} exponent is a plain integer");
let lit = if m.contains('.') {
"e".to_string()
} else {
format!("{}e{}{:02}", m, if exp < 0 { '-' } else { '+' }, exp.abs())
};
return Some(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
lit
)));
}
None
}
fn decimal_sci_value_error(s: &str) -> N2WError {
let n = s.strip_prefix('-').unwrap_or(s);
let lit = if n.contains('.') { "E" } else { n };
N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
lit
))
}
const FALLBACK_CURRENCY: &str = "USD";
const SEPARATOR_UNSET: &str = ",";
const SEPARATOR_DEFAULT: &str = " ";
pub struct LangHaw {
forms: HashMap<&'static str, CurrencyForms>,
}
impl LangHaw {
pub fn new() -> Self {
let mut forms = HashMap::with_capacity(2);
forms.insert(
"USD",
CurrencyForms::new(&["kālā", "kālā"], &["keneka", "keneka"]),
);
forms.insert(
"EUR",
CurrencyForms::new(&["euro", "euros"], &["cent", "cents"]),
);
LangHaw { forms }
}
}
impl Default for LangHaw {
fn default() -> Self {
LangHaw::new()
}
}
impl Lang for LangHaw {
fn default_currency(&self) -> &str {
"USD"
}
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, ret) = if value.is_negative() {
(value.abs(), NEGWORD)
} else {
(value.clone(), "")
};
Ok(format!("{}{}", ret, int_to_word(&n)).trim().to_string())
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
if value == &BigInt::from(1) {
return Ok("ka mua".to_string()); }
if value == &BigInt::from(2) {
return Ok("ka lua".to_string()); }
Ok(format!("ka {}", 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 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 cardinal_float_entry(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value: f, .. } => {
if let Some(err) = sci_float_value_error(*f) {
return Err(err);
}
}
FloatValue::Decimal { value: d, .. } => {
let s = python_decimal_str(d);
if s.contains('E') {
return Err(decimal_sci_value_error(&s));
}
}
}
if value.has_visible_point() {
return self.to_cardinal_float(value, precision_override);
}
let i = value
.as_whole_int()
.expect("a point-free, non-scientific value is whole");
self.to_cardinal(&i)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
if let Some(i) = value.as_whole_int() {
if i == BigInt::from(1) {
return Ok("ka mua".to_string());
}
if i == BigInt::from(2) {
return Ok("ka lua".to_string());
}
}
Ok(format!("ka {}", 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 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 lang_name(&self) -> &str {
"Num2Word_HAW"
}
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 == SEPARATOR_UNSET {
SEPARATOR_DEFAULT
} 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 USD")
});
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())
}
}