use crate::change::*;
use crate::changestore::*;
use crate::pristine::*;
use crate::small_string::*;

pub fn undo_file_addition<
    T: GraphMutTxnT + TreeMutTxnT<TreeError = <T as GraphTxnT>::GraphError>,
>(
    txn: &mut T,
    change_id: ChangeId,
    new_vertex: &NewVertex<Option<Hash>>,
) -> Result<(), TxnErr<T::GraphError>> {
    if new_vertex.start == new_vertex.end {
        let pos = Position {
            change: change_id,
            pos: new_vertex.start,
        };
        if let Some(inode) = txn.get_revinodes(pos, None)? {
            del_inodes_with_rev(txn, inode, pos)?;
        }
    }
    Ok(())
}

pub fn undo_file_deletion<
    T: ChannelTxnT + TreeMutTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>,
    P: ChangeStore,
>(
    txn: &mut T,
    changes: &P,
    channel: &T::Channel,
    change_id: ChangeId,
    newedges: &EdgeMap<Option<Hash>>,
) -> Result<(), super::UnrecordError<P::Error, T::TreeError>> {
    for e in newedges.edges.iter().rev() {
        assert!(!e.flag.contains(EdgeFlags::PARENT));
        let source =
            txn.find_block_end(T::graph(channel), internal_pos(txn, &e.from, change_id)?)?;
        if e.flag.contains(EdgeFlags::FOLDER) && e.to.start_pos() == e.to.end_pos() {
            let dest = internal_pos(txn, &e.to.start_pos(), change_id)?;
            restore(txn, changes, channel, source, dest)?
        }
    }
    Ok(())
}

fn restore<
    T: ChannelTxnT + TreeMutTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>,
    P: ChangeStore,
>(
    txn: &mut T,
    changes: &P,
    channel: &T::Channel,
    source: Vertex<ChangeId>,
    dest: Position<ChangeId>,
) -> Result<(), super::UnrecordError<P::Error, T::TreeError>> {
    let mut stack = vec![(source, dest)];
    let mut return_value = None;
    while let Some((source, dest)) = stack.pop() {
        if let Some(parent_inode) = return_value {
            return_value = Some(restore_inode(txn, changes, source, dest, parent_inode)?);
            continue;
        }
        let mut source_parent = None;
        for e in iter_adjacent(
            txn,
            T::graph(channel),
            source,
            EdgeFlags::PARENT | EdgeFlags::FOLDER,
            EdgeFlags::all(),
        )? {
            let e = e?;
            if e.flag.contains(EdgeFlags::PARENT | EdgeFlags::FOLDER) {
                source_parent = Some(e.dest);
                break;
            }
        }
        let source_parent = source_parent.unwrap();

        if source_parent.change.is_root() {
            return_value = Some(restore_inode(txn, changes, source, dest, Inode::ROOT)?);
        } else if let Some(inode) = txn.get_revinodes(source_parent, None)? {
            return_value = Some(restore_inode(txn, changes, source, dest, inode)?);
        } else {
            let grandparent = find_youngest_parent(txn, channel, source_parent.inode_vertex())?;
            stack.push((source, dest));
            stack.push((grandparent, source_parent));
        }
    }
    Ok(())
}

fn restore_inode<
    T: TreeMutTxnT + GraphTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>,
    P: ChangeStore,
>(
    txn: &mut T,
    changes: &P,
    source: Vertex<ChangeId>,
    dest: Position<ChangeId>,
    parent_inode: Inode,
) -> Result<Inode, super::UnrecordError<P::Error, T::TreeError>> {
    let mut name = Vec::new();
    let (_, basename) = changes
        .get_file_name(|h| txn.get_external(h).unwrap(), source, &mut name)
        .map_err(super::UnrecordError::Changestore)?;
    let basename = SmallString::from_str(basename);
    let file_id = OwnedPathId {
        parent_inode,
        basename,
    };
    if let Some(inode) = txn.get_revinodes(dest, None)? {
        Ok(inode)
    } else {
        let inode = crate::fs::create_new_inode(txn)?;
        put_tree_with_rev(txn, file_id.as_file_id(), inode)?;
        put_inodes_with_rev(txn, inode, dest)?;
        Ok(inode)
    }
}

fn find_youngest_parent<T: ChannelTxnT>(
    txn: &T,
    channel: &T::Channel,
    current: Vertex<ChangeId>,
) -> Result<Vertex<ChangeId>, BlockError<T::GraphError>> {
    let mut next = None;
    for e in iter_adjacent(
        txn,
        T::graph(channel),
        current,
        EdgeFlags::FOLDER | EdgeFlags::PARENT,
        EdgeFlags::FOLDER | EdgeFlags::PARENT | EdgeFlags::DELETED | EdgeFlags::BLOCK,
    )? {
        let e = e?;
        if !e.flag.contains(EdgeFlags::FOLDER | EdgeFlags::PARENT) {
            continue;
        }
        if e.flag.contains(EdgeFlags::DELETED) {
            debug!("deleted: {:?}", e.introduced_by);
            let age = txn
                .get_changeset(T::changes(channel), e.introduced_by)?
                .unwrap();
            if let Some((ref mut age0, ref mut v)) = next {
                if age > *age0 {
                    *age0 = age;
                    *v = e.dest
                }
            } else {
                next = Some((age, e.dest))
            }
        } else {
            next = Some((0, e.dest));
            break;
        }
    }
    txn.find_block_end(T::graph(channel), next.unwrap().1)
}

pub fn undo_file_reinsertion<
    P: ChangeStore,
    T: GraphTxnT + TreeMutTxnT + TreeTxnT<TreeError = <T as GraphTxnT>::GraphError>,
>(
    txn: &mut T,
    change_id: ChangeId,
    newedges: &EdgeMap<Option<Hash>>,
) -> Result<(), super::UnrecordError<P::Error, T::TreeError>> {
    for e in newedges.edges.iter() {
        assert!(!e.flag.contains(EdgeFlags::PARENT));
        if e.to.start_pos() == e.to.end_pos() {
            let position = internal_pos(txn, &e.to.start_pos(), change_id)?;
            if let Some(inode) = txn.get_revinodes(position, None)? {
                del_inodes_with_rev(txn, inode, position)?;
            }
        }
    }
    Ok(())
}