use super::Thrown;
use crate::error::Error;
use crate::parse::{js_to_number, normalize_decimal_mark, string_to_double, trim_spaces};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimpleOp {
Avg,
Sum,
Prd,
Min,
Max,
}
impl SimpleOp {
#[must_use]
pub fn parse(name: &str) -> Option<Self> {
Some(match name {
n if n.eq_ignore_ascii_case("AVG") => Self::Avg,
n if n.eq_ignore_ascii_case("SUM") => Self::Sum,
n if n.eq_ignore_ascii_case("PRD") => Self::Prd,
n if n.eq_ignore_ascii_case("MIN") => Self::Min,
n if n.eq_ignore_ascii_case("MAX") => Self::Max,
_ => return None,
})
}
#[must_use]
pub fn combine(self, running: f64, next: f64) -> f64 {
match self {
Self::Avg | Self::Sum => running + next,
Self::Prd => running * next,
Self::Min => running.min(next),
Self::Max => running.max(next),
}
}
#[must_use]
pub fn identity(self) -> f64 {
match self {
Self::Prd => 1.0,
Self::Avg | Self::Sum | Self::Min | Self::Max => 0.0,
}
}
#[must_use]
pub fn seeds_from_first(self) -> bool {
matches!(self, Self::Min | Self::Max)
}
}
pub fn af_simple(op: &str, a: f64, b: f64) -> Result<f64, Thrown> {
if a.is_nan() || b.is_nan() {
return Err(Thrown::bare(Error::Value));
}
let op = SimpleOp::parse(op).ok_or_else(|| Thrown::bare(Error::Value))?;
let combined = op.combine(a, b);
Ok(if op == SimpleOp::Avg {
combined / 2.0
} else {
combined
})
}
fn round_to_six_places(value: f64) -> f64 {
let scale = f64::from(10f32.powi(6));
(value * scale + 0.49).floor() / scale
}
pub fn af_simple_calculate(op: &str, values: &[f64]) -> Result<f64, Thrown> {
let Some(op) = SimpleOp::parse(op) else {
return if values.is_empty() {
Ok(round_to_six_places(0.0))
} else {
Err(Thrown::bare(Error::Value))
};
};
let mut total = op.identity();
let mut counted = 0usize;
for (index, value) in values.iter().copied().enumerate() {
if index == 0 && op.seeds_from_first() {
total = value;
}
total = op.combine(total, value);
counted = counted.saturating_add(1);
}
if op == SimpleOp::Avg
&& let Ok(divisor) = u32::try_from(counted)
&& divisor > 0
{
total /= f64::from(divisor);
}
Ok(round_to_six_places(total))
}
pub fn af_simple_calculate_texts(op: &str, texts: &[&str]) -> Result<f64, Thrown> {
let nums: Vec<f64> = texts
.iter()
.map(|t| string_to_double(trim_spaces(t)))
.collect();
af_simple_calculate(op, &nums)
}
#[must_use]
pub fn af_split_field_list(s: &str) -> Vec<String> {
let mut names = Vec::new();
let mut rest = s;
while !rest.is_empty() {
if let Some((head, tail)) = rest.split_once(',') {
names.push(trim_spaces(head).to_string());
rest = tail;
} else {
names.push(trim_spaces(rest).to_string());
break;
}
}
names
}
#[must_use]
pub fn af_make_number(s: &str) -> f64 {
let normalized = normalize_decimal_mark(s);
match js_to_number(&normalized) {
Some(n) if n.is_nan() => f64::NAN, Some(n) => n,
None => 0.0,
}
}