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