use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
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] = [
"", "ta", "khi", "thuh", "lwi", "yeh", "hu", "nwi", "ho", "khwi",
];
const TENS: [&str; 10] = [
"", "tasi", "khisi", "thuhsi", "lwisi", "yehsi", "husi", "nwisi", "hosi",
"khwisi",
];
const HUNDRED: &str = "yah";
const THOUSAND: &str = "klah";
const MILLION: &str = "kade";
const ZERO_WORD: &str = "lah";
pub struct LangKsw {
currency_forms: HashMap<&'static str, CurrencyForms>,
fallback_forms: CurrencyForms,
}
impl LangKsw {
pub fn new() -> Self {
let mut currency_forms = HashMap::new();
currency_forms.insert(
"MMK",
CurrencyForms::new(&["kyat", "kyat"], &["pya", "pya"]),
);
currency_forms.insert(
"USD",
CurrencyForms::new(&["dollar", "dollar"], &["cent", "cent"]),
);
currency_forms.insert("EUR", CurrencyForms::new(&["euro", "euro"], &["cent", "cent"]));
let fallback_forms = currency_forms
.get("MMK")
.expect("CURRENCY_FORMS[\"MMK\"] is inserted directly above")
.clone();
LangKsw {
currency_forms,
fallback_forms,
}
}
}
impl Default for LangKsw {
fn default() -> Self {
Self::new()
}
}
fn int_to_word(number: &BigInt) -> String {
if number.is_zero() {
return ZERO_WORD.to_string();
}
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 {
let i = number.to_usize().expect("proven < 10");
return ONES[i].to_string();
}
if number < &hundred {
let (t, o) = number.div_rem(&ten);
let t = t.to_usize().expect("proven < 10");
let o = o.to_usize().expect("remainder of 10 is < 10");
let mut out = TENS[t].to_string();
if o != 0 {
out.push_str(" di ");
out.push_str(ONES[o]);
}
return out;
}
if number < &thousand {
let (h, r) = number.div_rem(&hundred);
let h = h.to_usize().expect("proven < 10");
let mut out = format!("{} {}", ONES[h], HUNDRED);
if !r.is_zero() {
out.push_str(" di ");
out.push_str(&int_to_word(&r));
}
return out;
}
if number < &million {
let (t, r) = number.div_rem(&thousand);
let mut out = format!("{} {}", int_to_word(&t), THOUSAND);
if !r.is_zero() {
out.push(' ');
out.push_str(&int_to_word(&r));
}
return out;
}
if number < &billion {
let (m, r) = number.div_rem(&million);
let mut out = format!("{} {}", int_to_word(&m), MILLION);
if !r.is_zero() {
out.push(' ');
out.push_str(&int_to_word(&r));
}
return out;
}
number.to_string()
}
fn float_repr(value: f64, precision: u32) -> String {
let neg = value.is_sign_negative();
let abs = value.abs();
let body = if abs.is_finite() && abs != 0.0 {
let es = format!("{:e}", abs);
let epos = es.find('e').expect("LowerExp of a finite nonzero has 'e'");
let exp: i32 = es[epos + 1..]
.parse()
.expect("LowerExp exponent is an integer");
if exp >= 16 || exp <= -5 {
let mantissa = &es[..epos];
let sign = if exp < 0 { "-" } else { "+" };
format!("{}e{}{:02}", mantissa, sign, exp.abs())
} else {
format!("{:.*}", precision as usize, abs)
}
} else {
format!("{:.*}", precision as usize, abs)
};
if neg {
format!("-{}", body)
} else {
body
}
}
fn decimal_repr(value: &BigDecimal) -> String {
let (int_val, scale) = value.as_bigint_and_exponent();
let exp = -scale; let sign = if int_val.is_negative() { "-" } else { "" };
let digits = int_val.magnitude().to_string(); let ndigits = digits.len() as i64;
let leftdigits = exp + ndigits;
let dotplace = if exp <= 0 && leftdigits > -6 {
leftdigits
} else {
1
};
let (intpart, fracpart) = if dotplace <= 0 {
(
"0".to_string(),
format!(".{}{}", "0".repeat((-dotplace) as usize), digits),
)
} else if dotplace >= ndigits {
(
format!("{}{}", digits, "0".repeat((dotplace - ndigits) as usize)),
String::new(),
)
} else {
let dp = dotplace as usize;
(digits[..dp].to_string(), format!(".{}", &digits[dp..]))
};
let expstr = if leftdigits == dotplace {
String::new()
} else {
format!("E{:+}", leftdigits - dotplace)
};
format!("{}{}{}{}", sign, intpart, fracpart, expstr)
}
fn parse_pyint(s: &str) -> Result<BigInt> {
s.parse::<BigInt>().map_err(|_| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s))
})
}
fn pydigit(ch: char) -> Result<u32> {
ch.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", ch))
})
}
impl LangKsw {
fn cardinal_from_str(&self, s: &str) -> Result<String> {
let s = s.trim();
if let Some(rest) = s.strip_prefix('-') {
let inner = self.cardinal_from_str(rest)?;
return Ok(format!("{}{}", NEGWORD, inner).trim().to_string());
}
if let Some(dot) = s.find('.') {
let left = &s[..dot];
let right = &s[dot + 1..];
let mut ret = format!(
"{} {}",
int_to_word(&parse_pyint(left)?),
self.pointword()
);
for ch in right.chars() {
let d = pydigit(ch)?;
ret.push(' ');
ret.push_str(if d == 0 { ZERO_WORD } else { ONES[d as usize] });
}
return Ok(ret.trim().to_string());
}
Ok(int_to_word(&parse_pyint(s)?))
}
}
impl Lang for LangKsw {
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!("{}-tu", cardinal))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("{}-tu", 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 {
"MMK"
}
fn default_separator(&self) -> &str {
" "
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"decimal"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
if value.is_negative() {
let inner = int_to_word(&value.abs());
return Ok(format!("{}{}", NEGWORD, inner).trim().to_string());
}
Ok(int_to_word(value))
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}-tu", self.to_cardinal(value)?))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}-tu", 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, precision } => float_repr(*value, *precision),
FloatValue::Decimal { value, .. } => decimal_repr(value),
};
self.cardinal_from_str(&s)
}
fn lang_name(&self) -> &str {
"Num2Word_KSW"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
if forms.is_empty() {
return Ok(String::new());
}
Ok(if n.is_one() {
forms[0].clone()
} else {
forms[forms.len() - 1].clone()
})
}
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 is_negative = val.is_negative();
let s = match val {
CurrencyValue::Int(v) => v.abs().to_string(),
CurrencyValue::Decimal { value: d, .. } => d.abs().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())
}
}