use crate::base::{Lang, N2WError, Result};
use crate::currency::CurrencyForms;
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive};
use std::collections::HashMap;
const NEGWORD: &str = "mənfi";
const POINTWORD: &str = "nöqtə";
const DIGITS: [&str; 10] = [
"sıfır", "bir", "iki", "üç", "dörd", "beş", "altı", "yeddi", "səkkiz", "doqquz",
];
const DECIMALS: [&str; 10] = [
"", "on", "iyirmi", "otuz", "qırx", "əlli", "altmış", "yetmiş", "səksən", "doxsan",
];
const VOWELS: [char; 9] = ['a', 'ı', 'o', 'u', 'e', 'ə', 'i', 'ö', 'ü'];
fn power_of_ten(index: usize) -> Option<&'static str> {
Some(match index {
2 => "yüz",
3 => "min",
6 => "milyon",
9 => "milyard",
12 => "trilyon",
15 => "katrilyon",
18 => "kentilyon",
21 => "sekstilyon",
24 => "septilyon",
27 => "oktilyon",
30 => "nonilyon",
33 => "desilyon",
36 => "undesilyon",
39 => "dodesilyon",
42 => "tredesilyon",
45 => "katordesilyon",
48 => "kendesilyon",
51 => "seksdesilyon",
54 => "septendesilyon",
57 => "oktodesilyon",
60 => "novemdesilyon",
63 => "vigintilyon",
_ => return None,
})
}
fn cardinal_suffix(vowel: char) -> Option<&'static str> {
Some(match vowel {
'a' | 'ı' => "ıncı",
'e' | 'ə' | 'i' => "inci",
'o' | 'u' => "uncu",
'ö' | 'ü' => "üncü",
_ => return None,
})
}
fn cardinal_num_suffix(vowel: char) -> Option<&'static str> {
Some(match vowel {
'a' | 'ı' => "cı",
'e' | 'ə' | 'i' => "ci",
'o' | 'u' => "cu",
'ö' | 'ü' => "cü",
_ => return None,
})
}
fn last_vowel(value: &str) -> Option<char> {
value.chars().rev().find(|c| VOWELS.contains(c))
}
fn assert_whole(value: &FloatValue) -> Result<()> {
if value.as_whole_int().is_none() {
return Err(N2WError::Assertion(String::new()));
}
Ok(())
}
fn ordinalize(cardinal: String) -> Result<String> {
let vowel = last_vowel(&cardinal).ok_or_else(|| {
N2WError::Value("AssertionError: last_vowel is None".into())
})?;
let suffix = cardinal_suffix(vowel).ok_or_else(|| N2WError::Key(format!("{}", vowel)))?;
let mut stem = cardinal;
if let Some(last) = stem.chars().next_back() {
if VOWELS.contains(&last) {
let cut = stem.len() - last.len_utf8();
stem.truncate(cut);
}
}
Ok(format!("{}{}", stem, suffix))
}
fn ordinal_num_suffix_for(cardinal: &str) -> Result<&'static str> {
let vowel = last_vowel(cardinal)
.ok_or_else(|| N2WError::Value("AssertionError: last_vowel is None".into()))?;
cardinal_num_suffix(vowel).ok_or_else(|| N2WError::Key(format!("{}", vowel)))
}
fn int_to_word(num_str: &str, leading_zeros: bool) -> Result<String> {
let mut words: Vec<&'static str> = Vec::new();
let reversed: Vec<char> = num_str.chars().rev().collect();
for (index, digit) in reversed.iter().enumerate() {
let digit_int = digit.to_digit(10).ok_or_else(|| {
N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
digit
))
})? as usize;
let remainder_to_3 = index % 3;
if remainder_to_3 == 0 {
if index > 0 {
let end = std::cmp::min(index + 3, reversed.len());
let chunk_is_zero = reversed[index..end].iter().all(|c| *c == '0');
if !chunk_is_zero {
let word =
power_of_ten(index).ok_or_else(|| N2WError::Key(format!("{}", index)))?;
words.insert(0, word);
}
}
if digit_int > 0 {
if !(digit_int == 1 && index == 3) {
words.insert(0, DIGITS[digit_int]);
}
}
} else if remainder_to_3 == 1 {
if digit_int != 0 {
words.insert(0, DECIMALS[digit_int]);
}
} else {
if digit_int > 0 {
words.insert(0, power_of_ten(2).expect("hundreds"));
}
if digit_int > 1 {
words.insert(0, DIGITS[digit_int]);
}
}
}
if num_str == "0" {
words.push(DIGITS[0]);
}
if leading_zeros {
let significant = num_str.trim_start_matches('0');
let int_len = if significant.is_empty() {
1 } else {
significant.chars().count()
};
let zeros_count = num_str.chars().count().saturating_sub(int_len);
for _ in 0..zeros_count {
words.insert(0, DIGITS[0]);
}
}
Ok(words.join(" "))
}
fn py_str_f64(value: f64) -> String {
if value.is_nan() {
return "nan".to_string();
}
if value.is_infinite() {
return if value < 0.0 {
"-inf".to_string()
} else {
"inf".to_string()
};
}
let sci = format!("{:e}", value);
let (mantissa, exponent) = match sci.split_once('e') {
Some(pair) => pair,
None => return sci, };
let decpt = exponent.parse::<i32>().unwrap_or(0) + 1;
let ndigits = mantissa.chars().filter(char::is_ascii_digit).count() as i32;
if decpt <= -4 || decpt > 16 {
let (sign, mantissa) = match mantissa.strip_prefix('-') {
Some(rest) => ("-", rest),
None => ("", mantissa),
};
let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
let mut out = String::from(sign);
out.push_str(&digits[..1]);
if digits.len() > 1 {
out.push('.');
out.push_str(&digits[1..]);
}
let exp = decpt - 1;
out.push_str(&format!(
"e{}{:02}",
if exp < 0 { '-' } else { '+' },
(exp as i64).abs()
));
out
} else {
let frac = std::cmp::max(ndigits - decpt, 1) as usize;
format!("{:.*}", frac, value)
}
}
fn py_str_decimal(value: &BigDecimal) -> String {
let (coefficient, scale) = value.as_bigint_and_exponent();
let sign = if coefficient.is_negative() { "-" } else { "" };
let int_digits = coefficient.abs().to_string(); let exp = -scale; let ndigits = int_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), int_digits),
)
} else if dotplace >= ndigits {
(
format!(
"{}{}",
int_digits,
"0".repeat((dotplace - ndigits) as usize)
),
String::new(),
)
} else {
let cut = dotplace as usize;
(
int_digits[..cut].to_string(),
format!(".{}", &int_digits[cut..]),
)
};
let exppart = if leftdigits == dotplace {
String::new()
} else {
let exp = leftdigits - dotplace;
format!("E{}{}", if exp < 0 { "-" } else { "+" }, exp.abs())
};
format!("{}{}{}{}", sign, intpart, fracpart, exppart)
}
fn cardinal_from_py_str(value_str: &str, is_negative: bool) -> Result<String> {
let mut parts = value_str.split('.');
let integral_part = parts.next().unwrap_or("");
let fraction_part = parts.next().unwrap_or("");
let integral_part = integral_part.strip_prefix('-').unwrap_or(integral_part);
let integral_part_in_words = int_to_word(integral_part, false)?;
let fraction_part_in_words = if fraction_part.is_empty() {
String::new()
} else {
int_to_word(fraction_part, true)?
};
let mut value_in_words = integral_part_in_words;
if !fraction_part.is_empty() {
value_in_words = format!(
"{} {} {}",
value_in_words, POINTWORD, fraction_part_in_words
);
}
if is_negative {
value_in_words = format!("{} {}", NEGWORD, value_in_words);
}
Ok(value_in_words)
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert(
"AZN",
CurrencyForms::new(&["manat", "manat"], &["qəpik", "qəpik"]),
);
m.insert(
"EUR",
CurrencyForms::new(&["avro", "avro"], &["sent", "sent"]),
);
m.insert(
"USD",
CurrencyForms::new(&["dollar", "dollar"], &["sent", "sent"]),
);
m
}
pub struct LangAz {
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl LangAz {
pub fn new() -> Self {
LangAz {
currency_forms: build_currency_forms(),
}
}
}
impl Default for LangAz {
fn default() -> Self {
Self::new()
}
}
impl Lang for LangAz {
fn default_currency(&self) -> &str {
"EUR"
}
fn default_separator(&self) -> &str {
","
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
POINTWORD
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
let value_str = value.to_string();
let integral_part = value_str.strip_prefix('-').unwrap_or(&value_str);
let integral_part_in_words = int_to_word(integral_part, false)?;
if value.is_negative() {
Ok(format!("{} {}", NEGWORD, integral_part_in_words))
} else {
Ok(integral_part_in_words)
}
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
let cardinal = self.to_cardinal(value)?;
ordinalize(cardinal)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
let cardinal = self.to_cardinal(value)?;
let suffix = ordinal_num_suffix_for(&cardinal)?;
Ok(format!("{}-{}", value, suffix))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
let year = self.to_cardinal(&value.abs())?;
if value.is_negative() {
Ok(format!("e.ə. {}", year))
} else {
Ok(year)
}
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let value_str = match value {
FloatValue::Float { value, .. } => py_str_f64(*value),
FloatValue::Decimal { value, .. } => py_str_decimal(value),
};
let lt_zero = match value {
FloatValue::Float { value, .. } => *value < 0.0,
FloatValue::Decimal { value, .. } => value.is_negative(),
};
cardinal_from_py_str(&value_str, lt_zero)
}
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> {
assert_whole(value)?;
let cardinal = self.to_cardinal_float(value, None)?;
ordinalize(cardinal)
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
assert_whole(value)?;
let cardinal = self.to_cardinal_float(value, None)?;
let suffix = ordinal_num_suffix_for(&cardinal)?;
Ok(format!("{}-{}", repr_str, suffix))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
assert_whole(value)?;
let (value_str, lt_zero) = match value {
FloatValue::Float { value: f, .. } => (py_str_f64(f.abs()), *f < 0.0),
FloatValue::Decimal { value: d, .. } => (py_str_decimal(&d.abs()), d.is_negative()),
};
let year = cardinal_from_py_str(&value_str, false)?;
if lt_zero {
Ok(format!("e.ə. {}", year))
} else {
Ok(year)
}
}
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: 'y'".into(),
)),
other => Ok(other),
}
}
fn cardinal_from_decimal(&self, value: &BigDecimal) -> Result<String> {
let float = value
.to_f64()
.ok_or_else(|| N2WError::Value(format!("cannot represent {} as f64", value)))?;
cardinal_from_py_str(&py_str_f64(float), float < 0.0)
}
fn lang_name(&self) -> &str {
"Num2Word_AZ"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
let form = if n.is_one() { 0 } else { 1 };
forms
.get(form)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
}