use super::super::TreePathMut;
use super::{Position, PrimInt, StructuralPosition, StructuralPositionStore, TreePath};
use std::fmt::Debug;
use num::{one, traits::NumAssign, zero};
use crate::{
    store::defaults::LabelIdentifier,
    types::{
        self, AnyType, Children, HyperAST, HyperType, IterableChildren, LabelStore, Labeled,
        NodeId, NodeStore, TypeStore, Typed, WithChildren, WithSerialization,
    },
};
#[derive(Clone, Debug)]
pub struct Scout<IdN, Idx> {
    pub(super) path: StructuralPosition<IdN, Idx>,
    pub(super) ancestors: usize,
}
impl<IdN: Eq + Copy, Idx: PrimInt + NumAssign> TreePathMut<IdN, Idx> for Scout<IdN, Idx> {
    fn pop(&mut self) -> Option<(IdN, Idx)> {
        self.path.pop()
    }
    fn goto(&mut self, node: IdN, i: Idx) {
        self.path.goto(node, i)
    }
    fn inc(&mut self, node: IdN) {
        self.path.inc(node)
    }
    fn dec(&mut self, node: IdN) {
        self.path.dec(node)
    }
}
impl<IdN: Eq + Copy, Idx: PrimInt> TreePath<IdN, Idx> for Scout<IdN, Idx> {
    fn node(&self) -> Option<&IdN> {
        self.path.node()
    }
    fn offset(&self) -> Option<&Idx> {
        self.path.offset()
    }
    fn check<'store, HAST>(&self, stores: &'store HAST) -> Result<(), ()>
    where
        HAST: HyperAST<'store, IdN = IdN::IdN>,
        HAST::T: WithChildren<ChildIdx = Idx>,
        HAST::IdN: Eq,
        IdN: NodeId,
        IdN::IdN: NodeId<IdN = IdN::IdN>,
    {
        self.path.check(stores)
    }
}
impl<IdN: Eq + Copy, Idx: PrimInt> Scout<IdN, Idx> {
    pub fn node_always(&self, x: &StructuralPositionStore<IdN, Idx>) -> IdN {
        if let Some(y) = self.path.node() {
            *y
        } else {
            x.nodes[self.ancestors]
        }
    }
    pub fn offset_always(&self, x: &StructuralPositionStore<IdN, Idx>) -> Idx {
        if let Some(y) = self.path.offset() {
            *y
        } else {
            x.offsets[self.ancestors]
        }
    }
    pub fn has_parents(&self) -> bool {
        if self.path.parents.is_empty() {
            self.ancestors != zero()
        } else {
            true
        }
    }
}
impl<IdN: Eq + Copy, Idx: PrimInt> Scout<IdN, Idx> {
    pub fn _up(&mut self) {
        self.path.pop();
        assert_eq!(self.path.parents.len(), self.path.offsets.len());
    }
    pub fn make_child(&self, node: IdN, i: Idx) -> Self {
        let mut s = self.clone();
        s.path.goto(node, i);
        s
    }
    pub fn up(&mut self, x: &StructuralPositionStore<IdN, Idx>) -> Option<IdN> {
        if self.path.parents.is_empty() {
            self.path = StructuralPosition::empty();
            assert_eq!(self.path.parents.len(), self.path.offsets.len());
            if self.ancestors == 0 {
                None
            } else {
                self.ancestors = x.parents[self.ancestors];
                Some(self.node_always(x))
            }
            } else {
            self._up();
            Some(self.node_always(x))
        }
        }
}
impl<IdN: Eq + Copy, Idx: PrimInt> Scout<IdN, Idx> {
    pub fn make_position<'store, HAST>(
        &self,
        sp: &StructuralPositionStore<HAST::IdN, Idx>,
        stores: &'store HAST,
    ) -> Position
    where
        HAST: HyperAST<'store, IdN = IdN, Label = LabelIdentifier>,
        HAST::T: Typed<Type = AnyType> + WithSerialization + WithChildren<ChildIdx = Idx>,
        <<HAST as HyperAST<'store>>::T as types::WithChildren>::ChildIdx: Debug,
        IdN: Copy + Debug + NodeId<IdN = IdN>,
    {
        self.check(stores).unwrap();
        let mut from_file = false;
        let x = self.node_always(sp);
        let b = stores.node_store().resolve(&x);
        let t = stores.type_store().resolve_type(&b);
        let len = if let Some(y) = b.try_bytes_len() {
            if !t.is_file() {
                from_file = true;
            }
            y as usize
            } else {
            0
            };
        let mut offset = 0;
        let mut path = vec![];
        if self.path.parents.is_empty() {
            return sp
                .get(super::SpHandle(self.ancestors + 1))
                .make_position_aux(stores, from_file, len, offset, path);
        }
        let mut i = self.path.parents.len() - 1;
        if from_file {
            while i > 0 {
                let p = self.path.parents[i - 1];
                let b = stores.node_store().resolve(&p);
                let t = stores.type_store().resolve_type(&b);
                let o = self.path.offsets[i];
                let o: <HAST::T as WithChildren>::ChildIdx = num::cast(o).unwrap();
                let c: usize = {
                    let v: Vec<_> = b
                        .children()
                        .unwrap()
                        .before(o - one())
                        .iter_children()
                        .collect();
                    v.iter()
                        .map(|x| {
                            let b = stores.node_store().resolve(x);
                            b.try_bytes_len().unwrap() as usize
                        })
                        .sum()
                };
                offset += c;
                if t.is_file() {
                    from_file = false;
                    i -= 1;
                    break;
                } else {
                    i -= 1;
                }
            }
        }
        if self.path.parents.is_empty() {
        } else if !from_file
        {
            loop {
                from_file = false;
                let n = self.path.parents[i];
                let b = stores.node_store().resolve(&n);
                let l = stores.label_store().resolve(b.get_label_unchecked());
                path.push(l);
                if i == 0 {
                    break;
                } else {
                    i -= 1;
                }
            }
        } else {
            let p = if i == 0 {
                sp.nodes[self.ancestors]
            } else {
                self.path.parents[i - 1]
            };
            let b = stores.node_store().resolve(&p);
            let t = stores.type_store().resolve_type(&b);
            let o = self.path.offsets[i];
            let o: <HAST::T as WithChildren>::ChildIdx = num::cast(o).unwrap();
            let c: usize = {
                let v: Vec<_> = b
                    .children()
                    .unwrap()
                    .before(o - one())
                    .iter_children()
                    .collect();
                v.iter()
                    .map(|x| {
                        let b = stores.node_store().resolve(x);
                        b.try_bytes_len().unwrap() as usize
                    })
                    .sum()
            };
            offset += c;
            if t.is_file() {
                from_file = false;
            } else {
            }
        }
        sp.get(super::SpHandle(self.ancestors + 1))
            .make_position_aux(stores, from_file, len, offset, path)
    }
}
impl<IdN: Clone, Idx: PrimInt> From<(StructuralPosition<IdN, Idx>, usize)> for Scout<IdN, Idx> {
    fn from((path, ancestors): (StructuralPosition<IdN, Idx>, usize)) -> Self {
        let path = if !path.offsets.is_empty() && path.offsets[0].is_zero() {
            assert_eq!(ancestors, 0);
            (path.parents[1..].to_owned(), path.offsets[1..].to_owned()).into()
        } else {
            path
        };
        Self { path, ancestors }
    }
}