use super::assemble::{run_report, trial_report};
use super::resolve::trial_identity;
use super::select::admission;
use super::types::{
Admission, Invocation, ReportRecordingRefusal, SelectionPlan, TrialBinding, TrialTableView,
};
use crate::report::{HostTrialRecord, RunReport, TrialId, TrialReport};
use std::collections::{BTreeMap, BTreeSet};
pub fn record_one(
binding: &TrialBinding,
invocation: &Invocation,
record: HostTrialRecord,
) -> Result<TrialReport, ReportRecordingRefusal> {
let expected = trial_identity(binding.row());
let recorded = record.trial();
if expected != recorded {
return Err(ReportRecordingRefusal::TrialMismatch { expected, recorded });
}
let (_, attempt, measurement) = record.into_parts();
Ok(trial_report(binding, invocation, attempt, measurement))
}
pub fn record_all(
view: &TrialTableView<'_>,
selection: &SelectionPlan,
invocation: &Invocation,
records: Vec<HostTrialRecord>,
) -> Result<RunReport, ReportRecordingRefusal> {
let mut host = HostRecords::indexed(records)?;
host.admissible_against(view, selection)?;
run_report(view, selection, invocation, |binding| {
let trial = trial_identity(binding.row());
let record = host
.by_trial
.remove(&trial)
.ok_or(ReportRecordingRefusal::MissingSelectedRecord(trial))?;
record_one(binding, invocation, record)
})
}
struct HostRecords {
by_trial: BTreeMap<TrialId, HostTrialRecord>,
order: Vec<TrialId>,
}
impl HostRecords {
fn indexed(records: Vec<HostTrialRecord>) -> Result<Self, ReportRecordingRefusal> {
let mut by_trial = BTreeMap::new();
let mut order = Vec::new();
for record in records {
let trial = record.trial();
if by_trial.insert(trial, record).is_some() {
return Err(ReportRecordingRefusal::DuplicateHostRecord(trial));
}
order.push(trial);
}
Ok(Self { by_trial, order })
}
fn admissible_against(
&self,
view: &TrialTableView<'_>,
selection: &SelectionPlan,
) -> Result<(), ReportRecordingRefusal> {
let mut table = BTreeSet::new();
let mut selected = BTreeSet::new();
for binding in view.bindings() {
let row = binding.row();
let trial = trial_identity(row);
table.insert(trial);
if let Admission::Selected = admission(selection.chooses(), row, trial) {
selected.insert(trial);
}
}
for trial in self.order.iter().copied() {
if !table.contains(&trial) {
return Err(ReportRecordingRefusal::TrialOutsideTable(trial));
}
if !selected.contains(&trial) {
return Err(ReportRecordingRefusal::RecordForUnselectedTrial(trial));
}
}
Ok(())
}
}