use alloc::vec;
use alloc::vec::Vec;
use crate::{ApplyEdit, CmdEdit, ExtractEdit, RevertEdit};
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct HistoryTreeNodeId(usize);
impl HistoryTreeNodeId {
#[inline]
pub fn new(index: usize) -> Self {
Self(index)
}
#[inline]
pub fn index(self) -> usize {
self.0
}
}
pub struct HistoryNode<E, Cmd = ()> {
pub cmd_edit: CmdEdit<Cmd, E>,
pub parent: HistoryTreeNodeId,
pub children: Vec<HistoryTreeNodeId>,
pub depth: u64,
}
pub struct HistoryTreeCursor<C> {
curr_node: HistoryTreeNodeId,
container: C,
}
impl<C> HistoryTreeCursor<C> {
#[inline]
pub fn curr_node(&self) -> HistoryTreeNodeId {
self.curr_node
}
#[inline]
pub fn into_container(self) -> C {
self.container
}
#[inline]
pub fn container(&self) -> &C {
&self.container
}
#[inline]
pub fn container_mut(&mut self) -> &mut C {
&mut self.container
}
}
impl<C> AsRef<C> for HistoryTreeCursor<C> {
#[inline]
fn as_ref(&self) -> &C {
&self.container
}
}
pub struct HistoryTree<E, Cmd = ()> {
nodes: Vec<HistoryNode<E, Cmd>>,
}
impl<Cmd: Default, E: Default> HistoryTree<E, Cmd> {
#[inline]
pub fn new() -> Self {
Self {
nodes: vec![HistoryNode {
cmd_edit: CmdEdit::default(), parent: HistoryTreeNodeId::new(0), children: Vec::new(),
depth: 0,
}],
}
}
}
impl<Cmd, E> HistoryTree<E, Cmd> {
#[inline]
pub fn root_cursor<T>(&self, target: T) -> HistoryTreeCursor<T> {
self.cursor(HistoryTreeNodeId::new(0), target)
}
#[inline]
fn cursor<T>(&self, curr_node: HistoryTreeNodeId, target: T) -> HistoryTreeCursor<T> {
HistoryTreeCursor {
curr_node,
container: target,
}
}
}
impl<Cmd: Default, E> HistoryTree<E, Cmd> {
#[inline]
pub fn commit<T>(&mut self, cursor: &mut HistoryTreeCursor<T>)
where
E: ExtractEdit<T>,
{
self.cmd_commit(Default::default(), cursor);
}
}
impl<Cmd, E> HistoryTree<E, Cmd> {
#[inline]
pub fn cmd_commit<T>(&mut self, cmd: Cmd, cursor: &mut HistoryTreeCursor<T>)
where
E: ExtractEdit<T>,
{
self.nodes.push(HistoryNode {
cmd_edit: CmdEdit {
cmd,
edit: E::extract_edit(cursor.container_mut()),
},
parent: cursor.curr_node,
children: Vec::new(),
depth: self.nodes[cursor.curr_node.index()].depth + 1,
});
let node_id = HistoryTreeNodeId::new(self.nodes.len() - 1);
self.nodes[cursor.curr_node.index()].children.push(node_id);
cursor.curr_node = node_id;
}
}
impl<Cmd: Clone, E: Clone> HistoryTree<E, Cmd> {
#[inline]
pub fn undo<T>(&mut self, cursor: &mut HistoryTreeCursor<T>) -> Option<Cmd>
where
E: RevertEdit<T>,
{
if cursor.curr_node.index() == 0 {
return None;
}
let curr_node = cursor.curr_node;
let parent_node = self.nodes[curr_node.index()].parent;
let CmdEdit { cmd, edit } = self.nodes[curr_node.index()].cmd_edit.clone();
edit.revert_edit(&mut cursor.container);
cursor.curr_node = parent_node;
Some(cmd)
}
#[inline]
pub fn redo<T>(&mut self, cursor: &mut HistoryTreeCursor<T>, node: HistoryTreeNodeId) -> Cmd
where
E: ApplyEdit<T> + RevertEdit<T>,
{
assert!(
self.nodes[cursor.curr_node.index()]
.children
.contains(&node)
);
let CmdEdit { cmd, edit } = self.nodes[node.index()].cmd_edit.clone();
edit.apply_edit(&mut cursor.container);
cursor.curr_node = node;
cmd
}
#[inline]
pub fn checkout<T>(
&mut self,
cursor: &mut HistoryTreeCursor<T>,
target_node: HistoryTreeNodeId,
) -> Vec<Cmd>
where
E: ApplyEdit<T> + RevertEdit<T>,
{
let mut source_node = cursor.curr_node;
let mut target_path = Vec::new();
let mut source_depth = self.nodes[source_node.index()].depth;
let mut target_depth = self.nodes[target_node.index()].depth;
let mut aligned_target = target_node;
while source_depth > target_depth {
assert!(self.undo(cursor).is_some());
source_node = cursor.curr_node;
source_depth -= 1;
}
while target_depth > source_depth {
target_path.push(aligned_target);
aligned_target = self.nodes[aligned_target.index()].parent;
target_depth -= 1;
}
while source_node != aligned_target {
assert!(self.undo(cursor).is_some());
source_node = cursor.curr_node;
target_path.push(aligned_target);
aligned_target = self.nodes[aligned_target.index()].parent;
}
let mut cmds = Vec::new();
for node in target_path.into_iter().rev() {
cmds.push(self.redo(cursor, node));
}
cmds
}
}