use std::collections::{BTreeMap, BTreeSet};
use super::conflict::parent_conflict;
use super::flat::{Flat, Parent, Place, accepts};
use super::same::same_value;
use crate::codec::ConflictRec;
use crate::codec::schema::Kind;
use crate::identity::Key;
#[derive(Debug, Clone)]
pub(super) struct Placed {
pub(super) place: Place,
pub(super) conflict: Option<ConflictRec>,
}
struct Ladder {
chosen: Place,
apart: bool,
base: Option<Place>,
levels: Vec<Place>,
at: usize,
}
impl Ladder {
fn place(&self) -> Place {
self.levels[self.at]
}
fn can_step(&self) -> bool {
self.at + 1 < self.levels.len()
}
fn should_step(&self) -> bool {
self.can_step() && Some(self.place()) != self.base
}
}
struct Fits<'a> {
kinds: &'a BTreeMap<Key, Kind>,
top: Option<Kind>,
}
impl Fits<'_> {
fn fits(&self, key: Key, (parent, name): Place) -> bool {
let parent = match parent {
Parent::Top => self.top,
Parent::Key(parent) if parent == key => return false,
Parent::Key(parent) => match self.kinds.get(&parent) {
Some(kind) => Some(*kind),
None => return false,
},
};
accepts(parent, name, self.kinds[&key])
}
}
pub(super) fn merge_places(
sides: &[Flat; 3],
kinds: &BTreeMap<Key, Kind>,
top: Option<Kind>,
sure: &BTreeSet<Key>,
) -> Result<BTreeMap<Key, Placed>, Key> {
let fits = Fits { kinds, top };
let mut ladders = BTreeMap::new();
for key in kinds.keys() {
match ladder(*key, sides, &fits) {
Some(ladder) => {
ladders.insert(*key, ladder);
}
None if !sure.contains(key) => {}
None => return Err(*key),
}
}
settle_cycles(&mut ladders)?;
Ok(ladders
.into_iter()
.map(|(key, ladder)| {
let place = ladder.place();
let parents = sides.each_ref().map(|side| {
let (parent, _) = side.place(&key)?;
side.parent_value(parent)
});
let mut held = parents.iter().flatten();
let first = held.next();
let differ = held.any(|parent| first.is_none_or(|first| !same_value(first, parent)));
let conflict = ((ladder.apart || place != ladder.chosen) && differ)
.then(|| parent_conflict(parents));
(key, Placed { place, conflict })
})
.collect())
}
fn ladder(key: Key, sides: &[Flat; 3], fits: &Fits<'_>) -> Option<Ladder> {
let [base, ours, theirs] = sides.each_ref().map(|side| side.place(&key));
let (chosen, apart) = chosen(base, ours, theirs)?;
let takes = |place: &Place| fits.fits(key, *place);
let side = [ours, theirs].into_iter().flatten().find(takes);
let mut levels: Vec<Place> = [Some(chosen), base]
.into_iter()
.flatten()
.filter(takes)
.chain(side)
.collect();
levels.dedup();
(!levels.is_empty()).then_some(Ladder {
chosen,
apart,
base,
levels,
at: 0,
})
}
fn chosen(
base: Option<Place>,
ours: Option<Place>,
theirs: Option<Place>,
) -> Option<(Place, bool)> {
Some(match (ours, theirs) {
(Some(ours), Some(theirs)) if ours == theirs => (ours, false),
(Some(ours), Some(theirs)) => match base {
Some(base) if ours == base => (theirs, false),
Some(base) if theirs == base => (ours, false),
Some(base) => (base, true),
None => (ours, true),
},
(Some(only), None) | (None, Some(only)) => (only, false),
(None, None) => return None,
})
}
fn settle_cycles(ladders: &mut BTreeMap<Key, Ladder>) -> Result<(), Key> {
loop {
let parents = ladders
.iter()
.map(|(key, ladder)| (*key, ladder.place().0))
.collect();
let cycles = on_cycles(&parents);
let Some(first) = cycles.first().copied() else {
return Ok(());
};
let away = |key: &Key| ladders[key].should_step();
let stepping: Vec<Key> = if cycles.iter().any(away) {
cycles.into_iter().filter(away).collect()
} else {
cycles
.into_iter()
.filter(|key| ladders[key].can_step())
.collect()
};
if stepping.is_empty() {
return Err(first);
}
for key in stepping {
ladders.get_mut(&key).expect("a record with a place").at += 1;
}
}
}
fn on_cycles(parents: &BTreeMap<Key, Parent>) -> BTreeSet<Key> {
let mut done = BTreeSet::new();
let mut cycles = BTreeSet::new();
for start in parents.keys() {
let mut path: Vec<Key> = Vec::new();
let mut next = Some(*start);
while let Some(key) = next.filter(|key| !done.contains(key)) {
if let Some(at) = path.iter().position(|each| *each == key) {
cycles.extend(path[at..].iter().copied());
break;
}
path.push(key);
next = match parents.get(&key) {
Some(Parent::Key(parent)) => Some(*parent),
_ => None,
};
}
done.extend(path);
}
cycles
}