use super::super::geom::{Cell, Rect};
use super::super::proj::{AxisMap, Bound, RefProj};
use super::super::store::{BuildInput, EdgeSpec, FormulaFacts, OriginSpec, Store, Tag, TagFilter};
use crate::engine::arena::{AstNodeId, ValueRef};
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::smallvec;
pub struct Rng(pub u64);
impl Rng {
pub fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
pub fn below(&mut self, n: u32) -> u32 {
(self.next() % u64::from(n.max(1))) as u32
}
pub fn chance(&mut self, pct: u32) -> bool {
self.below(100) < pct
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Formula {
pub refs: Vec<RefProj>,
pub l: u64,
pub literal: u32,
}
impl Formula {
pub fn facts(&self) -> FormulaFacts {
let mut edges: Vec<EdgeSpec> = self
.refs
.iter()
.map(|p| EdgeSpec {
proj: *p,
tag: Tag::R1,
origin: OriginSpec::Text,
})
.collect();
edges.sort_unstable();
edges.dedup();
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in format!("{:?}", self.refs).bytes() {
h = (h ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3);
}
FormulaFacts {
edges,
ltokens: Some(vec![self.l, h].into_boxed_slice()),
template: AstNodeId::from_u32(self.l as u32),
template_anchor: None,
literals: smallvec![ValueRef::from_raw(self.literal)],
flags: 0,
}
}
}
pub fn rel(dr: i32, dc: i32, sheet: u16) -> RefProj {
RefProj {
sheet,
rows: AxisMap::point(Bound::Rel(dr)),
cols: AxisMap::point(Bound::Rel(dc)),
}
}
pub fn abs(r: u32, c: u32, sheet: u16) -> RefProj {
RefProj {
sheet,
rows: AxisMap::point(Bound::Abs(r)),
cols: AxisMap::point(Bound::Abs(c)),
}
}
pub fn window(a: i32, b: i32, col: u32, sheet: u16) -> RefProj {
RefProj {
sheet,
rows: AxisMap {
lo: Bound::Rel(a),
hi: Bound::Rel(b),
},
cols: AxisMap::point(Bound::Abs(col)),
}
}
pub fn column(col: u32, sheet: u16) -> RefProj {
RefProj {
sheet,
rows: AxisMap {
lo: Bound::Open,
hi: Bound::Open,
},
cols: AxisMap::point(Bound::Abs(col)),
}
}
#[derive(Clone, Default)]
pub struct Model {
pub cells: FxHashMap<Cell, Formula>,
}
fn window_of(p: &RefProj, cell: Cell) -> Option<(u16, i64, i64, i64, i64)> {
let f = |b: Bound, x: u32, open: i64| match b {
Bound::Rel(k) => i64::from(x) + i64::from(k),
Bound::Abs(v) => i64::from(v),
Bound::Open => open,
};
let (r0, r1) = (f(p.rows.lo, cell.1, 0), f(p.rows.hi, cell.1, 1_048_575));
let (c0, c1) = (f(p.cols.lo, cell.2, 0), f(p.cols.hi, cell.2, 16_383));
(r0 >= 0 && c0 >= 0 && r0 <= r1 && c0 <= c1).then_some((p.sheet, r0, c0, r1, c1))
}
impl Model {
pub fn valid(f: &Formula, cell: Cell) -> bool {
f.refs
.iter()
.all(|p| window_of(p, cell).is_some_and(|w| w.3 <= 1_048_575 && w.4 <= 16_383))
}
pub fn direct_dependents(&self, q: Cell) -> Vec<Cell> {
let mut v: Vec<Cell> = self
.cells
.iter()
.filter(|(c, f)| {
f.refs.iter().any(|p| {
window_of(p, **c).is_some_and(|(s, r0, c0, r1, c1)| {
s == q.0
&& r0 <= i64::from(q.1)
&& i64::from(q.1) <= r1
&& c0 <= i64::from(q.2)
&& i64::from(q.2) <= c1
})
})
})
.map(|(c, _)| *c)
.collect();
v.sort_unstable();
v
}
pub fn dependents(&self, seed: Cell) -> Vec<Cell> {
let mut seen: FxHashSet<Cell> = FxHashSet::default();
let mut queue = vec![seed];
while let Some(c) = queue.pop() {
for d in self.direct_dependents(c) {
if seen.insert(d) {
queue.push(d);
}
}
}
let mut v: Vec<Cell> = seen.into_iter().collect();
v.sort_unstable();
v
}
}
pub fn store_direct(s: &Store, q: Cell) -> Vec<Cell> {
let mut hits = Vec::new();
s.direct_dependents(q.0, &Rect::cell(q.1, q.2), TagFilter::All, &mut hits);
let mut set: FxHashSet<Cell> = FxHashSet::default();
for (sh, r) in hits {
for c in r.c0..=r.c1 {
for row in r.r0..=r.r1 {
set.insert((sh, row, c));
}
}
}
let mut v: Vec<Cell> = set.into_iter().collect();
v.sort_unstable();
v
}
pub fn store_closure(s: &Store, q: Cell) -> Vec<Cell> {
let (cover, _) = s.dependents(&[(q.0, Rect::cell(q.1, q.2))], TagFilter::All);
cover.cells()
}
pub fn random_formula(g: &mut Rng, rows: u32) -> Formula {
let l = u64::from(g.below(6));
let refs = match l {
0 => vec![rel(0, -1, 0)],
1 => vec![rel(-1, 0, 0), rel(0, -2, 0)],
2 => vec![window(-2, 0, 0, 0)],
3 => vec![abs(g.below(rows), 0, 0), rel(0, -1, 0)],
4 => vec![column(1, 0)],
_ => vec![rel(0, 0, 1)],
};
Formula {
refs,
l,
literal: if g.chance(30) { g.below(4) } else { 1 },
}
}
pub fn build_from(m: &Model) -> Store {
let input: Vec<BuildInput> = m.cells.iter().map(|(c, f)| (*c, f.facts())).collect();
Store::build(input)
}