use crate::Result;
use crate::currency::vocab::FA as CURRENCY_VOCAB;
use crate::currency::{Currency, assert_minor_unit};
use crate::language::{Kind, Language, form};
use crate::max::Max;
use crate::number::{Number, digits_to_biguint, parse_cardinal, parse_currency, parse_ordinal};
use crate::options::options;
use num_bigint::BigUint;
use num_traits::{ToPrimitive, Zero};
pub const CARDINAL_MAX: Max = Max::Unbounded;
pub const ORDINAL_MAX: Max = Max::Unbounded;
pub const CURRENCY_MAX: Max = Max::Unbounded;
const ONES: [&str; 10] = [
"", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه",
];
const TEENS: [&str; 10] = [
"ده",
"یازده",
"دوازده",
"سیزده",
"چهارده",
"پانزده",
"شانزده",
"هفده",
"هجده",
"نوزده",
];
const TENS: [&str; 10] = [
"",
"",
"بیست",
"سی",
"چهل",
"پنجاه",
"شصت",
"هفتاد",
"هشتاد",
"نود",
];
const HUNDREDS: [&str; 10] = [
"",
"صد",
"دویست",
"سيصد",
"چهار صد",
"پانصد",
"ششصد",
"هفتصد",
"هشتصد",
"نهصد",
];
const THOUSAND: &str = "هزار";
const MILLION: &str = "میلیون";
const MILLIARD: &str = "میلیارد";
const ZERO: &str = "صفر";
const NEGATIVE: &str = "منفى";
const DECIMAL_SEP: &str = "ممیّز";
const ORDINAL_SUFFIX: &str = "م";
const ORDINAL_ONES: [&str; 10] = [
"",
"اول", "دوم", "سوم", "چهارم",
"پنجم",
"ششم",
"هفتم",
"هشتم",
"نهم",
];
const BILLION: u64 = 1_000_000_000;
fn small_to_words(n: u64) -> String {
if n == 0 {
return ZERO.to_string();
}
if n <= 9 {
return ONES[n as usize].to_string();
}
if n <= 19 {
return TEENS[(n - 10) as usize].to_string();
}
if n < 100 {
let ones = (n % 10) as usize;
let tens = (n / 10) as usize;
if ones == 0 {
return TENS[tens].to_string();
}
return format!("{} و {}", TENS[tens], ONES[ones]);
}
if n < 1000 {
let hundreds = (n / 100) as usize;
let remainder = n % 100;
if remainder == 0 {
return HUNDREDS[hundreds].to_string();
}
return format!("{} و {}", HUNDREDS[hundreds], small_to_words(remainder));
}
if n < 1_000_000 {
let multiplier = n / 1000;
let prefix = if multiplier == 1 {
String::new()
} else {
format!("{} ", small_to_words(multiplier))
};
let remainder = n % 1000;
let suffix = if remainder == 0 {
String::new()
} else {
format!(" {}", small_to_words(remainder))
};
return format!("{prefix}{THOUSAND}{suffix}");
}
let multiplier = n / 1_000_000;
let remainder = n % 1_000_000;
let mut result = format!("{} {MILLION}", small_to_words(multiplier));
if remainder != 0 {
result.push_str(" و ");
result.push_str(&small_to_words(remainder));
}
result
}
fn integer_to_words(n: &BigUint) -> String {
if n.is_zero() {
return ZERO.to_string();
}
if let Some(small) = n.to_u64() {
if small < BILLION {
return small_to_words(small);
}
}
let billion = BigUint::from(BILLION);
let multiplier = n / &billion;
let remainder = n % &billion;
let mut result = format!("{} {MILLIARD}", integer_to_words(&multiplier));
if !remainder.is_zero() {
result.push_str(" و ");
result.push_str(&integer_to_words(&remainder));
}
result
}
fn decimal_part_to_words(decimal: &str) -> String {
let mut result = String::new();
let zeros = decimal.bytes().take_while(|&b| b == b'0').count();
for _ in 0..zeros {
if !result.is_empty() {
result.push(' ');
}
result.push_str(ZERO);
}
let remainder = &decimal[zeros..];
if !remainder.is_empty() {
if !result.is_empty() {
result.push(' ');
}
result.push_str(&integer_to_words(&digits_to_biguint(remainder)));
}
result
}
pub fn to_cardinal<'a>(value: impl Into<Number<'a>>) -> Result<String> {
let number = parse_cardinal(&value.into())?;
let mut result = String::new();
if number.negative {
result.push_str(NEGATIVE);
result.push(' ');
}
result.push_str(&integer_to_words(&number.integer));
if let Some(fraction) = &number.fraction {
result.push(' ');
result.push_str(DECIMAL_SEP);
result.push(' ');
result.push_str(&decimal_part_to_words(fraction));
}
Ok(result)
}
fn integer_to_ordinal(n: &BigUint) -> String {
if let Some(small) = n.to_u64() {
if (1..=9).contains(&small) {
return ORDINAL_ONES[small as usize].to_string();
}
}
format!("{}{ORDINAL_SUFFIX}", integer_to_words(n))
}
pub fn to_ordinal<'a>(value: impl Into<Number<'a>>) -> Result<String> {
let n = parse_ordinal(&value.into())?;
Ok(integer_to_ordinal(&n))
}
options! {
pub struct CurrencyOptions {
currency: Currency = Currency::IRR, table = CURRENCY_VOCAB;
}
}
pub fn to_currency<'a>(value: impl Into<Number<'a>>) -> Result<String> {
to_currency_with(value, &CurrencyOptions::default())
}
pub fn to_currency_with<'a>(
value: impl Into<Number<'a>>,
options: &CurrencyOptions,
) -> Result<String> {
let words = CURRENCY_VOCAB.words(options.currency)?;
let money = parse_currency(&value.into(), options.currency.minor_unit_digits())?;
CURRENCY_MAX.check(&money.major, None)?;
assert_minor_unit(money.minor, options.currency)?;
let mut result = String::new();
if money.negative {
result.push_str(NEGATIVE);
result.push(' ');
}
result.push_str(&integer_to_words(&money.major));
result.push(' ');
result.push_str(words.major_at(0));
Ok(result)
}
pub static LANGUAGE: Language = Language {
code: "fa-IR",
name: "Persian (Iran)",
kind: Kind::Full,
cardinal: Some(form!(plain to_cardinal, CARDINAL_MAX)),
ordinal: Some(form!(plain to_ordinal, ORDINAL_MAX)),
currency: Some(form!(with_options to_currency_with, CurrencyOptions, CURRENCY_MAX)),
};