use crate::index::{
    config::IndexOrd,
    tree::{
        ChangeResults, IndexLimits, ParentChange, PosRef,
        nodes::TreeNodeRef,
        path::{BottomDepth, Path, TopDepth},
    },
};

#[derive(Clone)]
pub(crate) struct LockItem<K: Clone> {
    path: Path<K>,
    len: usize,
    add: Option<usize>,
    removes: Option<usize>,
    check_key: bool,
}

impl<K: IndexOrd + Clone> LockItem<K> {
    fn new(path: Path<K>, len: usize) -> Self {
        Self {
            path,
            len,
            add: None,
            removes: None,
            check_key: false,
        }
    }

    fn new_changes(path: Path<K>, len: usize, parent_change: &ParentChange<K>) -> Self {
        Self {
            path,
            len,
            add: Some(parent_change.add_count()),
            removes: Some(parent_change.remove_count()),
            check_key: parent_change.change_prev_key(),
        }
    }

    pub fn merge_v(&mut self, parent_change: &ParentChange<K>, path: Path<K>) {
        self.add = Some(self.add.unwrap_or(0) + parent_change.add_count());

        self.removes = Some(self.removes.unwrap_or(0) + parent_change.remove_count());

        self.check_key |= parent_change.change_prev_key();
        self.path = path;
    }
    pub fn len(&self) -> usize {
        let mut len = self.len;
        if let Some(a) = self.add {
            len += a;
        }
        if let Some(r) = self.removes {
            len -= r;
        }
        len
    }

    pub fn parent_id(&self) -> Option<TreeNodeRef> {
        self.path
            .get(TopDepth(self.path.len() - 2))
            .map(|x| x.pos_ref().node_ref)
    }

    pub fn path(&self) -> &Path<K> {
        &self.path
    }
    pub fn check_key(&self) -> bool {
        self.check_key
    }
}

pub(crate) struct LockGroup<K: Clone> {
    depth: BottomDepth,
    group: Vec<LockItem<K>>,
}

impl<K: IndexOrd + Clone> LockGroup<K> {
    pub(crate) fn new(depth: BottomDepth) -> Self {
        Self {
            depth,
            group: Vec::new(),
        }
    }
    pub(crate) fn is_for(&self, node_ref: &TreeNodeRef) -> bool {
        self.group.is_empty()
            || self
                .group
                .iter()
                .rfind(|e| e.path.last_id() == Some(*node_ref))
                .is_some()
    }
    pub(crate) fn add(&mut self, mut path: Path<K>, len: usize, pc: &Option<&ParentChange<K>>) {
        path.compute_depths_start(self.depth);
        for g in &mut self.group {
            if g.path.point_same_node(&path) {
                if let Some(parent_change) = pc {
                    if parent_change.path().point_same_node(&path) {
                        g.merge_v(parent_change, path);
                    }
                }
                return;
            }
        }

        if let Some(parent_change) = pc {
            if parent_change.path().point_same_node(&path) {
                self.group.push(LockItem::new_changes(path, len, parent_change));
            } else {
                self.group.push(LockItem::new(path, len));
            }
        } else {
            self.group.push(LockItem::new(path, len));
        }
    }

    fn set_change(&mut self, nr: &TreeNodeRef, cr: ChangeResults) {
        let mut found = false;
        for g in &mut self.group {
            if g.path.last_pos_ref().map(|r| &r.node_ref) == Some(nr) {
                if let Some(nadd) = cr.add {
                    g.add = Some(if let Some(add) = g.add { add + nadd } else { nadd });
                }
                if let Some(nrem) = cr.remove {
                    g.removes = Some(if let Some(r) = g.removes { r + nrem } else { nrem });
                }
                found = true;
                break;
            }
        }
        assert!(found);
    }

    pub(crate) fn parent_group(&self, limits: &IndexLimits) -> Vec<ParentChange<K>> {
        let mut parents = Vec::new();

        let mut parent_change: Option<ParentChange<K>> = None;
        for value in &self.group {
            parent_change = Some(if let Some(mut pc) = parent_change {
                if pc.id() == value.parent_id().unwrap() {
                    pc.add_child(value, limits);
                    pc
                } else {
                    if !pc.children().is_empty() {
                        parents.push(pc);
                    }
                    ParentChange::new_child(value, limits)
                }
            } else {
                ParentChange::new_child(value, limits)
            });
        }
        if let Some(x) = parent_change {
            if !x.children().is_empty() {
                parents.push(x);
            }
        }

        parents
    }

    pub fn is_not_only_root(&self) -> bool {
        self.group[0].path.len() > 1
    }
    pub fn is_only_root(&self) -> bool {
        self.group[0].path.len() == 1
    }
}

pub(crate) struct LockGroups<K: Clone> {
    groups: Vec<LockGroup<K>>,
    last: Option<LockGroup<K>>,
}
impl<K: Clone + IndexOrd> LockGroups<K> {
    pub(crate) fn new() -> Self {
        Self {
            groups: Default::default(),
            last: Default::default(),
        }
    }
    pub(crate) fn group_for_id(&mut self, pos_ref: &PosRef<K>, bottom_depth: BottomDepth) -> &mut LockGroup<K> {
        let lock_group = if let Some(lg) = self.last.take() {
            if lg.is_for(&pos_ref.node_ref) {
                lg
            } else {
                if !lg.group.is_empty() {
                    self.groups.push(lg);
                }
                LockGroup::new(bottom_depth)
            }
        } else {
            LockGroup::new(bottom_depth)
        };
        self.last = Some(lock_group);
        self.last.as_mut().unwrap()
    }

    pub(crate) fn last_set_change(&mut self, nr: &TreeNodeRef, cr: ChangeResults) {
        //TODO: replace that with a value return from the lock_logic function
        self.last.as_mut().unwrap().set_change(nr, cr)
    }
    pub(crate) fn finish(&mut self) {
        if let Some(lg) = self.last.take() {
            if !lg.group.is_empty() {
                self.groups.push(lg);
            }
        }
    }

    pub(crate) fn groups(self) -> Vec<LockGroup<K>> {
        self.groups
    }
}
