use crate::base::{Lang as LangTrait, 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;
const ONES: [&str; 10] = [
"",
"ĩmwe",
"igĩrĩ",
"ithatũ",
"inya",
"ithano",
"ithathatũ",
"mũgwanja",
"inyanya",
"kenda",
];
const TENS: [&str; 10] = [
"",
"ikũmi",
"mĩrongo ĩĩrĩ",
"mĩrongo ĩtatũ",
"mĩrongo ĩna",
"mĩrongo ĩtano",
"mĩrongo ĩtandatũ",
"mĩrongo mũgwanja",
"mĩrongo ĩnyanya",
"mĩrongo kenda",
];
const NEGWORD: &str = "njuru ";
const POINTWORD: &str = "ona";
const HUNDRED: &str = "igana";
const THOUSAND: &str = "ngiri";
const MILLION: &str = "milioni";
const ZERO_WORD: &str = "wĩra";
const NA: &str = " na ";
const WA: &str = "wa ";
const FALLBACK_CODE: &str = "KES";
fn split_currency(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();
if scale < 0 {
return Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
d
)));
}
if scale == 0 {
return Ok((digits, BigInt::zero()));
}
let s = digits.abs().to_string();
let scale = scale as usize;
let (int_part, frac_part) = if s.len() > scale {
let (a, b) = s.split_at(s.len() - scale);
(a.to_string(), b.to_string())
} else {
("0".to_string(), format!("{:0>width$}", s, width = scale))
};
let left = int_part
.parse::<BigInt>()
.unwrap_or_else(|_| BigInt::zero());
let head: String = frac_part.chars().take(2).collect();
let right = format!("{:0<2}", head)
.parse::<BigInt>()
.unwrap_or_else(|_| BigInt::zero());
Ok((left, right))
}
pub struct LangKi {
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl LangKi {
pub fn new() -> Self {
const SENTI: [&str; 2] = ["senti", "senti"];
let mut currency_forms = HashMap::with_capacity(3);
currency_forms.insert("KES", CurrencyForms::new(&["shilingi", "shilingi"], &SENTI));
currency_forms.insert("USD", CurrencyForms::new(&["dora", "dora"], &SENTI));
currency_forms.insert("EUR", CurrencyForms::new(&["yuro", "yuro"], &SENTI));
LangKi {
exclude_title: vec!["na".to_string(), "ona".to_string(), "njuru".to_string()],
currency_forms,
}
}
}
impl Default for LangKi {
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(10u32);
let hundred = BigInt::from(100u32);
let thousand = BigInt::from(1_000u32);
let million = BigInt::from(1_000_000u32);
let billion = BigInt::from(1_000_000_000u32);
if number < &ten {
return ONES[number.to_usize().expect("0..9 by band guard")].to_string();
}
if number < &hundred {
let (t, o) = number.div_rem(&ten);
let mut s = TENS[t.to_usize().expect("1..9 by band guard")].to_string();
if !o.is_zero() {
s.push_str(NA);
s.push_str(ONES[o.to_usize().expect("1..9 by divmod")]);
}
return s;
}
if number < &thousand {
let (h, r) = number.div_rem(&hundred);
let mut s = String::new();
if h > BigInt::one() {
s.push_str(ONES[h.to_usize().expect("2..9 by band guard")]);
s.push(' ');
}
s.push_str(HUNDRED);
if !r.is_zero() {
s.push_str(NA);
s.push_str(&int_to_word(&r));
}
return s;
}
if number < &million {
let (t, r) = number.div_rem(&thousand);
let mut s = String::new();
if t > BigInt::one() {
s.push_str(&int_to_word(&t));
s.push(' ');
}
s.push_str(THOUSAND);
if !r.is_zero() {
s.push_str(NA);
s.push_str(&int_to_word(&r));
}
return s;
}
if number < &billion {
let (m, r) = number.div_rem(&million);
let mut s = String::new();
if m > BigInt::one() {
s.push_str(&int_to_word(&m));
s.push(' ');
}
s.push_str(MILLION);
if !r.is_zero() {
s.push_str(NA);
s.push_str(&int_to_word(&r));
}
return s;
}
number.to_string()
}
fn python_float_repr(v: f64) -> String {
if v.is_nan() {
return "nan".to_string();
}
if v.is_infinite() {
return (if v.is_sign_negative() { "-inf" } else { "inf" }).to_string();
}
let sign = if v.is_sign_negative() { "-" } else { "" };
let a = v.abs();
let s = format!("{:e}", a);
let (mant, exp) = s.split_once('e').expect("LowerExp always emits an 'e'");
let exp: i32 = exp.parse().expect("LowerExp emits an integer exponent");
let mut digits: String = mant.chars().filter(|c| *c != '.').collect();
let mut decpt = exp + 1;
let frac_digits = digits.chars().count() as i32 - decpt;
if frac_digits > 0 && a != 0.0 {
let t = format!("{:.*}", frac_digits as usize, a);
let (ip, fp) = t.split_once('.').expect("frac_digits > 0 forces a point");
let all = format!("{}{}", ip, fp);
let trimmed = all.trim_start_matches('0');
if !trimmed.is_empty() {
let lead = all.chars().count() - trimmed.chars().count();
digits = trimmed.to_string();
decpt = ip.chars().count() as i32 - lead as i32;
}
}
let n = digits.chars().count() as i32;
if decpt <= -4 || decpt > 16 {
let e = decpt - 1;
let mut out = String::from(sign);
let mut it = digits.chars();
out.push(it.next().expect("a finite double has at least one digit"));
if n > 1 {
out.push('.');
out.push_str(it.as_str());
}
out.push('e');
out.push(if e < 0 { '-' } else { '+' });
out.push_str(&format!("{:02}", (e as i64).abs()));
out
} else if decpt <= 0 {
format!("{}0.{}{}", sign, "0".repeat((-decpt) as usize), digits)
} else if decpt >= n {
format!("{}{}{}.0", sign, digits, "0".repeat((decpt - n) as usize))
} else {
let k = decpt as usize;
format!(
"{}{}.{}",
sign,
digits.chars().take(k).collect::<String>(),
digits.chars().skip(k).collect::<String>()
)
}
}
fn python_decimal_str(d: &BigDecimal) -> String {
let (unscaled, scale) = d.as_bigint_and_exponent();
let sign = if unscaled.is_negative() { "-" } else { "" };
let int_digits = unscaled.abs().to_string();
let exp: i64 = -scale;
let ndig = int_digits.chars().count() as i64;
let leftdigits = exp + ndig;
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), int_digits),
)
} else if dotplace >= ndig {
(
format!("{}{}", int_digits, "0".repeat((dotplace - ndig) as usize)),
String::new(),
)
} else {
let k = dotplace as usize;
(
int_digits.chars().take(k).collect::<String>(),
format!(".{}", int_digits.chars().skip(k).collect::<String>()),
)
};
let exp_part = if leftdigits == dotplace {
String::new()
} else {
format!("E{:+}", leftdigits - dotplace)
};
format!("{}{}{}{}", sign, intpart, fracpart, exp_part)
}
fn python_str(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, .. } => python_float_repr(*value),
FloatValue::Decimal { value, .. } => python_decimal_str(value),
}
}
fn python_int(s: &str) -> Result<BigInt> {
s.parse::<BigInt>()
.map_err(|_| N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s)))
}
fn cardinal_from_str(n: &str) -> Result<String> {
let n = n.trim();
if let Some(rest) = n.strip_prefix('-') {
let inner = cardinal_from_str(rest)?;
return Ok(format!("{}{}", NEGWORD, inner).trim().to_string());
}
if let Some((left, right)) = n.split_once('.') {
let mut ret = format!("{} {}", int_to_word(&python_int(left)?), 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))
})? as usize;
ret.push(' ');
ret.push_str(if ONES[d].is_empty() {
ZERO_WORD
} else {
ONES[d]
});
}
return Ok(ret.trim().to_string());
}
Ok(int_to_word(&python_int(n)?))
}
impl LangTrait for LangKi {
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!("wa {}", cardinal))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("wa {}", 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 {
"KES"
}
fn default_separator(&self) -> &str {
" "
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
POINTWORD
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
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!("{}{}", WA, self.to_cardinal(value)?))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}{}", WA, 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> {
cardinal_from_str(&python_str(value))
}
fn lang_name(&self) -> &str {
"Num2Word_KI"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
match forms.last() {
None => Ok(String::new()),
Some(last) => Ok(if n.is_one() {
forms[0].clone()
} else {
last.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 (left, right) = split_currency(val)?;
let forms = self
.currency_forms
.get(currency)
.or_else(|| self.currency_forms.get(FALLBACK_CODE))
.expect("FALLBACK_CODE is inserted by new()");
let one = BigInt::one();
let unit = if left != one {
&forms.unit[1]
} else {
&forms.unit[0]
};
let mut result = format!("{} {}", int_to_word(&left), unit);
if cents && !right.is_zero() {
let subunit = if right != one {
&forms.subunit[1]
} else {
&forms.subunit[0]
};
result.push_str(&format!("{}{} {}", separator, int_to_word(&right), subunit));
}
if is_negative {
result = format!("{}{}", NEGWORD, result);
}
Ok(result.trim().to_string())
}
}