use std::io::{self, Write};
use std::time::{Duration, Instant};
use crate::{Verification, centroid_first_interior_point, interior_point, verify_interior_point};
use super::args::{help_text, parse_cli_args};
use super::io::{OutputRecord, read_input, serialize, write_output};
const VERIFICATION_OUTCOMES: [Verification; 4] = [
Verification::Interior,
Verification::OnGeometry,
Verification::OffGeometry,
Verification::Unverifiable,
];
fn verify_summary(verifications: &[Verification]) -> String {
let mut line = format!("verify: {} records", verifications.len());
for outcome in VERIFICATION_OUTCOMES {
let count = verifications.iter().filter(|v| **v == outcome).count();
if count > 0 {
line.push_str(&format!(", {count} {outcome}"));
}
}
line
}
struct Phase {
name: &'static str,
elapsed: Duration,
}
fn time_report(records: usize, phases: &[Phase]) -> String {
let segments: Vec<String> = phases
.iter()
.map(|phase| format!("{} {:.1} ms", phase.name, millis(phase.elapsed)))
.collect();
let total: Duration = phases.iter().map(|phase| phase.elapsed).sum();
format!(
"time: {records} records, {}, total {:.1} ms",
segments.join(", "),
millis(total)
)
}
fn millis(elapsed: Duration) -> f64 {
elapsed.as_secs_f64() * 1000.0
}
pub fn run(
argv: &[String],
out: &mut dyn Write,
err: &mut dyn Write,
read_stdin: &mut dyn FnMut() -> io::Result<String>,
) -> i32 {
let options = match parse_cli_args(argv) {
Ok(options) => options,
Err(e) => {
let message = e.to_string();
let first_paragraph = message.split("\n\n").next().unwrap_or(&message);
let _ = writeln!(err, "{first_paragraph}\n\n{}", help_text());
return 1;
}
};
if options.help {
let _ = write!(out, "{}", help_text());
return 0;
}
let mut phases: Vec<Phase> = Vec::new();
let read_started = Instant::now();
let input = match read_input(options.input.as_deref(), read_stdin) {
Ok(input) => input,
Err(e) => {
let _ = writeln!(err, "{e}");
return 1;
}
};
phases.push(Phase {
name: "read",
elapsed: read_started.elapsed(),
});
let records = input.records.len();
let compute_point: fn(&geo_types::Geometry<f64>) -> Option<geo_types::Coord<f64>> =
if options.centroid_first {
centroid_first_interior_point
} else {
interior_point
};
let mut verifications: Vec<Verification> = Vec::new();
let mut verify_elapsed = Duration::ZERO;
let compute_started = Instant::now();
let results: Vec<OutputRecord> = input
.records
.into_iter()
.map(|record| {
let point = record.geometry.as_ref().and_then(compute_point);
if options.verify {
let started = options.time.then(Instant::now);
verifications.push(verify_interior_point(point, record.geometry.as_ref()));
if let Some(started) = started {
verify_elapsed += started.elapsed();
}
}
OutputRecord {
point,
meta: record.meta,
}
})
.collect();
phases.push(Phase {
name: "compute",
elapsed: compute_started.elapsed().saturating_sub(verify_elapsed),
});
if options.verify {
phases.push(Phase {
name: "verify",
elapsed: verify_elapsed,
});
}
if !options.quiet {
let write_started = Instant::now();
let text = serialize(input.kind, results, options.format);
if let Err(e) = write_output(&text, options.output.as_deref(), out) {
let _ = writeln!(err, "{e}");
return 1;
}
phases.push(Phase {
name: "write",
elapsed: write_started.elapsed(),
});
}
let mut code = 0;
if options.verify {
if !options.quiet {
let _ = writeln!(err, "{}", verify_summary(&verifications));
}
for (index, verification) in verifications.iter().enumerate() {
if *verification == Verification::OffGeometry {
let _ = writeln!(err, "verify: record {}: {verification}", index + 1);
}
}
if verifications.contains(&Verification::OffGeometry) {
code = 2;
}
}
if options.time {
let _ = writeln!(err, "{}", time_report(records, &phases));
}
code
}