use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::lang_en::LangEn;
use crate::lang_en_aero::{aero_special_of, aero_special_of_decimal, aero_str_to_number};
use crate::strnum::ParsedNumber;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::Signed;
const ICAO_DIGITS: [&str; 10] = [
"zero", "wun", "too", "tree", "fower", "fife", "six", "seven", "ait", "niner",
];
const ICAO_DECIMAL: &str = "decimal";
const ICAO_MINUS: &str = "minus";
pub struct LangEnAeroFaa {
english: LangEn,
digit_table: [&'static str; 10],
decimal_word: &'static str,
minus_word: &'static str,
exclude_title: Vec<String>,
}
impl Default for LangEnAeroFaa {
fn default() -> Self {
Self::new()
}
}
impl LangEnAeroFaa {
pub fn new() -> Self {
LangEnAeroFaa {
english: LangEn::new(),
digit_table: ICAO_DIGITS,
decimal_word: ICAO_DECIMAL,
minus_word: ICAO_MINUS,
exclude_title: vec!["and".into(), "point".into(), "minus".into()],
}
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
fn digits_of(s: &str) -> (bool, String, String) {
let is_negative = s.starts_with('-');
let s = if is_negative { &s[1..] } else { s };
let s = s.replace(',', "").replace('_', "");
let (int_part, frac_part) = match s.find('.') {
Some(i) => (s[..i].to_string(), s[i + 1..].to_string()),
None => (s.clone(), String::new()),
};
let int_part = if int_part.is_empty() {
"0".to_string()
} else {
int_part
};
(is_negative, int_part, frac_part)
}
fn digit_word(&self, ch: char) -> Result<&'static str> {
match ch.to_digit(10) {
Some(d) => Ok(self.digit_table[d as usize]),
None => Err(N2WError::Key(format!("'{}'", ch))),
}
}
fn cardinal_str(&self, s: &str) -> Result<String> {
let (is_negative, int_part, frac_part) = Self::digits_of(s);
let mut words: Vec<&str> = Vec::new();
if is_negative {
words.push(self.minus_word);
}
for ch in int_part.chars() {
if ch.is_ascii_digit() {
words.push(self.digit_word(ch)?);
}
}
if !frac_part.is_empty() {
words.push(self.decimal_word);
for ch in frac_part.chars() {
if ch.is_ascii_digit() {
words.push(self.digit_word(ch)?);
}
}
}
Ok(words.join(" "))
}
}
impl Lang for LangEnAeroFaa {
fn python_maxval(&self) -> Option<num_bigint::BigInt> {
Some(num_bigint::BigInt::from(10u32).pow(306))
}
fn cardinal_float_entry(
&self,
value: &crate::floatpath::FloatValue,
precision_override: Option<u32>,
) -> crate::base::Result<String> {
if let Some(sp) = aero_special_of(value) {
return Ok(sp.cardinal_words().to_string());
}
self.to_cardinal_float(value, precision_override)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
if let Some(sp) = aero_special_of(value) {
return Err(sp.int_error());
}
self.english.ordinal_float_entry(value)
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
if let Some(sp) = aero_special_of(value) {
return Err(sp.int_error());
}
match value.as_whole_int() {
None => Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
repr_str
))),
Some(i) => {
if i.is_negative() {
Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
repr_str
)))
} else {
Ok(i.to_string())
}
}
}
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
self.cardinal_float_entry(value, None)
}
fn to_fraction(&self, numerator: &BigInt, denominator: &BigInt) -> Result<String> {
self.english.to_fraction(numerator, denominator)
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
aero_str_to_number(s)
}
fn negword(&self) -> &str {
"minus "
}
fn pointword(&self) -> &str {
"point"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.cardinal_str(&value.to_string())
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.english.to_ordinal(value)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(value.to_string())
}
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> {
self.cardinal_str(&python_str(value))
}
fn lang_name(&self) -> &str {
"Num2Word_EN_AERO_FAA"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.english.currency_forms(code)
}
fn currency_adjective(&self, code: &str) -> Option<&str> {
self.english.currency_adjective(code)
}
fn currency_precision(&self, code: &str) -> i64 {
self.english.currency_precision(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
self.english.pluralize(n, forms)
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: Option<&str>,
adjective: bool,
) -> Result<String> {
if let CurrencyValue::Decimal { value: d, .. } = val {
if let Some(sp) = aero_special_of_decimal(d) {
return Err(sp.int_error());
}
}
self.english
.to_currency(val, currency, cents, separator, adjective)
}
fn to_cheque(&self, val: &BigDecimal, currency: &str) -> Result<String> {
self.english.to_cheque(val, currency)
}
}
fn python_str(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, precision } => python_float_repr(*value, *precision),
FloatValue::Decimal { value, .. } => python_decimal_format_f(value),
}
}
fn python_float_repr(v: f64, precision: u32) -> String {
if v.is_nan() {
return "nan".to_string();
}
if v.is_infinite() {
return if v < 0.0 { "-inf" } else { "inf" }.to_string();
}
let a = v.abs();
if a != 0.0 && (a >= 1e16 || a < 1e-4) {
let sci = format!("{:e}", v);
let (mantissa, exp) = sci
.split_once('e')
.expect("Rust's {:e} always emits an 'e' separator");
let exp: i32 = exp
.parse()
.expect("Rust's {:e} always emits a decimal exponent");
return format!(
"{}e{}{:02}",
mantissa,
if exp < 0 { '-' } else { '+' },
exp.abs()
);
}
format!("{:.*}", precision as usize, v)
}
fn python_decimal_format_f(d: &BigDecimal) -> String {
let (coefficient, scale) = d.as_bigint_and_exponent();
let exp = -scale; let sign = if coefficient.is_negative() { "-" } else { "" };
let digits = coefficient.abs().to_string();
let ndig = digits.len() as i64;
let leftdigits = exp + ndig;
let dotplace = leftdigits;
let (intpart, fracpart) = if dotplace <= 0 {
(
"0".to_string(),
format!(".{}{}", "0".repeat((-dotplace) as usize), digits),
)
} else if dotplace >= ndig {
(
format!("{}{}", digits, "0".repeat((dotplace - ndig) as usize)),
String::new(),
)
} else {
let k = dotplace as usize;
(digits[..k].to_string(), format!(".{}", &digits[k..]))
};
format!("{}{}{}", sign, intpart, fracpart)
}