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 = "\u{1021}\u{1014}\u{102F}\u{1010}\u{103A}\u{0020}";
const ONES: [&str; 10] = [
"\u{101E}\u{102F}\u{100A}", "\u{1010}\u{1005}\u{103A}", "\u{1014}\u{103E}\u{1005}\u{103A}", "\u{101E}\u{102F}\u{1036}\u{1038}", "\u{101C}\u{1031}\u{1038}", "\u{1004}\u{102B}\u{1038}", "\u{1001}\u{103C}\u{1031}\u{102C}\u{1000}\u{103A}", "\u{1001}\u{102F}\u{1014}\u{1005}\u{103A}", "\u{101B}\u{103E}\u{1005}\u{103A}", "\u{1000}\u{102D}\u{102F}\u{1038}", ];
const TEN: &str = "\u{1010}\u{1005}\u{103A}\u{1006}\u{101A}\u{103A}";
const TEEN_PREFIX: &str = "\u{1010}\u{1005}\u{103A}\u{1006}\u{101A}\u{1037}\u{103A}";
const SAY: &str = "\u{1006}\u{101A}\u{103A}";
const DOT: &str = "\u{1037}";
const HUNDRED: &str = "\u{101B}\u{102C}"; const THOUSAND: &str = "\u{1011}\u{1031}\u{102C}\u{1004}\u{103A}"; const TEN_THOUSAND: &str = "\u{101E}\u{1031}\u{102C}\u{1004}\u{103A}\u{1038}"; const LAKH: &str = "\u{101E}\u{102D}\u{1014}\u{103A}\u{1038}"; const MILLION: &str = "\u{101E}\u{1014}\u{103A}\u{1038}";
const KUTAY: &str = "\u{0020}\u{1000}\u{102F}\u{100B}\u{1031}";
const ORDINAL_SUFFIX: &str = "\u{1019}\u{103C}\u{1031}\u{102C}\u{1000}\u{103A}";
const MMK_UNIT: &str = "\u{1000}\u{103B}\u{1015}\u{103A}";
const MMK_SUB: &str = "\u{1015}\u{103C}\u{102C}\u{1038}";
const USD_UNIT: &str = "\u{1012}\u{1031}\u{102B}\u{103A}\u{101C}\u{102C}";
const EUR_UNIT: &str = "\u{101A}\u{1030}\u{101B}\u{102D}\u{102F}";
const CENT: &str = "\u{1006}\u{1004}\u{1037}\u{103A}";
const YEAR_SUFFIX: &str = "\u{0020}\u{1001}\u{102F}\u{1014}\u{103E}\u{1005}\u{103A}";
fn ones_at(n: &BigInt) -> &'static str {
let i = n
.to_usize()
.expect("guarded by the caller's range check: 0..=9");
ONES[i]
}
fn int_to_word(number: &BigInt) -> String {
let ten = BigInt::from(10);
let hundred = BigInt::from(100);
let thousand = BigInt::from(1_000);
let ten_thousand = BigInt::from(10_000);
let lakh = BigInt::from(100_000);
let million = BigInt::from(1_000_000);
let ten_million = BigInt::from(10_000_000);
let billion = BigInt::from(1_000_000_000);
if number.is_zero() {
return ONES[0].to_string();
}
if number.is_negative() {
return format!("{}{}", NEGWORD, int_to_word(&number.abs()));
} else if *number < ten {
return ones_at(number).to_string();
} else if *number == ten {
return TEN.to_string();
} else if *number < BigInt::from(20) {
return format!("{}{}", TEEN_PREFIX, ones_at(&(number - &ten)));
} else if *number < hundred {
let tens_val = number / &ten;
let ones_val = number % &ten;
let tens_word = format!("{}{}", ones_at(&tens_val), SAY);
if ones_val.is_zero() {
return tens_word;
} else {
return format!("{}{}{}", tens_word, DOT, ones_at(&ones_val));
}
} else if *number < thousand {
let hundreds_val = number / &hundred;
let remainder = number % &hundred;
let mut result = format!("{}{}", ones_at(&hundreds_val), HUNDRED);
if !remainder.is_zero() {
result.push_str(DOT);
result.push_str(&int_to_word(&remainder));
}
return result;
} else if *number < ten_thousand {
let thousands_val = number / &thousand;
let remainder = number % &thousand;
let mut result = format!("{}{}", ones_at(&thousands_val), THOUSAND);
if !remainder.is_zero() {
result.push_str(DOT);
result.push_str(&int_to_word(&remainder));
}
return result;
} else if *number < lakh {
let ten_thousands_val = number / &ten_thousand;
let remainder = number % &ten_thousand;
let mut result = format!("{}{}", ones_at(&ten_thousands_val), TEN_THOUSAND);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&int_to_word(&remainder));
}
return result;
} else if *number < million {
let hundred_thousands_val = number / &lakh;
let remainder = number % &lakh;
let mut result = format!("{}{}", ones_at(&hundred_thousands_val), LAKH);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&int_to_word(&remainder));
}
return result;
} else if *number < ten_million {
let millions_val = number / &million;
let remainder = number % &million;
let mut result = format!("{}{}", ones_at(&millions_val), MILLION);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&int_to_word(&remainder));
}
return result;
} else if *number < billion {
let ten_millions_val = number / &ten_million;
let remainder = number % &ten_million;
let mut result = format!("{}{}", int_to_word(&ten_millions_val), KUTAY);
if !remainder.is_zero() {
result.push(' ');
result.push_str(&int_to_word(&remainder));
}
return result;
} else {
return 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 parse_pyint(s: &str) -> Result<BigInt> {
BigInt::from_str(s)
.map_err(|_| N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s)))
}
pub struct LangMy {
forms: HashMap<&'static str, CurrencyForms>,
}
impl LangMy {
pub fn new() -> Self {
let mut forms = HashMap::new();
forms.insert(
"MMK",
CurrencyForms::new(&[MMK_UNIT, MMK_UNIT], &[MMK_SUB, MMK_SUB]),
);
forms.insert(
"USD",
CurrencyForms::new(&[USD_UNIT, USD_UNIT], &[CENT, CENT]),
);
forms.insert(
"EUR",
CurrencyForms::new(&[EUR_UNIT, EUR_UNIT], &[CENT, CENT]),
);
LangMy { forms }
}
fn cardinal_from_str(&self, s: &str) -> Result<String> {
let s = s.trim();
let (neg, n) = match s.strip_prefix('-') {
Some(rest) => (NEGWORD, rest),
None => ("", s),
};
if let Some((left, right)) = n.split_once('.') {
let mut ret = format!(
"{}{} {} ",
neg,
int_to_word(&parse_pyint(left)?),
self.pointword()
);
for ch in right.chars() {
let d = 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(d)));
ret.push(' ');
}
return Ok(ret.trim().to_string());
}
Ok(format!("{}{}", neg, int_to_word(&parse_pyint(n)?))
.trim()
.to_string())
}
}
impl Default for LangMy {
fn default() -> Self {
Self::new()
}
}
impl Lang for LangMy {
fn default_currency(&self) -> &str {
"MMK"
}
fn default_separator(&self) -> &str {
" "
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"ဒသမ"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
let (ret, magnitude) = if value.is_negative() {
(NEGWORD, value.abs())
} else {
("", value.clone())
};
let joined = format!("{}{}", ret, int_to_word(&magnitude));
Ok(joined.trim().to_string())
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}{}", self.to_cardinal(value)?, ORDINAL_SUFFIX))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}{}", value, ORDINAL_SUFFIX))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}{}", self.to_cardinal(value)?, YEAR_SUFFIX))
}
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, .. } => python_decimal_str(value),
};
self.cardinal_from_str(&s)
}
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!(
"{}{}",
self.cardinal_float_entry(value, None)?,
ORDINAL_SUFFIX
))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("{}{}", repr_str, ORDINAL_SUFFIX))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
Ok(format!(
"{}{}",
self.cardinal_float_entry(value, None)?,
YEAR_SUFFIX
))
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
python_decimal_parse(s)
}
fn inf_result(&self, _negative: bool, _to: &str) -> Result<String> {
Err(N2WError::Value(
"invalid literal for int() with base 10: 'Infinity'".into(),
))
}
fn nan_result(&self, _to: &str) -> Result<String> {
Err(N2WError::Value(
"invalid literal for int() with base 10: 'NaN'".into(),
))
}
fn lang_name(&self) -> &str {
"Num2Word_MY"
}
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 is_negative = val.is_negative();
let text = match val {
CurrencyValue::Int(i) => i.abs().to_string(),
CurrencyValue::Decimal { value: d, .. } => d.abs().to_string(),
};
let parts: Vec<&str> = text.split('.').collect();
let left = if parts[0].is_empty() {
BigInt::zero()
} else {
BigInt::from_str(parts[0]).map_err(|e| N2WError::Value(e.to_string()))?
};
let right = match parts.get(1) {
Some(frac) if !frac.is_empty() => {
let mut s: String = frac.chars().take(2).collect();
while s.chars().count() < 2 {
s.push('0');
}
BigInt::from_str(&s).map_err(|e| N2WError::Value(e.to_string()))?
}
_ => BigInt::zero(),
};
let forms = self
.forms
.get(currency)
.unwrap_or_else(|| self.forms.get("MMK").expect("MMK inserted in new()"));
let mut result = format!("{} {}", int_to_word(&left), forms.unit[0]);
if cents && !right.is_zero() {
result.push_str(separator);
result.push_str(&int_to_word(&right));
result.push(' ');
result.push_str(&forms.subunit[0]);
}
Ok(format!(
"{}{}",
if is_negative { NEGWORD } else { "" },
result
))
}
}