use num::ToPrimitive;
use super::{Position, PrimInt, StructuralPosition, TreePath};
use std::path::PathBuf;
use crate::{
    store::{
        defaults::{LabelIdentifier, NodeIdentifier},
        nodes::HashedNodeRef,
    },
    types::{
        AnyType, Children, HyperAST, HyperType, IterableChildren, LabelStore, Labeled, NodeStore,
        TypeStore, WithChildren, WithSerialization,
    },
};
pub fn compute_range<'store, It, HAST>(
    root: HAST::IdN,
    offsets: &mut It,
    stores: &'store HAST,
) -> (usize, usize, HAST::IdN)
where
    HAST: HyperAST<'store>,
    HAST::IdN: Copy,
    HAST::T: WithSerialization,
    It: Iterator,
    It::Item: PrimInt,
{
    let mut offset = 0;
    let mut x = root;
    for o in offsets {
        let b = stores.node_store().resolve(&x);
        if let Some(cs) = b.children() {
            let cs = cs.clone();
            for y in 0..o.to_usize().unwrap() {
                let id = &cs[num::cast(y).unwrap()];
                let b = stores.node_store().resolve(id);
                offset += b.try_bytes_len().unwrap_or(0).to_usize().unwrap();
            }
            if let Some(a) = cs.get(num::cast(o).unwrap()) {
                x = *a;
            } else {
                break;
            }
        } else {
            break;
        }
    }
    let b = stores.node_store().resolve(&x);
    let len = b.try_bytes_len().unwrap_or(0).to_usize().unwrap();
    (offset, offset + len, x)
}
pub fn compute_position<'store, HAST, It>(
    root: HAST::IdN,
    offsets: &mut It,
    stores: &'store HAST,
) -> (Position, HAST::IdN)
where
    It::Item: Clone,
    HAST::IdN: Clone,
    HAST: HyperAST<'store>,
    HAST::T: WithSerialization + WithChildren,
    It: Iterator<Item = HAST::Idx>,
{
    let mut offset = 0;
    let mut x = root;
    let mut path = vec![];
    for o in &mut *offsets {
        let b = stores.node_store().resolve(&x);
        let t = stores.type_store().resolve_type(&b);
        if t.is_directory() || t.is_file() {
            let l = stores.label_store().resolve(b.get_label_unchecked());
            path.push(l);
        }
        if let Some(cs) = b.children() {
            let cs = cs.clone();
            if !t.is_directory() {
                for y in cs.before(o.clone()).iter_children() {
                    let b = stores.node_store().resolve(y);
                    offset += b.try_bytes_len().unwrap().to_usize().unwrap();
                }
            } else {
                }
            if let Some(a) = cs.get(o) {
                x = a.clone();
            } else {
                break;
            }
        } else {
            break;
        }
    }
    assert!(offsets.next().is_none());
    let b = stores.node_store().resolve(&x);
    let t = stores.type_store().resolve_type(&b);
    if t.is_directory() || t.is_file() {
        let l = stores.label_store().resolve(b.get_label_unchecked());
        path.push(l);
    }
    let len = if !t.is_directory() {
        b.try_bytes_len().unwrap().to_usize().unwrap()
    } else {
        0
    };
    let file = PathBuf::from_iter(path.iter());
    (Position::new(file, offset, len), x)
}
pub fn compute_position_and_nodes<'store, HAST, It: Iterator>(
    root: HAST::IdN,
    offsets: &mut It,
    stores: &'store HAST,
) -> (Position, Vec<HAST::IdN>)
where
    It::Item: Clone,
    HAST::IdN: Clone,
    HAST: HyperAST<'store>,
    HAST::T: WithSerialization + WithChildren<ChildIdx = It::Item>,
{
    let mut offset = 0;
    let mut x = root;
    let mut path_ids = vec![];
    let mut path = vec![];
    for o in &mut *offsets {
        let b = stores.node_store().resolve(&x);
        let t = stores.type_store().resolve_type(&b);
        if t.is_directory() || t.is_file() {
            let l = stores.label_store().resolve(b.get_label_unchecked());
            path.push(l);
        }
        if let Some(cs) = b.children() {
            let cs = cs.clone();
            if !t.is_directory() {
                for y in cs.before(o.clone()).iter_children() {
                    let b = stores.node_store().resolve(y);
                    offset += b.try_bytes_len().unwrap().to_usize().unwrap();
                }
            } else {
                }
            if let Some(a) = cs.get(o) {
                x = a.clone();
                path_ids.push(x.clone());
            } else {
                break;
            }
        } else {
            break;
        }
    }
    assert!(offsets.next().is_none());
    let b = stores.node_store().resolve(&x);
    let t = stores.type_store().resolve_type(&b);
    if t.is_directory() || t.is_file() {
        let l = stores.label_store().resolve(b.get_label_unchecked());
        path.push(l);
    }
    let len = if !t.is_directory() {
        b.try_bytes_len().unwrap().to_usize().unwrap()
    } else {
        0
    };
    let file = PathBuf::from_iter(path.iter());
    path_ids.reverse();
    (Position::new(file, offset, len), path_ids)
}
impl StructuralPosition<NodeIdentifier, u16> {
    pub fn make_position<'store, HAST>(&self, stores: &'store HAST) -> Position
    where
        HAST: HyperAST<
            'store,
            T = HashedNodeRef<'store>,
            IdN = NodeIdentifier,
            Label = LabelIdentifier,
        >,
        HAST::TS: TypeStore<HashedNodeRef<'store>, Ty = AnyType>,
        {
        self.check(stores).unwrap();
        let mut from_file = false;
        let x = *self.node().unwrap();
        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.parents.is_empty() {
            let file = PathBuf::from_iter(path.iter().rev());
            return Position::new(file, offset, len);
        }
        let mut i = self.parents.len() - 1;
        if from_file {
            while i > 0 {
                let p = self.parents[i - 1];
                let b = stores.node_store().resolve(&p);
                let t = stores.type_store().resolve_type(&b);
                let o = self.offsets[i];
                let c: usize = {
                    let v: Vec<_> = b.children().unwrap().before(o.to_u16().unwrap() - 1).into();
                    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.parents.is_empty() {
        } else if !from_file
        {
            loop {
                let n = self.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 = self.parents[i - 1];
            let b = stores.node_store().resolve(&p);
            let o = self.offsets[i];
            let c: usize = {
                let v: Vec<_> = b.children().unwrap().before(o.to_u16().unwrap() - 1).into();
                v.iter()
                    .map(|x| {
                        let b = stores.node_store().resolve(x);
                        b.try_bytes_len().unwrap() as usize
                    })
                    .sum()
            };
            offset += c;
        }
        let file = PathBuf::from_iter(path.iter().rev());
        Position::new(file, offset, len)
    }
}