use mumu::parser::interpreter::Interpreter;
use mumu::parser::types::Value;
use crate::partials::{is_placeholder, make_two_arg_partial};
use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
use rust_decimal::{Decimal, RoundingStrategy};
pub fn math_banking_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
match args.len() {
0 => Ok(make_two_arg_partial(banking_finalize, None, None)),
1 => {
let a = &args[0];
if is_placeholder(a) {
Ok(make_two_arg_partial(banking_finalize, None, None))
} else {
Ok(make_two_arg_partial(
banking_finalize,
Some(a.clone()),
None,
))
}
}
2 => {
let lp = is_placeholder(&args[0]);
let rp = is_placeholder(&args[1]);
if !lp && !rp {
banking_finalize(vec![args[0].clone(), args[1].clone()])
} else {
Ok(make_two_arg_partial(
banking_finalize,
if lp { None } else { Some(args[0].clone()) },
if rp { None } else { Some(args[1].clone()) },
))
}
}
n => Err(format!("math:banking expects 0, 1, or 2 arguments, got {}", n)),
}
}
fn banking_finalize(args: Vec<Value>) -> Result<Value, String> {
if args.len() != 2 {
return Err(format!(
"banking_finalize => expected 2 args (places, value), got {}",
args.len()
));
}
let places = coerce_places(&args[0])?;
let dec_val = to_decimal(&args[1])?;
let rounded = dec_val.round_dp_with_strategy(places, RoundingStrategy::MidpointNearestEven);
if places == 0 {
if let Some(i) = rounded.to_i32() {
return Ok(Value::Int(i));
}
}
let f = rounded
.to_f64()
.ok_or_else(|| "math:banking: cannot convert result to float".to_string())?;
Ok(Value::Float(f))
}
fn coerce_places(v: &Value) -> Result<u32, String> {
let raw: i64 = match v {
Value::Int(i) => *i as i64,
Value::Long(l) => *l,
Value::Float(f) => (*f as i64),
Value::SingleString(s) => s
.parse::<i64>()
.map_err(|_| format!("math:banking: places must be an integer, got {:?}", v))?,
other => {
return Err(format!(
"math:banking: places must be an integer, got {:?}",
other
))
}
};
if raw < 0 {
return Err("math:banking: places must be >= 0".to_string());
}
Ok(raw as u32)
}
fn to_decimal(v: &Value) -> Result<Decimal, String> {
match v {
Value::Int(i) => Ok(Decimal::from(*i)),
Value::Long(l) => Decimal::from_i64(*l).ok_or_else(|| "math:banking: long out of range".to_string()),
Value::Float(f) => Decimal::from_f64(*f)
.ok_or_else(|| "math:banking: cannot represent float precisely".to_string()),
Value::SingleString(s) => s
.parse::<Decimal>()
.map_err(|_| "math:banking: cannot parse decimal string".to_string()),
other => Err(format!(
"math:banking: value must be numeric or decimal string, got {:?}",
other
)),
}
}