minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;

use crate::kairos::{Clock, TickCounter};
use crate::metis::{Anchor, Dot, DotSet, Dotted, Locus, Rhapsody};

use super::super::d;
use proptest::prelude::*;

pub(super) type Seq = Dotted<Rhapsody>;

pub(super) const REPLICAS: usize = 3;

#[derive(Clone, Debug)]
pub(super) enum Op {
    Insert {
        replica: usize,
        anchor_pick: usize,
    },
    Paste {
        replica: usize,
        anchor_pick: usize,
        len: u32,
    },
    Delete {
        replica: usize,
        victim_pick: usize,
    },
    Gossip {
        from: usize,
        to: usize,
    },
}

pub(super) fn arb_ops() -> impl Strategy<Value = Vec<Op>> {
    let op = prop_oneof![
        (0..REPLICAS, 0usize..8).prop_map(|(replica, anchor_pick)| Op::Insert {
            replica,
            anchor_pick,
        }),
        (0..REPLICAS, 0usize..8).prop_map(|(replica, victim_pick)| Op::Delete {
            replica,
            victim_pick
        }),
        (0..REPLICAS, 0..REPLICAS).prop_map(|(from, to)| Op::Gossip { from, to }),
    ];
    prop::collection::vec(op, 0..28)
}

/// [`arb_ops`] extended with the run write (R-19): paste lengths crossing
/// the fragment cap and the fiber page width, so the properties over THIS
/// source sample multi-fragment, page-spanning bulk weaves and their
/// deltas. A separate source because pastes grow documents by two orders,
/// which the verge-pair-quadratic oracles (the extent property) cannot
/// afford; the linear-oracle suites (convergence, folds, order, positional)
/// take this one.
pub(super) fn arb_ops_with_paste() -> impl Strategy<Value = Vec<Op>> {
    let op = prop_oneof![
        4 => (0..REPLICAS, 0usize..8).prop_map(|(replica, anchor_pick)| Op::Insert {
            replica,
            anchor_pick,
        }),
        1 => (0..REPLICAS, 0usize..8, 1u32..96).prop_map(|(replica, anchor_pick, len)| {
            Op::Paste { replica, anchor_pick, len }
        }),
        4 => (0..REPLICAS, 0usize..8).prop_map(|(replica, victim_pick)| Op::Delete {
            replica,
            victim_pick
        }),
        4 => (0..REPLICAS, 0..REPLICAS).prop_map(|(from, to)| Op::Gossip { from, to }),
    ];
    prop::collection::vec(op, 0..28)
}

pub(super) struct Record {
    pub(super) woven: BTreeMap<Dot, Locus>,
    pub(super) expelled: BTreeSet<Dot>,
}

pub(super) fn run(ops: &[Op]) -> (Vec<Seq>, Record) {
    let mut live: Vec<Seq> = (0..REPLICAS).map(|_| Dotted::new()).collect();
    let clocks: Vec<Clock<TickCounter>> = (0..REPLICAS)
        .map(|i| {
            let station = u32::try_from(i + 1).unwrap();
            Clock::with_default_config(TickCounter::new(), station).unwrap()
        })
        .collect();
    let mut record = Record {
        woven: BTreeMap::new(),
        expelled: BTreeSet::new(),
    };

    for op in ops {
        match *op {
            Op::Insert {
                replica,
                anchor_pick,
            } => {
                let station = u32::try_from(replica + 1).unwrap();
                let visible = live[replica].store().order();
                let anchor = if visible.is_empty() {
                    None
                } else {
                    Some(visible[anchor_pick % visible.len()])
                };
                let dot = live[replica].next_dot(station);
                let rank = clocks[replica].now(0u16);
                let locus = Locus {
                    anchor: anchor.map_or(Anchor::Origin, |dot| Anchor::After(dot.into())),
                    rank,
                };
                let mut rhapsody = Rhapsody::new();
                assert!(rhapsody.weave(dot, locus));
                live[replica] = live[replica].merge(&Dotted::from_store(rhapsody));
                let _ = record.woven.insert(dot, locus);
            }
            Op::Paste {
                replica,
                anchor_pick,
                len,
            } => {
                let station = u32::try_from(replica + 1).unwrap();
                let visible = live[replica].store().order();
                let anchor = if visible.is_empty() {
                    Anchor::Origin
                } else {
                    Anchor::After(visible[anchor_pick % visible.len()].into())
                };
                let first = live[replica].next_dot(station);
                let run = clocks[replica].now_run(core::num::NonZeroU32::new(len).unwrap(), 0u16);
                let mut rhapsody = Rhapsody::new();
                assert!(rhapsody.weave_run(first, anchor, run));
                for k in 0..u64::from(len) {
                    let dot = d(station, first.counter() + k);
                    let locus = rhapsody.locus(dot).expect("the run was woven");
                    let _ = record.woven.insert(dot, locus);
                }
                live[replica] = live[replica].merge(&Dotted::from_store(rhapsody));
            }
            Op::Delete {
                replica,
                victim_pick,
            } => {
                let visible = live[replica].store().order();
                if visible.is_empty() {
                    continue;
                }
                let victim = visible[victim_pick % visible.len()];
                let mut ctx = DotSet::new();
                let _ = ctx.insert(victim);
                live[replica] = live[replica].merge(&Dotted::from_context(ctx));
                let _ = record.expelled.insert(victim);
            }
            Op::Gossip { from, to } => {
                live[to] = live[to].merge(&live[from]);
            }
        }
    }
    (live, record)
}