use crate::eval::functions::check_arity;
use crate::types::{ErrorKind, Value};
fn collect_numbers(v: &Value, out: &mut Vec<f64>) {
match v {
Value::Array(elems) => {
for elem in elems {
collect_numbers(elem, out);
}
}
Value::Number(n) => out.push(*n),
_ => {}
}
}
fn count_nonempty(v: &Value) -> usize {
match v {
Value::Array(elems) => elems.iter().map(count_nonempty).sum(),
Value::Empty => 0,
_ => 1,
}
}
pub fn subtotal_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 255) {
return err;
}
let code = match &args[0] {
Value::Number(n) => *n as i64,
_ => return Value::Error(ErrorKind::Value),
};
let func = code % 100;
let rest = &args[1..];
if rest.iter().any(|a| matches!(a, Value::Array(_))) {
return Value::Error(ErrorKind::Value);
}
let mut nums: Vec<f64> = Vec::new();
for arg in rest {
collect_numbers(arg, &mut nums);
}
match func {
9 => {
let sum: f64 = nums.iter().sum();
if !sum.is_finite() {
return Value::Error(ErrorKind::Num);
}
Value::Number(sum)
}
1 => {
if nums.is_empty() {
return Value::Error(ErrorKind::DivByZero);
}
Value::Number(nums.iter().sum::<f64>() / nums.len() as f64)
}
2 => {
Value::Number(nums.len() as f64)
}
3 => {
let total: usize = rest.iter().map(count_nonempty).sum();
Value::Number(total as f64)
}
4 => {
match nums.iter().cloned().reduce(f64::max) {
Some(v) => Value::Number(v),
None => Value::Error(ErrorKind::Num),
}
}
5 => {
match nums.iter().cloned().reduce(f64::min) {
Some(v) => Value::Number(v),
None => Value::Error(ErrorKind::Num),
}
}
_ => Value::Error(ErrorKind::Value),
}
}