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::{FromPrimitive, One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const NEGWORD: &str = "minus";
const ONES: [&str; 11] = [
"zero",
"motsi",
"piri",
"tatu",
"china",
"shanu",
"tanhatu",
"nomwe",
"sere",
"pfumbamwe",
"gumi",
];
const ONES_AFTER_NE: [&str; 11] = [
"zero",
"imwe",
"piri",
"tatu",
"china",
"shanu",
"nhatu",
"nomwe",
"sere",
"pfumbamwe",
"gumi",
];
const TENS_FORMS: [&str; 10] = [
"", "", "maviri",
"matatu",
"mana",
"mashanu",
"matanhatu",
"manomwe",
"masere",
"mapfumbamwe",
];
const ORDINALS: [&str; 11] = [
"", "wekutanga",
"wechipiri",
"wechitatu",
"wechina",
"wechishanu",
"wechitanhatu",
"wechinomwe",
"wechisere",
"wechipfumbamwe",
"wegumi",
];
const THOUSANDS_FORMS: [&str; 10] = [
"", "", "viri",
"tatu",
"na",
"shanu",
"tanhatu",
"nomwe",
"sere",
"pfumbamwe",
];
fn get_thousands_form(n: usize) -> &'static str {
match THOUSANDS_FORMS.get(n) {
Some(w) if !w.is_empty() => w,
_ => ONES[n],
}
}
fn drop2(s: &str) -> String {
s.chars().skip(2).collect()
}
fn big(n: u64) -> BigInt {
BigInt::from(n)
}
fn int_to_sn_word(number: &BigInt) -> String {
if number.is_zero() {
return ONES[0].to_string();
}
if number.is_negative() {
return format!("{} {}", NEGWORD, int_to_sn_word(&-number));
}
if number <= &big(10) {
return ONES[number.to_usize().expect("0 < n <= 10")].to_string();
}
if number < &big(20) {
let idx = (number - big(10)).to_usize().expect("11 <= n < 20");
return format!("gumi ne{}", ONES_AFTER_NE[idx]);
}
if number < &big(100) {
let (tens, units) = number.div_rem(&big(10));
let tens = tens.to_usize().expect("2 <= tens <= 9");
let units = units.to_usize().expect("0 <= units <= 9");
if units == 0 {
return format!("makumi {}", TENS_FORMS[tens]);
}
return format!("makumi {} ne{}", TENS_FORMS[tens], ONES_AFTER_NE[units]);
}
if number < &big(1000) {
let (hundreds, remainder) = number.div_rem(&big(100));
let hundreds = hundreds.to_usize().expect("1 <= hundreds <= 9");
if hundreds == 1 {
if remainder.is_zero() {
return "zana".to_string();
}
if remainder < big(10) {
let r = remainder.to_usize().expect("1 <= r < 10");
return format!("zana ne{}", ONES_AFTER_NE[r]);
}
return format!("zana ne{}", int_to_sn_word(&remainder));
}
if remainder.is_zero() {
return format!("mazana {}", TENS_FORMS[hundreds]);
}
return format!(
"mazana {} ne{}",
TENS_FORMS[hundreds],
int_to_sn_word(&remainder)
);
}
if number < &big(10000) {
let (thousands, remainder) = number.div_rem(&big(1000));
let thousands_u = thousands.to_usize().expect("1 <= thousands <= 9");
if thousands_u == 1 {
if remainder.is_zero() {
return "churu".to_string();
}
if remainder < big(10) {
let r = remainder.to_usize().expect("1 <= r < 10");
return format!("churu ne{}", ONES_AFTER_NE[r]);
}
return format!("churu ne{}", int_to_sn_word(&remainder));
}
if remainder.is_zero() {
if thousands_u < 10 {
return format!("zvuru zvi{}", get_thousands_form(thousands_u));
}
return format!("zvuru {}", int_to_sn_word(&thousands));
}
if thousands_u < 10 {
return format!(
"zvuru zvi{} ne{}",
get_thousands_form(thousands_u),
int_to_sn_word(&remainder)
);
}
return format!(
"zvuru {} ne{}",
int_to_sn_word(&thousands),
int_to_sn_word(&remainder)
);
}
if number < &big(100000) {
let (ten_thousands, remainder) = number.div_rem(&big(1000));
if remainder.is_zero() {
return format!("zvuru {}", int_to_sn_word(&ten_thousands));
}
if remainder < big(10) {
let r = remainder.to_usize().expect("1 <= r < 10");
return format!(
"zvuru {} ne{}",
int_to_sn_word(&ten_thousands),
ONES_AFTER_NE[r]
);
}
return format!(
"zvuru {} ne{}",
int_to_sn_word(&ten_thousands),
int_to_sn_word(&remainder)
);
}
if number < &big(1000000) {
let (hundred_thousands, remainder) = number.div_rem(&big(1000));
if remainder.is_zero() {
return format!("zvuru {}", int_to_sn_word(&hundred_thousands));
}
return format!(
"zvuru {} ne{}",
int_to_sn_word(&hundred_thousands),
int_to_sn_word(&remainder)
);
}
if number < &big(1000000000) {
let (millions, remainder) = number.div_rem(&big(1000000));
if millions == BigInt::from(1) {
if remainder.is_zero() {
return "miriyoni".to_string();
}
if remainder < big(10) {
let r = remainder.to_usize().expect("1 <= r < 10");
return format!("miriyoni ne{}", ONES_AFTER_NE[r]);
}
return format!("miriyoni ne{}", int_to_sn_word(&remainder));
}
if remainder.is_zero() {
if millions == BigInt::from(2) {
return "miriyoni mbiri".to_string();
}
if millions < big(10) {
let m = millions.to_usize().expect("3 <= millions <= 9");
return format!("miriyoni {}", ONES[m]);
}
return format!("miriyoni {}", int_to_sn_word(&millions));
}
if millions == BigInt::from(2) {
return format!("miriyoni mbiri ne{}", int_to_sn_word(&remainder));
}
if millions < big(10) {
let m = millions.to_usize().expect("3 <= millions <= 9");
return format!("miriyoni {} ne{}", ONES[m], int_to_sn_word(&remainder));
}
return format!(
"miriyoni {} ne{}",
int_to_sn_word(&millions),
int_to_sn_word(&remainder)
);
}
if number < &big(1000000000000) {
let (billions, remainder) = number.div_rem(&big(1000000000));
if billions == BigInt::from(1) {
if remainder.is_zero() {
return "bhiriyoni".to_string();
}
return format!("bhiriyoni ne{}", int_to_sn_word(&remainder));
}
let base: String = if billions == BigInt::from(2) {
"mbiri".to_string()
} else if billions < big(10) {
ONES[billions.to_usize().expect("3 <= billions <= 9")].to_string()
} else {
int_to_sn_word(&billions)
};
if remainder.is_zero() {
return format!("bhiriyoni {}", base);
}
return format!("bhiriyoni {} ne{}", base, int_to_sn_word(&remainder));
}
let (trillions, remainder) = number.div_rem(&big(1000000000000));
if trillions == BigInt::from(1) {
if remainder.is_zero() {
return "tiriyoni".to_string();
}
return format!("tiriyoni ne{}", int_to_sn_word(&remainder));
}
let base: String = if trillions == BigInt::from(2) {
"mbiri".to_string()
} else if trillions < big(10) {
ONES[trillions.to_usize().expect("3 <= trillions <= 9")].to_string()
} else {
int_to_sn_word(&trillions)
};
if remainder.is_zero() {
return format!("tiriyoni {}", base);
}
format!("tiriyoni {} ne{}", base, int_to_sn_word(&remainder))
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
const SENDI: [&str; 2] = ["sendi", "masendi"];
const DHORA: [&str; 2] = ["dhora", "madhora"];
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert("USD", CurrencyForms::new(&DHORA, &SENDI));
m.insert("ZWL", CurrencyForms::new(&DHORA, &SENDI));
m.insert("ZAR", CurrencyForms::new(&["randi", "marandi"], &SENDI));
m
}
pub struct LangSn {
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl Default for LangSn {
fn default() -> Self {
Self::new()
}
}
impl LangSn {
pub fn new() -> Self {
LangSn {
currency_forms: build_currency_forms(),
}
}
}
fn sn_key_repr(value: &FloatValue) -> String {
match value {
FloatValue::Float { value, precision } => {
if value.is_finite() {
format!("{:.*}", *precision as usize, value)
} else {
format!("{}", value)
}
}
FloatValue::Decimal { value, .. } => {
format!("Decimal('{}')", crate::strnum::python_decimal_str(value))
}
}
}
impl Lang for LangSn {
fn default_currency(&self) -> &str {
"USD"
}
fn default_separator(&self) -> &str {
"ne"
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"poindi"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
Ok(int_to_sn_word(value))
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
if value >= &BigInt::from(1) && value <= &big(10) {
let i = value.to_usize().expect("1 <= n <= 10");
return Ok(ORDINALS[i].to_string());
}
let cardinal = int_to_sn_word(value);
if cardinal.starts_with("gumi") {
Ok(format!("we{}", cardinal))
} else if cardinal.starts_with("makumi") {
Ok(format!("we{}", cardinal))
} else if cardinal.starts_with("zana") {
Ok(format!("we{}", cardinal))
} else if cardinal.starts_with("mazana") {
Ok(format!("we{}", drop2(&cardinal)))
} else if cardinal.starts_with("churu") {
Ok(format!("we{}", cardinal))
} else if cardinal.starts_with("zvuru") {
Ok(format!("we{}", drop2(&cardinal)))
} else {
Ok(format!("we{}", cardinal))
}
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}.", value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
Ok(int_to_sn_word(value))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Decimal { value, .. } => {
let int_part = value.with_scale(0).as_bigint_and_exponent().0;
Ok(int_to_sn_word(&int_part))
}
FloatValue::Float { value, precision } => {
let mut sign = String::new();
let mut num = *value;
if num < 0.0 {
sign = format!("{} ", NEGWORD); num = num.abs();
}
let integer_part = BigInt::from_f64(num.trunc()).ok_or_else(|| {
N2WError::Value(format!("cannot int() {}", num))
})?;
let abs_num = num.abs();
let visible_point =
num.is_finite() && (num == 0.0 || (abs_num >= 1e-4 && abs_num < 1e16));
let decimal_part: String = if visible_point && *precision > 0 {
let s = format!("{:.*}", *precision as usize, num);
match s.split_once('.') {
Some((_, frac)) => frac.to_string(),
None => String::new(),
}
} else {
String::new()
};
let mut result = int_to_sn_word(&integer_part);
if !decimal_part.is_empty() {
result.push(' ');
result.push_str(self.pointword()); for ch in decimal_part.chars() {
let d = ch
.to_digit(10)
.expect("formatted fractional part is all digits");
result.push(' ');
result.push_str(ONES[d as usize]);
}
}
Ok(format!("{}{}", sign, result))
}
}
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
self.to_cardinal_float(value, precision_override)
}
fn neg_zero_decimal(&self, to: &str) -> Option<Result<String>> {
match to {
"cardinal" | "year" => Some(Ok("zero".into())),
"ordinal" => Some(Ok("wezero".into())),
_ => None,
}
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
match value.as_whole_int() {
Some(i) => self.to_ordinal(&i),
None => Err(N2WError::Key(sn_key_repr(value))),
}
}
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> {
match value.as_whole_int() {
Some(i) => self.to_year(&i),
None => Err(N2WError::Key(sn_key_repr(value))),
}
}
fn lang_name(&self) -> &str {
"Num2Word_SN"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_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 mut result: Vec<String> = Vec::new();
let decimal_val: BigDecimal = match val {
CurrencyValue::Int(i) => BigDecimal::from(i.clone()),
CurrencyValue::Decimal { value, .. } => value.clone(),
};
let scaled = &decimal_val * BigDecimal::from(100);
let has_fractional_cents = &scaled - scaled.with_scale(0) != BigDecimal::zero();
if val.is_negative() {
result.push(NEGWORD.to_string());
}
let abs_dec = decimal_val.abs();
let forms = self.currency_forms.get(currency).ok_or_else(|| {
N2WError::NotImplemented(format!("Currency {} not implemented for Shona", currency))
})?;
let major_singular = &forms.unit[0];
let minor_singular = &forms.subunit[0];
let integer_part: BigInt;
let decimal_part: BigInt;
if has_fractional_cents {
integer_part = abs_dec.with_scale(0).as_bigint_and_exponent().0;
if cents {
return Err(N2WError::NotImplemented(
"Num2Word_SN.to_currency: fractional cents render via \
Num2Word_SN.to_cardinal_float, whose repr(float) \
semantics this core does not reproduce; deferring to the \
Python converter"
.into(),
));
}
decimal_part = BigInt::zero(); } else {
match val {
CurrencyValue::Int(i) => {
integer_part = i.abs();
decimal_part = BigInt::zero();
}
CurrencyValue::Decimal { .. } => {
let f = abs_dec.to_f64().ok_or_else(|| {
N2WError::Value(format!("cannot represent {} as f64", abs_dec))
})?;
let ip = f.trunc();
let dp = ((f - ip) * 100.0).round_ties_even();
integer_part = BigInt::from_f64(ip)
.ok_or_else(|| N2WError::Value(format!("cannot int() {}", ip)))?;
decimal_part = BigInt::from_f64(dp)
.ok_or_else(|| N2WError::Value(format!("cannot int() {}", dp)))?;
}
}
}
let ten = BigInt::from(10);
if integer_part.is_one() {
result.push(format!("{} rimwe", major_singular));
} else if integer_part == BigInt::from(2) {
result.push(format!("ma{} maviri", major_singular));
} else if integer_part.is_positive() && integer_part < ten {
let i = integer_part.to_usize().expect("3 <= integer_part <= 9");
result.push(format!("ma{} {}", major_singular, TENS_FORMS[i]));
} else {
result.push(format!(
"ma{} {}",
major_singular,
int_to_sn_word(&integer_part)
));
}
if cents && !decimal_part.is_zero() {
if decimal_part.is_one() {
result.push(format!("{}{} rimwe", separator, minor_singular));
} else {
result.push(format!(
"{}{} {}",
separator,
minor_singular,
int_to_sn_word(&decimal_part)
));
}
}
Ok(result.join(" "))
}
}