use crate::Result;
use crate::currency::vocab::JA as CURRENCY_VOCAB;
use crate::currency::{Currency, assert_minor_unit};
use crate::language::{Kind, Language, form};
use crate::max::Max;
use crate::number::{Number, parse_cardinal, parse_currency, parse_ordinal, split_groups};
use crate::options::options;
use num_bigint::BigUint;
use num_traits::{ToPrimitive, Zero};
const ONES: [&str; 10] = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
const SCALES: &[&str] = &[
"万", "億", "兆", "京", "垓", "秭", "穣", "溝", "澗", "正", "載", "極", "恒河沙", "阿僧祇", "那由他", "不可思議", "無量大数", ];
pub const CARDINAL_MAX: Max = Max::myriad(SCALES.len() as u32);
pub const ORDINAL_MAX: Max = Max::myriad(SCALES.len() as u32);
pub const CURRENCY_MAX: Max = Max::myriad(SCALES.len() as u32);
const ZERO: &str = "零";
const NEGATIVE: &str = "マイナス";
const DECIMAL_SEP: &str = "点";
const ORDINAL_PREFIX: &str = "第";
const TEN: &str = "十";
const HUNDRED: &str = "百";
const THOUSAND: &str = "千";
fn push_place(result: &mut String, digit: usize, scale: &str) {
if digit > 0 {
if digit != 1 {
result.push_str(ONES[digit]);
}
result.push_str(scale);
}
}
fn build_segment(n: u32) -> String {
if n == 0 {
return String::new();
}
let ones = (n % 10) as usize;
let tens = ((n / 10) % 10) as usize;
let hundreds = ((n / 100) % 10) as usize;
let thousands = (n / 1000) as usize;
let mut result = String::new();
push_place(&mut result, thousands, THOUSAND);
push_place(&mut result, hundreds, HUNDRED);
push_place(&mut result, tens, TEN);
if ones > 0 {
result.push_str(ONES[ones]);
}
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 < 10_000 {
return build_segment(small as u32);
}
if small < 100_000_000 {
let man = (small / 10_000) as u32;
let remainder = (small % 10_000) as u32;
let mut result = if man == 1 {
format!("{}{}", ONES[1], SCALES[0]) } else {
format!("{}{}", build_segment(man), SCALES[0])
};
if remainder > 0 {
result.push_str(&build_segment(remainder));
}
return result;
}
}
build_large_number_words(n)
}
fn build_large_number_words(n: &BigUint) -> String {
let segments = split_groups(n, 10_000);
let mut result = String::new();
for (i, &segment) in segments.iter().enumerate().rev() {
if segment == 0 {
continue;
}
if i > 0 {
if segment == 1 {
result.push_str(ONES[1]);
} else {
result.push_str(&build_segment(segment));
}
result.push_str(SCALES[i - 1]);
} else {
result.push_str(&build_segment(segment));
}
}
if result.is_empty() {
ZERO.to_string()
} else {
result
}
}
fn decimal_part_to_words(decimal: &str) -> String {
let mut result = String::new();
for digit in decimal.bytes() {
let digit = usize::from(digit - b'0');
if digit == 0 {
result.push_str(ZERO);
} else {
result.push_str(ONES[digit]);
}
}
result
}
pub fn to_cardinal<'a>(value: impl Into<Number<'a>>) -> Result<String> {
let number = parse_cardinal(&value.into())?;
CARDINAL_MAX.check(&number.integer, None)?;
let mut result = String::new();
if number.negative {
result.push_str(NEGATIVE);
}
result.push_str(&integer_to_words(&number.integer));
if let Some(fraction) = &number.fraction {
result.push_str(DECIMAL_SEP);
result.push_str(&decimal_part_to_words(fraction));
}
Ok(result)
}
fn integer_to_ordinal(n: &BigUint) -> String {
format!("{ORDINAL_PREFIX}{}", integer_to_words(n))
}
pub fn to_ordinal<'a>(value: impl Into<Number<'a>>) -> Result<String> {
let n = parse_ordinal(&value.into())?;
ORDINAL_MAX.check(&n, None)?;
Ok(integer_to_ordinal(&n))
}
options! {
pub struct CurrencyOptions {
currency: Currency = Currency::JPY, 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);
}
if money.major.is_zero() {
result.push_str(ZERO);
} else {
result.push_str(&integer_to_words(&money.major));
}
result.push_str(words.major_at(0));
Ok(result)
}
pub static LANGUAGE: Language = Language {
code: "ja-JP",
name: "Japanese (Japan)",
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)),
};