use crate::base::{
default_to_cardinal, set_low_numwords, set_mid_numwords, Cards, Lang, N2WError, Result,
};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::{default_to_cardinal_float, FloatValue};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
pub struct LangSq {
cards: Cards,
maxval: BigInt,
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
}
enum SqCents {
Int(BigInt),
Dec(BigDecimal),
}
impl Default for LangSq {
fn default() -> Self {
Self::new()
}
}
fn verify_ordinal(value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
fn ordinal_form(value: &BigInt) -> Option<&'static str> {
match value.to_i64()? {
0 => Some("i zeroi"),
1 => Some("i pari"),
2 => Some("i dyti"),
3 => Some("i treti"),
4 => Some("i katërti"),
5 => Some("i pesti"),
6 => Some("i gjashti"),
7 => Some("i shtati"),
8 => Some("i teti"),
9 => Some("i nënti"),
10 => Some("i dhjeti"),
20 => Some("i njezeti"),
100 => Some("i njëqindi"),
1000 => Some("i një mijti"),
_ => None,
}
}
impl LangSq {
pub fn new() -> Self {
let mut cards = Cards::new();
let high = ["trilion", "miliard", "milion"];
let max = 3 + 3 * high.len() as i64;
let mut n = max;
for word in high.iter() {
if n <= 3 {
break;
}
cards.insert(BigInt::from(10u8).pow(n as u32), *word);
n -= 3;
}
set_mid_numwords(
&mut cards,
&[
(1000, "mijë"),
(100, "qind"),
(90, "nëntëdhjetë"),
(80, "tetëdhjetë"),
(70, "shtatëdhjetë"),
(60, "gjashtëdhjetë"),
(50, "pesëdhjetë"),
(40, "dyzet"),
(30, "tridhjetë"),
(20, "njëzet"),
],
);
set_low_numwords(
&mut cards,
&[
"nëntëmbëdhjetë",
"tetëmbëdhjetë",
"shtatëmbëdhjetë",
"gjashtëmbëdhjetë",
"pesëmbëdhjetë",
"katërmbëdhjetë",
"trembëdhjetë",
"dymbëdhjetë",
"njëmbëdhjetë",
"dhjetë",
"nëntë",
"tetë",
"shtatë",
"gjashtë",
"pesë",
"katër",
"tre",
"dy",
"një",
"zero",
],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let mut currency_forms = HashMap::new();
currency_forms.insert(
"ALL",
CurrencyForms::new(&["lek", "lekë"], &["qindarkë", "qindarkë"]),
);
currency_forms.insert(
"EUR",
CurrencyForms::new(&["euro", "euro"], &["cent", "centë"]),
);
currency_forms.insert(
"USD",
CurrencyForms::new(&["dollar", "dollarë"], &["cent", "centë"]),
);
currency_forms.insert(
"GBP",
CurrencyForms::new(&["paund", "paund"], &["peni", "pence"]),
);
currency_forms.insert(
"CHF",
CurrencyForms::new(&["frank zviceran", "franka zvicer"], &["centim", "centimë"]),
);
currency_forms.insert(
"JPY",
CurrencyForms::new(&["jen", "jenë"], &["sen", "senë"]),
);
currency_forms.insert(
"RUB",
CurrencyForms::new(&["rubël", "rubla"], &["kopek", "kopekë"]),
);
LangSq {
cards,
maxval,
exclude_title: vec!["e".into(), "presje".into(), "minus".into()],
currency_forms,
}
}
fn float2tuple(&self, value: f64) -> Result<(BigInt, usize, BigInt)> {
let pre_f = value.trunc();
let pre = BigInt::from_f64(pre_f).unwrap_or_else(BigInt::zero);
let repr = format!("{}", value);
let precision = repr.split_once('.').map_or(1, |(_, frac)| frac.len());
let scale = match BigInt::from(10).pow(precision as u32).to_f64() {
Some(s) if s.is_finite() => s,
_ => {
return Err(N2WError::Overflow(
"int too large to convert to float".to_string(),
))
}
};
let post_f = (value - pre_f).abs() * scale;
let rounded = post_f.round_ties_even();
let post = if (rounded - post_f).abs() < 0.01 {
BigInt::from_f64(rounded)
} else {
BigInt::from_f64(post_f.floor())
};
Ok((pre, precision, post.unwrap_or_else(BigInt::zero)))
}
fn to_cardinal_float(&self, value: f64) -> Result<String> {
let (pre, precision, post) = self.float2tuple(value)?;
let mut post_s = post.to_string();
let len = post_s.chars().count();
if precision > len {
post_s = "0".repeat(precision - len) + &post_s;
}
let mut out = vec![self.to_cardinal(&pre)?];
if value < 0.0 && pre.is_zero() {
out.insert(0, self.negword().trim().to_string());
}
if precision > 0 {
out.push(self.title(self.pointword()));
}
for ch in post_s.chars().take(precision) {
let digit = ch.to_digit(10).expect("float2tuple yields digits only");
out.push(self.to_cardinal(&BigInt::from(digit))?);
}
Ok(out.join(" "))
}
fn to_cardinal_decimal(&self, value: &BigDecimal) -> Result<String> {
if value.is_zero() {
return Ok("zero".to_string());
}
let truncated = value.with_scale(0);
if (value - &truncated).is_zero() {
return self.to_cardinal(&truncated.as_bigint_and_exponent().0);
}
self.to_cardinal_float(value.to_f64().unwrap_or(f64::NAN))
}
}
impl Lang for LangSq {
fn default_currency(&self) -> &str {
"ALL"
}
fn default_separator(&self) -> &str {
","
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
"minus "
}
fn pointword(&self) -> &str {
"presje"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
let (ctext, cnum) = l;
let (ntext, nnum) = r;
let mut ctext = ctext.to_string();
let hundred = BigInt::from(100);
let thousand = BigInt::from(1000);
let million = BigInt::from(1_000_000);
let twenty = BigInt::from(20);
let ten = BigInt::from(10);
if cnum.is_one() {
if nnum == &thousand {
return ("një mijë".to_string(), thousand);
}
if nnum == &hundred {
return ("njëqind".to_string(), hundred);
}
if nnum < &hundred {
return (ntext.to_string(), nnum.clone());
}
ctext = "një".to_string();
}
if nnum > cnum {
if nnum >= &million {
return (format!("{} {}", ctext, ntext), cnum * nnum);
} else if nnum >= &thousand {
return (format!("{} {}", ctext, ntext), cnum * nnum);
} else if nnum == &hundred {
return (format!("{} {}", ctext, ntext), cnum * nnum);
}
}
if nnum < cnum {
if cnum >= &thousand {
return (format!("{} e {}", ctext, ntext), cnum + nnum);
} else if cnum >= &hundred {
return (format!("{} e {}", ctext, ntext), cnum + nnum);
} else {
if cnum >= &twenty && nnum < &ten {
return (format!("{} e {}", ctext, ntext), cnum + nnum);
}
return (format!("{}{}", ctext, ntext), cnum + nnum);
}
}
(format!("{} {}", ctext, ntext), cnum + nnum)
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
if value.is_zero() {
return Ok("zero".to_string());
}
default_to_cardinal(self, value)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
if let Some(form) = ordinal_form(value) {
return Ok(form.to_string());
}
if value <= &BigInt::from(10) {
return Ok(format!("i {}", self.to_cardinal(value)?));
}
let cardinal = self.to_cardinal(value)?;
Ok(format!("i {}", cardinal))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
verify_ordinal(value)?;
let m10 = value.mod_floor(&BigInt::from(10));
let m100 = value.mod_floor(&BigInt::from(100));
let suffix = if m10 == BigInt::one() && m100 != BigInt::from(11) {
"-shi"
} else if m10 == BigInt::from(2) && m100 != BigInt::from(12) {
"-ri"
} else if m10 == BigInt::from(3) && m100 != BigInt::from(13) {
"-ti"
} else {
"-ti"
};
Ok(format!("{}{}", value, suffix))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
self.to_cardinal(value)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
if let Some(i) = value.as_whole_int() {
if let Some(form) = ordinal_form(&i) {
return Ok(form.to_string());
}
return Ok(format!("i {}", self.to_cardinal(&i)?));
}
Ok(format!("i {}", Lang::to_cardinal_float(self, value, None)?))
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
let i = match value.as_whole_int() {
Some(i) => i,
None => {
return Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
repr_str
)))
}
};
if i.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
repr_str
)));
}
let m10 = i.mod_floor(&BigInt::from(10));
let m100 = i.mod_floor(&BigInt::from(100));
let suffix = if m10 == BigInt::one() && m100 != BigInt::from(11) {
"-shi"
} else if m10 == BigInt::from(2) && m100 != BigInt::from(12) {
"-ri"
} else {
"-ti"
};
Ok(format!("{}{}", repr_str, suffix))
}
fn lang_name(&self) -> &str {
"Num2Word_SQ"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
let Some(first) = forms.first() else {
return Ok(String::new());
};
if forms.len() == 1 {
return Ok(first.clone());
}
if n.is_one() {
Ok(first.clone())
} else {
Ok(forms[1].clone())
}
}
fn to_cardinal_float(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
default_to_cardinal_float(self, value, precision_override)
}
fn cardinal_from_decimal(&self, value: &BigDecimal) -> Result<String> {
self.to_cardinal_decimal(value)
}
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 is_negative = val.is_negative();
let val = match val {
CurrencyValue::Int(v) => CurrencyValue::Int(v.abs()),
CurrencyValue::Decimal {
value: v, is_float, ..
} => CurrencyValue::Decimal {
value: v.abs(),
has_decimal: true,
is_float: *is_float,
},
};
let forms = match self.currency_forms.get(currency) {
Some(forms) => forms,
None => {
return match &val {
CurrencyValue::Int(v) => self.to_cardinal(v),
CurrencyValue::Decimal { value: v, .. } => self.to_cardinal_decimal(v),
};
}
};
let decimal_val = match &val {
CurrencyValue::Int(v) => BigDecimal::from(v.clone()),
CurrencyValue::Decimal { value: v, .. } => v.clone(),
};
let scaled = &decimal_val * BigDecimal::from(100);
let has_fractional_cents = !(&scaled - scaled.with_scale(0)).is_zero();
let (whole, cents_value) = match &val {
CurrencyValue::Decimal { value: _, .. } => {
if has_fractional_cents {
let whole = decimal_val.with_scale(0).as_bigint_and_exponent().0;
let cv = &scaled - BigDecimal::from(&whole * BigInt::from(100));
(whole, SqCents::Dec(cv))
} else {
let whole = decimal_val.with_scale(0).as_bigint_and_exponent().0;
let cv =
(&decimal_val - BigDecimal::from(whole.clone())) * BigDecimal::from(100);
(
whole,
SqCents::Int(cv.with_scale(0).as_bigint_and_exponent().0),
)
}
}
CurrencyValue::Int(v) => (v.clone(), SqCents::Int(BigInt::zero())),
};
if whole.is_positive() {
result.push(self.to_cardinal(&whole)?);
result.push(self.pluralize(&whole, &forms.unit)?);
}
let cents_positive = match ¢s_value {
SqCents::Int(v) => v.is_positive(),
SqCents::Dec(v) => v.is_positive(),
};
let from_float = matches!(&val, CurrencyValue::Decimal { is_float: true, .. });
if cents_positive && cents && !from_float {
} else if cents_positive && cents {
if whole.is_positive() {
let main_part = result.join(" ");
result = vec![format!("{}{}", main_part, separator)];
}
match ¢s_value {
SqCents::Dec(v) => {
result.push(self.to_cardinal_float(v.to_f64().unwrap_or(f64::NAN))?);
let int_cents = v.with_scale(0).as_bigint_and_exponent().0;
result.push(self.pluralize(&int_cents, &forms.subunit)?);
}
SqCents::Int(v) => {
result.push(self.to_cardinal(v)?);
result.push(self.pluralize(v, &forms.subunit)?);
}
}
}
if is_negative {
result.insert(0, self.negword().trim().to_string());
}
Ok(result.join(" "))
}
}