minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::collections::BTreeSet;

use super::super::super::super::support::dot;
use crate::metis::{Dot, DotSet, DotStore};

/// A store paired with a context that covers every store dot.
#[derive(Clone, Debug)]
pub(super) struct Covered<S> {
    store: S,
    context: DotSet,
}

impl<S: DotStore> Covered<S> {
    pub(super) fn new(store: S, context: DotSet) -> Self {
        let covered = Self { store, context };
        covered.assert_covered();
        covered
    }

    pub(super) fn store(&self) -> &S {
        &self.store
    }

    pub(super) fn context(&self) -> &DotSet {
        &self.context
    }

    fn assert_covered(&self) {
        for held in self.store.dots() {
            let (station, counter) = (held.station(), held.counter());
            assert!(
                self.context.contains(held),
                "harness input is uncovered at ({station}, {counter})"
            );
        }
    }
}

/// A dot survives causal merge iff both stores hold it, or one holds it and the
/// other side's context has never seen it.
fn check_survivor_law<S: DotStore>(a: &Covered<S>, b: &Covered<S>) {
    let merged = a.store.causal_merge(&a.context, &b.store, &b.context);
    let a_dots: BTreeSet<Dot> = a.store.dots().collect();
    let b_dots: BTreeSet<Dot> = b.store.dots().collect();
    let merged_dots: BTreeSet<Dot> = merged.dots().collect();
    for station in 0u32..4 {
        for counter in 1u64..9 {
            let probe = dot(station, counter);
            let in_a = a_dots.contains(&probe);
            let in_b = b_dots.contains(&probe);
            let survives = (in_a && (in_b || !b.context.contains(probe)))
                || (in_b && (in_a || !a.context.contains(probe)));
            assert_eq!(
                merged_dots.contains(&probe),
                survives,
                "survivor law disagrees at ({station}, {counter})"
            );
        }
    }
}

fn check_commutative<S: DotStore + PartialEq + core::fmt::Debug>(a: &Covered<S>, b: &Covered<S>) {
    let ab = a.store.causal_merge(&a.context, &b.store, &b.context);
    let ba = b.store.causal_merge(&b.context, &a.store, &a.context);
    assert_eq!(ab, ba, "causal_merge is not commutative");
}

fn check_associative<S: DotStore + PartialEq + core::fmt::Debug>(
    a: &Covered<S>,
    b: &Covered<S>,
    c: &Covered<S>,
) {
    let bc = b.store.causal_merge(&b.context, &c.store, &c.context);
    let bc_ctx = b.context.merge(&c.context);
    let left = a.store.causal_merge(&a.context, &bc, &bc_ctx);

    let ab = a.store.causal_merge(&a.context, &b.store, &b.context);
    let ab_ctx = a.context.merge(&b.context);
    let right = ab.causal_merge(&ab_ctx, &c.store, &c.context);

    assert_eq!(left, right, "causal_merge is not associative");
}

fn check_idempotent<S: DotStore + Clone + PartialEq + core::fmt::Debug>(a: &Covered<S>) {
    let merged = a.store.causal_merge(&a.context, &a.store, &a.context);
    assert_eq!(merged, a.store, "causal_merge is not idempotent");
}

/// The in-place fold agrees with the pure merge on every covered input: the
/// one law `causal_merge_from` carries (S182). An override is a cost seam,
/// never a semantic choice, and this check holds the default body and every
/// override to the same output.
fn check_in_place_agrees<S: DotStore + Clone + PartialEq + core::fmt::Debug>(
    a: &Covered<S>,
    b: &Covered<S>,
) {
    let pure = a.store.causal_merge(&a.context, &b.store, &b.context);
    let mut folded = a.store.clone();
    folded.causal_merge_from(&a.context, &b.store, &b.context);
    assert_eq!(
        folded, pure,
        "causal_merge_from disagrees with causal_merge"
    );
}

fn check_bottom_identity<S: DotStore + Clone + PartialEq + core::fmt::Debug>(a: &Covered<S>) {
    let bottom = S::default();
    let empty = DotSet::new();
    let right = a.store.causal_merge(&a.context, &bottom, &empty);
    assert_eq!(right, a.store, "bottom is not a right identity");
    let left = bottom.causal_merge(&empty, &a.store, &a.context);
    assert_eq!(left, a.store, "bottom is not a left identity");
}

/// Bottom carries no state at all, so it carries no support; the converse is
/// instance-specific (a recording plane keeps a store non-bottom with empty
/// support, the revised law 1 wording, S190), so only this direction is a
/// generic law.
fn check_bottom_is_support_empty<S: DotStore>(a: &Covered<S>) {
    assert!(
        S::default().is_bottom(),
        "the default store is not the bottom"
    );
    if a.store.is_bottom() {
        assert!(
            a.store.dots().next().is_none(),
            "a bottom store must carry no support"
        );
    }
}

/// `support()` is the have-set fold of `dots()`: the compact form and the
/// enumeration agree, for the default body and every override alike (the
/// arc-10 survivor half's one law).
fn check_support_is_the_dots_fold<S: DotStore>(a: &Covered<S>) {
    let support = a.store.support();
    let want: BTreeSet<Dot> = a.store.dots().collect();
    let got: BTreeSet<Dot> = support.dots().collect();
    assert_eq!(got, want, "support disagrees with the dots enumeration");
}

/// The witnessed selection's two generic laws: a bottom witness licenses no
/// sub-selection (the read equals the bare `novel_to`), and no witness moves
/// SUPPORT selection (the witness reaches recording planes only).
fn check_witnessed_selection<S: DotStore + Clone + PartialEq + core::fmt::Debug>(
    a: &Covered<S>,
    b: &Covered<S>,
) {
    let bare = a.store.novel_to(b.context());
    let bottomed = a.store.novel_to_witnessed(b.context(), &DotSet::new());
    assert_eq!(bottomed, bare, "a bottom witness must equal the bare read");

    let witnessed = a.store.novel_to_witnessed(b.context(), &a.store.support());
    let want: BTreeSet<Dot> = bare.dots().collect();
    let got: BTreeSet<Dot> = witnessed.dots().collect();
    assert_eq!(got, want, "a witness must not move support selection");
}

/// Restriction is fiber selection and commutes with causal merge under
/// restricted contexts.
fn check_restriction<S: DotStore + Clone + PartialEq + core::fmt::Debug>(
    a: &Covered<S>,
    b: &Covered<S>,
    roster: &[u32],
) {
    let kept = a.store.restrict(roster.iter().copied());
    let roster_set: BTreeSet<u32> = roster.iter().copied().collect();
    let want: BTreeSet<Dot> = a
        .store
        .dots()
        .filter(|held| roster_set.contains(&held.station()))
        .collect();
    let got: BTreeSet<Dot> = kept.dots().collect();
    assert_eq!(got, want, "restrict is not fiber selection");

    let merged = a.store.causal_merge(&a.context, &b.store, &b.context);
    let restrict_then_merge = a.store.restrict(roster.iter().copied()).causal_merge(
        &a.context.restrict(roster.iter().copied()),
        &b.store.restrict(roster.iter().copied()),
        &b.context.restrict(roster.iter().copied()),
    );
    assert_eq!(
        merged.restrict(roster.iter().copied()),
        restrict_then_merge,
        "restrict does not commute with causal_merge"
    );
}

pub(super) fn check_all_laws<S: DotStore + Clone + PartialEq + core::fmt::Debug>(
    a: &Covered<S>,
    b: &Covered<S>,
    c: &Covered<S>,
    roster: &[u32],
) {
    a.assert_covered();
    b.assert_covered();
    c.assert_covered();
    check_survivor_law(a, b);
    check_commutative(a, b);
    check_associative(a, b, c);
    check_idempotent(a);
    check_bottom_identity(a);
    check_bottom_is_support_empty(a);
    check_in_place_agrees(a, b);
    check_support_is_the_dots_fold(a);
    check_witnessed_selection(a, b);
    check_restriction(a, b, roster);
}