use crate::*;
use alux_ext::ext;
use anyhow::{Result, bail};
use std::fmt::Debug;
use std::time::Duration;
pub trait SessionLawFixture: SessionSorts {
fn law_wanting(&mut self, says: usize) -> Self::Id;
fn law_unbeginnable(&mut self) -> Self::Id;
fn law_wants(&mut self, id: &Self::Id, wanted: Wanted);
fn law_passes(&mut self, elapsed: Duration);
fn law_told(&self, id: &Self::Id) -> Vec<Telling>;
fn law_abandonments(&self, id: &Self::Id) -> usize;
}
#[ext(name = SessionLaws)]
pub impl<This> This
where
This: UndertakingAlg + RunReadAlg + RunHoldAlg + AttentionAlg + ClockAlg + SessionLawFixture,
This::Id: Clone + Eq + Debug,
{
fn session_laws(&mut self) -> Result<()> {
self.check_beginning_laws()?;
self.check_saying_laws()?;
self.check_unwanted_laws()?;
self.check_holding_laws()
}
fn check_beginning_laws(&mut self) -> Result<()> {
let mut running = Vec::new();
self.attend(&mut running);
if !running.is_empty() {
bail!("attending to nothing began something");
}
let wanting = [self.law_wanting(1), self.law_wanting(1)];
let refusing = self.law_unbeginnable();
self.attend(&mut running);
if running.len() != wanting.len() {
bail!(
"one pass began {} runs where {} requests wanted work",
running.len(),
wanting.len()
);
}
for id in &wanting {
if begun_count(&self.law_told(id)) != 1 {
bail!("{id:?} was not told once that its work began");
}
}
if self.law_told(&refusing) != vec![Telling::Refused] {
bail!("work refusing to begin was not stated once as a refusal");
}
self.attend(&mut running);
for id in &wanting {
if begun_count(&self.law_told(id)) != 1 {
bail!("{id:?} was begun again while its run was still happening");
}
}
if self.law_told(&refusing) != vec![Telling::Refused] {
bail!("work refusing to begin was begun again");
}
Ok(())
}
fn check_saying_laws(&mut self) -> Result<()> {
let mut running = Vec::new();
let said = 3;
let id = self.law_wanting(said);
self.attend(&mut running);
while !running.is_empty() {
self.attend(&mut running);
}
let told = self.law_told(&id);
let heard = told
.iter()
.filter(|telling| **telling == Telling::Heard)
.count();
if heard != said {
bail!("a run stating {said} things was heard {heard} times");
}
let Some(ending) = told.iter().position(|telling| *telling == Telling::Ended) else {
bail!("a run that finished never stated how it ended");
};
if ending + 1 != told.len() {
bail!("something was stated after the run had ended");
}
if told.first() != Some(&Telling::Begun) {
bail!("a run stated something before it began");
}
Ok(())
}
fn check_unwanted_laws(&mut self) -> Result<()> {
let mut running = Vec::new();
let id = self.law_wanting(4);
self.attend(&mut running);
self.law_wants(&id, Wanted::Unwanted);
let mut passes = 0;
while !running.is_empty() {
self.attend(&mut running);
passes += 1;
if passes > 8 {
bail!("a run nobody wanted never ended");
}
}
if self.law_abandonments(&id) != passes - 1 {
bail!(
"a run nobody wanted was told to end {} times over {passes} passes",
self.law_abandonments(&id)
);
}
let ended = self
.law_told(&id)
.iter()
.filter(|telling| matches!(telling, Telling::Ended | Telling::Refused))
.count();
if ended != 1 {
bail!("a run nobody wanted stated how it ended {ended} times");
}
Ok(())
}
fn check_holding_laws(&mut self) -> Result<()> {
let mut running = Vec::new();
let id = self.law_wanting(5);
let tick = Duration::from_secs(1);
self.attend(&mut running);
self.law_passes(tick);
self.law_wants(&id, Wanted::Held);
self.attend(&mut running);
let held_at = last_elapsed(&self.law_told(&id));
self.law_passes(tick * 10);
self.attend(&mut running);
if last_elapsed(&self.law_told(&id)) != held_at {
bail!("a run held still went on running");
}
self.law_wants(&id, Wanted::Running);
self.attend(&mut running);
self.law_passes(tick);
self.attend(&mut running);
if last_elapsed(&self.law_told(&id)) != held_at.map(|elapsed| elapsed + tick) {
bail!("a run let go again did not carry on from where it was held");
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Telling {
Begun,
Heard,
RanFor(Duration),
Ended,
Refused,
}
fn begun_count(told: &[Telling]) -> usize {
told.iter()
.filter(|telling| **telling == Telling::Begun)
.count()
}
fn last_elapsed(told: &[Telling]) -> Option<Duration> {
told.iter().rev().find_map(|telling| match telling {
Telling::RanFor(elapsed) => Some(*elapsed),
Telling::Begun | Telling::Heard | Telling::Ended | Telling::Refused => None,
})
}