use std::collections::HashMap;
use serde_json::{Map, Value};
use super::params::{COUNT_FIELD, SummarizeParams, sum_field};
use super::total::Total;
use crate::control::placeholder;
use crate::processor::BatchFile;
struct Group {
values: Map<String, Value>,
count: u64,
totals: Vec<Total>,
}
pub(super) fn summarize(
files: &[BatchFile],
params: &SummarizeParams,
) -> Result<Vec<Map<String, Value>>, String> {
let mut groups: Vec<Group> = Vec::new();
let mut index: HashMap<Vec<String>, usize> = HashMap::new();
for file in files {
let key = key_of(file, ¶ms.group_by);
let at = *index.entry(key).or_insert_with(|| {
groups.push(new_group(file, params));
groups.len() - 1
});
add_to(&mut groups[at], file, ¶ms.sum)?;
}
groups.into_iter().map(|g| fields_of(g, params)).collect()
}
fn key_of(file: &BatchFile, group_by: &[String]) -> Vec<String> {
group_by
.iter()
.map(|f| {
file.metadata
.get(f)
.map(placeholder::text)
.unwrap_or_default()
})
.collect()
}
fn new_group(file: &BatchFile, params: &SummarizeParams) -> Group {
let values = params
.group_by
.iter()
.map(|f| (f.clone(), group_value(file.metadata.get(f))))
.collect();
Group {
values,
count: 0,
totals: vec![Total::default(); params.sum.len()],
}
}
fn group_value(value: Option<&Value>) -> Value {
match value {
Some(v) if !placeholder::text(v).is_empty() => v.clone(),
_ => Value::Null,
}
}
fn add_to(group: &mut Group, file: &BatchFile, sum: &[String]) -> Result<(), String> {
group.count += 1;
for (field, total) in sum.iter().zip(group.totals.iter_mut()) {
total
.add(file.metadata.get(field))
.map_err(|why| format!("{}: sum field \"{field}\": {why}", file.filename))?;
}
Ok(())
}
fn fields_of(group: Group, params: &SummarizeParams) -> Result<Map<String, Value>, String> {
let mut fields = group.values;
fields.insert(COUNT_FIELD.to_string(), group.count.into());
for (field, total) in params.sum.iter().zip(group.totals) {
let value = total
.into_value()
.map_err(|why| format!("sum field \"{field}\": {why}"))?;
fields.insert(sum_field(field), value);
}
Ok(fields)
}