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_integer::Integer;
use num_traits::{One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
use std::str::FromStr;
const ONES: [&str; 10] = [
"", "ek", "don", "tin", "char", "panch", "so", "sat", "aath", "nov",
];
const TENS: [&str; 10] = [
"", "dha", "vis", "tis", "chalis", "ponnas", "saath", "sattar", "aaishi", "novod",
];
const HUNDRED: &str = "xambhar";
const THOUSAND: &str = "hozar";
const MILLION: &str = "dosh lakh";
const NEGWORD: &str = "rin ";
const POINTWORD: &str = "punto";
const ZERO_WORD: &str = "xunya";
const ORDINAL_SUFFIX: &str = "vo";
const CURRENCY_FORMS: [(&str, [&str; 2], [&str; 2]); 3] = [
("INR", ["rupya", "rupya"], ["paiso", "paiso"]),
("USD", ["dollar", "dollar"], ["sent", "sent"]),
("EUR", ["yuro", "yuro"], ["sent", "sent"]),
];
const DEFAULT_SEPARATOR: &str = " ";
const BASE_DEFAULT_SEPARATOR: &str = ",";
fn digit_index(n: &BigInt) -> usize {
n.to_usize()
.filter(|i| *i < 10)
.expect("KOK: ones/tens index is 0..=9 by the enclosing range guard")
}
pub struct LangKok {
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
fallback_forms: CurrencyForms,
}
impl Default for LangKok {
fn default() -> Self {
Self::new()
}
}
impl LangKok {
pub fn new() -> Self {
let mut currency_forms = HashMap::with_capacity(CURRENCY_FORMS.len());
for (code, unit, subunit) in CURRENCY_FORMS {
currency_forms.insert(code, CurrencyForms::new(&unit, &subunit));
}
let (_, unit, subunit) = CURRENCY_FORMS[0];
LangKok {
exclude_title: vec!["ani".to_string(), POINTWORD.to_string(), "rin".to_string()],
currency_forms,
fallback_forms: CurrencyForms::new(&unit, &subunit),
}
}
fn int_to_word(&self, number: &BigInt) -> String {
if number.is_zero() {
return ZERO_WORD.to_string();
}
let ten = BigInt::from(10);
if number < &ten {
return ONES[digit_index(number)].to_string();
}
let hundred = BigInt::from(100);
if number < &hundred {
let (t, o) = number.div_mod_floor(&ten);
let mut s = TENS[digit_index(&t)].to_string();
if !o.is_zero() {
s.push_str(" ani ");
s.push_str(ONES[digit_index(&o)]);
}
return s;
}
let thousand = BigInt::from(1000);
if number < &thousand {
let (h, r) = number.div_mod_floor(&hundred);
let mut s = format!("{} {}", ONES[digit_index(&h)], HUNDRED);
if !r.is_zero() {
s.push_str(" ani ");
s.push_str(&self.int_to_word(&r));
}
return s;
}
let million = BigInt::from(1_000_000);
if number < &million {
let (t, r) = number.div_mod_floor(&thousand);
let mut s = format!("{} {}", self.int_to_word(&t), THOUSAND);
if !r.is_zero() {
s.push(' ');
s.push_str(&self.int_to_word(&r));
}
return s;
}
let billion = BigInt::from(1_000_000_000);
if number < &billion {
let (m, r) = number.div_mod_floor(&million);
let mut s = format!("{} {}", self.int_to_word(&m), MILLION);
if !r.is_zero() {
s.push(' ');
s.push_str(&self.int_to_word(&r));
}
return s;
}
number.to_string()
}
fn currency_parts(&self, val: &CurrencyValue) -> Result<(BigInt, BigInt)> {
let d = match val {
CurrencyValue::Int(v) => return Ok((v.abs(), BigInt::zero())),
CurrencyValue::Decimal { value: d, .. } => d.abs(),
};
let (digits, scale) = d.as_bigint_and_exponent();
let ds = digits.to_string();
let ndigits = ds.chars().count() as i64;
if scale < 0 {
return Self::exponential_parts(&ds, ndigits, scale);
}
if scale == 0 {
return Ok((digits, BigInt::zero()));
}
let (int_part, frac): (String, String) = if ndigits > scale {
let cut = (ndigits - scale) as usize;
(ds.chars().take(cut).collect(), ds.chars().skip(cut).collect())
} else {
(
"0".to_string(),
format!("{}{}", "0".repeat((scale - ndigits) as usize), ds),
)
};
Ok((parse_digits(&int_part), first_two_cents(&frac)))
}
fn exponential_parts(ds: &str, ndigits: i64, scale: i64) -> Result<(BigInt, BigInt)> {
let mut chars = ds.chars();
let first = chars.next().unwrap_or('0');
match ndigits {
1 => {
Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}e+{:02}'",
first, -scale
)))
}
2 => {
let second = chars.next().unwrap_or('0');
Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}e'",
second
)))
}
_ => {
let two: String = chars.take(2).collect();
Ok((parse_digits(&first.to_string()), parse_digits(&two)))
}
}
}
fn cardinal_of_pystr(&self, n: &str) -> Result<String> {
let n = n.trim();
if let Some(rest) = n.strip_prefix('-') {
let inner = self.cardinal_of_pystr(rest)?;
return Ok(format!("{}{}", NEGWORD, inner).trim().to_string());
}
match n.split_once('.') {
Some((left, right)) => {
let mut ret = format!("{} {}", self.int_to_word(&py_int(left)?), POINTWORD);
for ch in right.chars() {
let d = py_int(&ch.to_string())?
.to_usize()
.expect("KOK: int(single decimal char) is 0..=9");
let word = if ONES[d].is_empty() { ZERO_WORD } else { ONES[d] };
ret.push(' ');
ret.push_str(word);
}
Ok(ret.trim().to_string())
}
None => Ok(self.int_to_word(&py_int(n)?)),
}
}
}
fn value_python_str(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, precision } => python_float_repr(*value, *precision),
FloatValue::Decimal { value, .. } => python_decimal_str(value),
}
}
fn python_float_repr(f: f64, precision: u32) -> String {
let a = f.abs();
if a != 0.0 && (a >= 1e16 || a < 1e-4) {
let s = format!("{:e}", f);
let (m, e) = s.split_once('e').expect("LowerExp always contains 'e'");
let exp: i64 = e.parse().expect("LowerExp exponent is an integer");
format!("{}e{}{:02}", m, if exp < 0 { '-' } else { '+' }, exp.abs())
} else {
format!("{:.*}", precision as usize, f)
}
}
fn py_int(s: &str) -> Result<BigInt> {
BigInt::from_str(s).map_err(|_| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s))
})
}
fn parse_digits(s: &str) -> BigInt {
BigInt::from_str(s).expect("KOK: slice of a BigInt decimal rendering is all digits")
}
fn first_two_cents(frac: &str) -> BigInt {
let mut two: String = frac.chars().take(2).collect();
while two.chars().count() < 2 {
two.push('0');
}
parse_digits(&two)
}
impl Lang for LangKok {
fn default_currency(&self) -> &str {
"INR"
}
fn default_separator(&self) -> &str {
" "
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"punto"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
if value.is_negative() {
let inner = self.int_to_word(&value.abs());
return Ok(format!("{}{}", NEGWORD, inner).trim().to_string());
}
Ok(self.int_to_word(value))
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
let cardinal = self.to_cardinal(value)?;
Ok(format!("{}{}", cardinal, ORDINAL_SUFFIX))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}{}", value, ORDINAL_SUFFIX))
}
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> {
self.cardinal_of_pystr(&value_python_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> {
let cardinal = self.cardinal_float_entry(value, None)?;
Ok(format!("{}{}", cardinal, 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> {
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_KOK"
}
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());
}
if n.is_one() {
Ok(forms[0].clone())
} else {
Ok(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 _ = adjective;
let separator = if separator == BASE_DEFAULT_SEPARATOR {
DEFAULT_SEPARATOR
} else {
separator
};
let is_negative = val.is_negative();
let (left, right) = self.currency_parts(val)?;
let forms = self
.currency_forms
.get(currency)
.unwrap_or(&self.fallback_forms);
let unit = if left.is_one() {
&forms.unit[0]
} else {
&forms.unit[1]
};
let mut result = format!("{} {}", self.int_to_word(&left), unit);
if cents && !right.is_zero() {
let subunit = if right.is_one() {
&forms.subunit[0]
} else {
&forms.subunit[1]
};
result.push_str(separator);
result.push_str(&self.int_to_word(&right));
result.push(' ');
result.push_str(subunit);
}
if is_negative {
result = format!("{}{}", NEGWORD, result);
}
Ok(result.trim().to_string())
}
}