use core::borrow::Borrow;
use alloc::format;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec::Vec;
use alloc::collections::btree_map::BTreeMap;
use regex::Regex;
use serde_json::from_str;
use crate::session::Session;
use crate::tokinizer::RuleItemList;
use crate::tokinizer::RuleType;
use crate::types::CurrencyInfo;
use crate::types::TimeOffset;
use crate::tokinizer::Tokinizer;
use crate::tokinizer::TokenInfo;
use crate::tokinizer::RULE_FUNCTIONS;
use crate::constants::*;
pub type LanguageData<T> = BTreeMap<String, T>;
pub type CurrencyData<T> = BTreeMap<Rc<CurrencyInfo>, T>;
#[derive(Default)]
#[derive(Clone)]
#[derive(Debug)]
#[derive(PartialEq)]
pub struct MoneyConfig {
pub remove_fract_if_zero: bool,
pub use_fract_rounding: bool
}
#[derive(Default)]
#[derive(Clone)]
#[derive(Debug)]
#[derive(PartialEq)]
pub struct NumberConfig {
pub decimal_digits: u8,
pub remove_fract_if_zero: bool,
pub use_fract_rounding: bool
}
#[derive(Default)]
#[derive(Clone)]
#[derive(Debug)]
#[derive(PartialEq)]
pub struct DynamicType {
pub group_name: String,
pub index: usize,
pub format: String,
pub parse: Vec<Vec<Rc<TokenInfo>>>,
pub upgrade_code: String,
pub downgrade_code: String,
pub names:Vec<String>,
pub decimal_digits: Option<u8>,
pub use_fract_rounding: Option<bool>,
pub remove_fract_if_zero: Option<bool>
}
impl DynamicType {
pub fn new(group_name: String, index: usize, format: String, parse: Vec<Vec<Rc<TokenInfo>>>, upgrade_code: String, downgrade_code: String, names:Vec<String>, decimal_digits: Option<u8>, use_fract_rounding: Option<bool>, remove_fract_if_zero: Option<bool>) -> Self {
DynamicType {
group_name,
index,
format,
parse,
upgrade_code,
downgrade_code,
names,
decimal_digits,
use_fract_rounding,
remove_fract_if_zero
}
}
}
pub struct SmartCalcConfig {
pub(crate) json_data: JsonConstant,
pub(crate) format: LanguageData<JsonFormat>,
pub(crate) currency: LanguageData<Rc<CurrencyInfo>>,
pub(crate) currency_alias: LanguageData<Rc<CurrencyInfo>>,
pub(crate) timezones: BTreeMap<String, i32>,
pub(crate) currency_rate: CurrencyData<f64>,
pub(crate) token_parse_regex: LanguageData<Vec<Regex>>,
pub(crate) word_group: LanguageData<BTreeMap<String, Vec<String>>>,
pub(crate) constant_pair: LanguageData<BTreeMap<String, ConstantType>>,
pub(crate) language_alias_regex: LanguageData<Vec<(Regex, String)>>,
pub(crate) alias_regex: Vec<(Regex, String)>,
pub(crate) rule: LanguageData<RuleItemList>,
pub(crate) types: BTreeMap<String, BTreeMap<usize, Rc<DynamicType>>>,
pub(crate) type_conversion: Vec<JsonTypeConversion>,
pub(crate) month_regex: LanguageData<MonthItemList>,
pub(crate) money_config: MoneyConfig,
pub(crate) number_config: NumberConfig,
pub(crate) percentage_config: NumberConfig,
pub(crate) decimal_seperator: String,
pub(crate) thousand_separator: String,
pub(crate) timezone: String,
pub(crate) timezone_offset: i32
}
impl Default for SmartCalcConfig {
fn default() -> Self {
SmartCalcConfig::load_from_json(JSON_DATA)
}
}
impl SmartCalcConfig {
pub fn get_time_offset(&self) -> TimeOffset {
TimeOffset {
name: self.timezone.to_string(),
offset: self.timezone_offset
}
}
pub fn get_currency<T: Borrow<String>>(&self, currency: T) -> Option<Rc<CurrencyInfo>> {
self.currency
.get(currency.borrow())
.cloned()
}
pub fn load_from_json(json_data: &str) -> Self {
let mut config = SmartCalcConfig {
json_data: match from_str(json_data) {
Ok(data) => data,
Err(error) => panic!("JSON parse error: {}", error)
},
format: LanguageData::new(),
currency: LanguageData::new(),
currency_alias: LanguageData::new(),
timezones: BTreeMap::new(),
currency_rate: CurrencyData::new(),
token_parse_regex: LanguageData::new(),
word_group: LanguageData::new(),
constant_pair: LanguageData::new(),
language_alias_regex: LanguageData::new(),
rule: LanguageData::new(),
types: BTreeMap::new(),
type_conversion: Vec::new(),
month_regex: LanguageData::new(),
alias_regex: Vec::new(),
decimal_seperator: ",".to_string(),
thousand_separator: ".".to_string(),
timezone: "UTC".to_string(),
timezone_offset: 0,
money_config: MoneyConfig {
remove_fract_if_zero: false,
use_fract_rounding: true
},
number_config: NumberConfig {
decimal_digits: 2,
remove_fract_if_zero: true,
use_fract_rounding: true
},
percentage_config: NumberConfig {
decimal_digits: 2,
remove_fract_if_zero: true,
use_fract_rounding: true
},
};
for (name, currency) in config.json_data.currencies.iter() {
config.currency.insert(name.to_lowercase(), currency.clone());
}
for (timezone, offset) in config.json_data.timezones.iter() {
config.timezones.insert(timezone.clone(), *offset);
}
for (from, to) in config.json_data.alias.iter() {
match Regex::new(&format!(r"\b{}\b", from)) {
Ok(re) => config.alias_regex.push((re, to.to_string())),
Err(error) => log::error!("Alias parser error ({}) {}", from, error)
}
}
for (language, language_object) in config.json_data.languages.iter() {
let mut language_clone = language_object.format.clone();
language_clone.language = language.to_string();
config.format.insert(language.to_string(), language_clone);
}
for (key, value) in config.json_data.currency_alias.iter() {
match config.get_currency(value) {
Some(currency) => { config.currency_alias.insert(key.to_string(), currency.clone()); },
None => log::warn!("'{}' currency not found at alias", value)
};
}
for (key, value) in config.json_data.currency_rates.iter() {
match config.get_currency(key) {
Some(currency) => { config.currency_rate.insert(currency.clone(), *value); },
None => log::warn!("'{}' currency not found at rate", key)
};
}
for (language, language_constant) in config.json_data.languages.iter() {
let mut language_aliases = Vec::new();
for (alias, target_name) in language_constant.alias.iter() {
match Regex::new(&format!(r"\b{}\b", alias)) {
Ok(re) => language_aliases.push((re, target_name.to_string())),
Err(error) => log::error!("Alias parser error ({}) {}", alias, error)
}
}
config.language_alias_regex.insert(language.to_string(), language_aliases);
}
for (parse_type, items) in &config.json_data.parse {
let mut patterns = Vec::new();
for pattern in items {
match Regex::new(pattern) {
Ok(re) => patterns.push(re),
Err(error) => log::error!("Token parse regex error ({}) {}", pattern, error)
}
}
config.token_parse_regex.insert(parse_type.to_string(), patterns);
}
for (language, language_constant) in config.json_data.languages.iter() {
let mut language_group = Vec::new();
let mut month_list = Vec::with_capacity(12);
for i in 0..12 {
month_list.push(MonthInfo {
short: String::new(),
long: String::new(),
month: i + 1
});
}
for (month_name, month_number) in &language_constant.long_months {
match month_list.get_mut((*month_number - 1) as usize) {
Some(month_object) => month_object.long = month_name.to_string(),
None => log::warn!("Month not fetched. {}", month_number)
};
}
for (month_name, month_number) in &language_constant.short_months {
match month_list.get_mut((*month_number - 1) as usize) {
Some(month_object) => month_object.short = month_name.to_string(),
None => log::warn!("Month not fetched. {}", month_number)
};
}
for month in month_list.iter() {
let pattern = &format!(r"\b{}\b|\b{}\b", month.long, month.short);
match Regex::new(pattern) {
Ok(re) => language_group.push((re, month.clone())),
Err(error) => log::error!("Month parser error ({}) {}", month.long, error)
}
}
config.month_regex.insert(language.to_string(), language_group);
}
for (language, language_constant) in config.json_data.languages.iter() {
let mut word_groups = BTreeMap::new();
for (word_group_name, word_group_items) in language_constant.word_group.iter() {
let mut patterns = Vec::new();
for pattern in word_group_items {
patterns.push(pattern.to_string());
}
word_groups.insert(word_group_name.to_string(), patterns);
}
config.word_group.insert(language.to_string(), word_groups);
}
for (language, language_constant) in config.json_data.languages.iter() {
let mut constants = BTreeMap::new();
for (alias_name, constant_type) in language_constant.constant_pair.iter() {
match ConstantType::from_u8(*constant_type) {
Some(const_type) => {
constants.insert(alias_name.to_string(), const_type);
},
_ => log::error!("Constant type not parsed. {}", constant_type)
};
}
config.constant_pair.insert(language.to_string(), constants);
}
for (language, language_constant) in config.json_data.languages.iter() {
let mut language_rules = Vec::new();
for (rule_name, rule) in language_constant.rules.iter() {
if let Some(function_ref) = RULE_FUNCTIONS.get(rule_name) {
let mut function_items = Vec::new();
for rule_item in &rule.rules {
let mut session = Session::new();
session.set_language(language.to_string());
session.set_text(rule_item.to_string());
function_items.push(Tokinizer::token_infos(&config, &session));
}
language_rules.push(RuleType::Internal {
function_name: rule_name.to_string(),
function: *function_ref,
tokens_list: function_items
});
}
else {
log::warn!("Function not found : {}", rule_name);
}
}
config.rule.insert(language.to_string(), language_rules);
}
for dynamic_type in config.json_data.types.iter() {
let mut dynamic_type_holder = BTreeMap::new();
for type_item in dynamic_type.items.iter() {
if type_item.upgrade_code.is_none() || type_item.downgrade_code.is_none() {
log::warn!("Dynamic type {}:{} has missing calculation code. Please check upgrade_code and downgrade_code fields", dynamic_type.name, type_item.index);
continue;
}
let upgrade_code = type_item.upgrade_code.as_ref().map_or(String::new(), |item| item.to_string());
let downgrade_code = type_item.downgrade_code.as_ref().map_or(String::new(), |item| item.to_string());
let mut token_info = DynamicType {
group_name: dynamic_type.name.to_string(),
index: type_item.index,
format: type_item.format.to_string(),
parse: Vec::new(),
upgrade_code,
downgrade_code,
names: type_item.names.clone(),
decimal_digits: type_item.decimal_digits,
use_fract_rounding: type_item.use_fract_rounding,
remove_fract_if_zero: type_item.remove_fract_if_zero
};
for type_parse_item in type_item.parse.iter() {
let mut session = Session::new();
session.set_language("en".to_string());
session.set_text(type_parse_item.to_string());
let tokens = Tokinizer::token_infos(&config, &session);
token_info.parse.push(tokens);
}
dynamic_type_holder.insert(token_info.index, Rc::new(token_info));
}
config.types.insert(dynamic_type.name.to_string(), dynamic_type_holder);
}
for type_conversion in config.json_data.type_conversion.iter() {
let source = config.types.get(&type_conversion.source.name);
let target = config.types.get(&type_conversion.target.name);
let mut source_found = false;
let mut target_found = false;
if let Some(source) = source {
source_found = source.contains_key(&type_conversion.source.index)
}
if let Some(target) = target {
target_found = target.contains_key(&type_conversion.target.index)
}
if !source_found {
log::warn!("{} type not defined", type_conversion.source.name);
}
if !target_found {
log::warn!("{} type not defined", type_conversion.target.name);
}
if source_found && target_found {
config.type_conversion.push(type_conversion.clone());
}
}
config
}
}