use std::fmt::Debug;
use std::marker::PhantomData;
use std::rc::*;
use std::cell::*;
use std::hash::{Hash, Hasher};
use std::collections::*;
use either::Either::{self, Left, Right};
use itertools::Itertools;
use ss_web_utils::js::console;
#[derive(Debug, Clone)]
pub enum InsertOp<M> {
    InsertBefore {
        new: Vec<M>,
        old: M,
    },
    InsertAfter {
        new: Vec<M>,
        old: M,
    },
    Swap {
        parent: M,
        current: M,
        target: M,
    },
    Append {
        parent: M,
        new: Vec<M>,
    }
}
#[derive(Debug, Clone)]
pub struct Update<S, I> {
    pub old: S,
    pub new: I,
}
pub trait TreeApi<M, SN, SL, IN, IL> {
    fn node_unchanged(&self, new: &IN, old: &SN) -> bool;
    fn node_recyclable(&self, new: &IN, old: &SN) -> bool;
    fn node_update(&self, update: Update<&mut SN, IN>);
    fn node_crate(&self, new: IN) -> SN;
    fn leaf_unchanged(&self, new: &IL, old: &SL) -> bool;
    fn leaf_recyclable(&self, new: &IL, old: &SL) -> bool;
    fn leaf_update(&self, update: Update<&mut SL, IL>);
    fn leaf_crate(&self, new: IL) -> SL;
    fn get_meta(&self, value: Either<&SN, &SL>) -> M;
    fn insert(&self, op: InsertOp<M>);
    fn remove(&self, x: M);
}
fn get_meta_option<'a, M, SN, SL, IN, IL>(
    api: &TreeApi<M, SN, SL, IN, IL>,
    value: Option<&SN>,
) -> Option<M>
where
    M: Clone,
    SN: PartialEq + Debug,
    SL: PartialEq + Debug,
    IN: PartialEq + Debug,
    IL: PartialEq + Debug
{
    match value {
        None => None,
        Some(x) => Some(api.get_meta(Left(x)))
    }
}
#[derive(Debug, Clone, PartialEq)]
pub enum ITree<N, L> {
    Leaf(ILeaf<L>),
    Node(INode<N, L>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ILeaf<L> {
    data: L
}
#[derive(Debug, Clone, PartialEq)]
pub struct INode<N, L> {
    data: N,
    children: IChildren<N, L>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct IChildren<N, L>(pub Vec<ITree<N, L>>);
impl<N, L> ITree<N, L> {
    fn unpack_node_mut(&mut self) -> Option<&mut INode<N, L>> {
        match self {
            ITree::Node(node) => Some(node),
            _ => None
        }
    }
    pub fn new(value: Either<N, L>) -> Self {
        match value {
            Left(data) => ITree::Node(INode {
                data,
                children: IChildren(Vec::new()),
            }),
            Right(data) => ITree::Leaf(ILeaf{
                data,
            }),
        }
    }
    pub fn add_child(&mut self, value: Self) {
        if let Some(node) = self.unpack_node_mut() {
            node.children.0.push(value);
        }
    }
    pub fn get_node_mut(&mut self) -> Option<&mut N> {
        match self {
            ITree::Node(node) => Some(&mut node.data),
            _ => None
        }
    }
}
#[derive(Debug, Clone, PartialEq)]
pub enum STree<M, SN, SL, IN, IL>{
    Leaf(SLeaf<M, SN, SL, IN, IL>),
    Node(SNode<M, SN, SL, IN, IL>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct SLeaf<M, SN, SL, IN, IL> {
    mark: PhantomData<(M, SN, SL, IN, IL)>,
    data: SL,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SNode<M, SN, SL, IN, IL> {
    mark: PhantomData<(M, SN, SL, IN, IL)>,
    data: SN,
    children: SChildren<M, SN, SL, IN, IL>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SChildren<M, SN, SL, IN, IL> {
    data: Vec<STree<M, SN, SL, IN, IL>>,
}
impl<M, SN, SL, IN, IL> STree<M, SN, SL, IN, IL> {
    pub fn get_meta(&self, api: &TreeApi<M, SN, SL, IN, IL>) -> M {
        match self {
            STree::Leaf(x) => api.get_meta(Right(&x.data)),
            STree::Node(x) => api.get_meta(Left(&x.data)),
        }
    }
    pub fn to_either(&self) -> Either<&SNode<M, SN, SL, IN, IL>, &SLeaf<M, SN, SL, IN, IL>> {
        match self {
            STree::Leaf(x) => Right(x),
            STree::Node(x) => Left(x),
        }
    }
    pub fn to_either_mut(&mut self) -> Either<&mut SNode<M, SN, SL, IN, IL>, &mut SLeaf<M, SN, SL, IN, IL>> {
        match self {
            STree::Leaf(x) => Right(x),
            STree::Node(x) => Left(x),
        }
    }
    pub fn to_either_inner(&self) -> Either<&SN, &SL> {
        match self {
            STree::Leaf(x) => Right(&x.data),
            STree::Node(x) => Left(&x.data),
        }
    }
    pub fn to_either_inner_mut(&mut self) -> Either<&mut SN, &mut SL> {
        match self {
            STree::Leaf(x) => Right(&mut x.data),
            STree::Node(x) => Left(&mut x.data),
        }
    }
    pub fn unpack_leaf(&self) -> Option<(&SLeaf<M, SN, SL, IN, IL>)> {
        match self {
            STree::Leaf(x) => Some(x),
            STree::Node(_) => None,
        }
    }
    pub fn unpack_node(&self) -> Option<(&SNode<M, SN, SL, IN, IL>)> {
        match self {
            STree::Node(x) => Some(x),
            STree::Leaf(_) => None,
        }
    }
    pub fn unpack_leaf_mut(&mut self) -> Option<(&mut SLeaf<M, SN, SL, IN, IL>)> {
        match self {
            STree::Leaf(x) => Some(x),
            STree::Node(_) => None,
        }
    }
    pub fn unpack_node_mut(&mut self) -> Option<(&mut SNode<M, SN, SL, IN, IL>)> {
        match self {
            STree::Node(x) => Some(x),
            STree::Leaf(_) => None,
        }
    }
}
impl<M, SN, SL, IN, IL> STree<M, SN, SL, IN, IL>
where
    M: PartialEq + Clone + Debug,
    SN: PartialEq + Debug,
    SL: PartialEq + Debug,
    IN: PartialEq + Debug,
    IL: PartialEq + Debug
{
    pub fn from(api: &TreeApi<M, SN, SL, IN, IL>, parent: &M, new: ITree<IN, IL>) -> Self {
        let new = create_tree(api, parent, new);
        let insert_op = InsertOp::Append {
            parent: parent.clone(),
            new: vec![new.get_meta(api)],
        };
        api.insert(insert_op);
        new
    }
}
impl<M, SN, SL, IN, IL> STree<M, SN, SL, IN, IL>
where
    M: PartialEq + Clone + Debug,
    SN: PartialEq + Debug,
    SL: PartialEq + Debug,
    IN: PartialEq + Debug,
    IL: PartialEq + Debug
{
    pub fn unchanged(&self, api: &TreeApi<M, SN, SL, IN, IL>, other: &ITree<IN, IL>) -> bool {
        match (self, other) {
            (STree::Node(old), ITree::Node(new)) => old.unchanged(api, new),
            (STree::Leaf(old), ITree::Leaf(new)) => old.unchanged(api, new),
            _ => false
        }
    }
    pub fn recyclable(&self, api: &TreeApi<M, SN, SL, IN, IL>, other: &ITree<IN, IL>) -> bool {
        match (self, other) {
            (STree::Node(old), ITree::Node(new)) => old.recyclable(api, new),
            (STree::Leaf(old), ITree::Leaf(new)) => old.recyclable(api, new),
            _ => false
        }
    }
    pub fn sync(&mut self, api: &TreeApi<M, SN, SL, IN, IL>, parent: &M, new: ITree<IN, IL>) {
        match new {
            ITree::Leaf(new) => {
                match self {
                    STree::Leaf(old) => {old.sync(api, parent, new)}
                    old @ STree::Node(_) => {
                        let new_tree = create_tree(api, parent, ITree::Leaf(new));
                        let insert_op = InsertOp::Swap {
                            parent: parent.clone(),
                            current: old.get_meta(api),
                            target: new_tree.get_meta(api),
                        };
                        api.insert(insert_op);
                        *old = new_tree;
                    }
                }
            }
            ITree::Node(new) => {
                match self {
                    STree::Node(old) => {old.sync(api, parent, new)}
                    old @ STree::Leaf(_) => {
                        let new_tree = create_tree(api, parent, ITree::Node(new));
                        let insert_op = InsertOp::Swap {
                            parent: parent.clone(),
                            current: old.get_meta(api),
                            target: new_tree.get_meta(api),
                        };
                        api.insert(insert_op);
                        *old = new_tree;
                    }
                }
            }
        }
    }
}
impl<M, SN, SL, IN, IL> SLeaf<M, SN, SL, IN, IL>
where
    M: PartialEq + Clone + Debug,
    SN: PartialEq + Debug,
    SL: PartialEq + Debug,
    IN: PartialEq + Debug,
    IL: PartialEq + Debug
{
    pub fn unchanged(&self, api: &TreeApi<M, SN, SL, IN, IL>, other: &ILeaf<IL>) -> bool {
        api.leaf_unchanged(&other.data, &self.data)
    }
    pub fn recyclable(&self, api: &TreeApi<M, SN, SL, IN, IL>, other: &ILeaf<IL>) -> bool {
        api.leaf_recyclable(&other.data, &self.data)
    }
    pub fn sync(&mut self, api: &TreeApi<M, SN, SL, IN, IL>, parent: &M, new: ILeaf<IL>) {
        if self.recyclable(api, &new) {
            api.leaf_update(Update {
                old: &mut self.data,
                new: new.data,
            });
        } else {
            unimplemented!()
        }
    }
}
impl<M, SN, SL, IN, IL> SNode<M, SN, SL, IN, IL>
where
    M: PartialEq + Clone + Debug,
    SN: PartialEq + Debug,
    SL: PartialEq + Debug,
    IN: PartialEq + Debug,
    IL: PartialEq + Debug
{
    pub fn unchanged(&self, api: &TreeApi<M, SN, SL, IN, IL>, other: &INode<IN, IL>) -> bool {
        api.node_unchanged(&other.data, &self.data) && self.children.unchanged(api, &other.children)
    }
    pub fn recyclable(&self, api: &TreeApi<M, SN, SL, IN, IL>, other: &INode<IN, IL>) -> bool {
        api.node_recyclable(&other.data, &self.data)
    }
    pub fn sync(&mut self, api: &TreeApi<M, SN, SL, IN, IL>, parent: &M, new: INode<IN, IL>) {
        if self.recyclable(api, &new) {
            api.node_update(Update {
                old: &mut self.data,
                new: new.data,
            });
            self.children.sync(api, &api.get_meta(Left(&self.data)), new.children);
        } else {
            let new_tree = create_tree(api, parent, ITree::Node(new));
            let insert_op = InsertOp::Swap {
                parent: parent.clone(),
                current: api.get_meta(Left(&self.data)),
                target: new_tree.get_meta(api),
            };
            api.insert(insert_op);
            
            let new_tree = match new_tree {
                STree::Node(node) => node,
                _ => panic!()
            };
            
            *self = new_tree;
        }
    }
}
#[derive(Debug, Clone, PartialEq)]
pub enum ItemType {
    Unchanged,
    Changed,
    New,
}
#[derive(Debug, Clone)]
pub enum Item<S> {
    Unchanged(S),
    Changed(S),
    New(S),
}
impl<S> Item<S> {
    pub fn is_unchanged(&self) -> bool {
        match self {
            Item::Unchanged(_) => true,
            _ => false
        }
    }
    pub fn get_type(&self) -> ItemType {
        match self {
            Item::Unchanged(_) => ItemType::Unchanged,
            Item::Changed(_) => ItemType::Changed,
            Item::New(_) => ItemType::New,
        }
    }
}
#[derive(Debug, Clone)]
pub enum ItemGroup<M, S> {
    Unchanged {
        items: Vec<S>,
    },
    Changed {
        items: Vec<S>,
    },
    New {
        insert_op: InsertOp<M>,
        items: Vec<S>,
    }
}
impl<M, S> ItemGroup<M, S> {
    pub fn extract_items(self) -> Vec<S> {
        match self {
            ItemGroup::Unchanged{items} => items,
            ItemGroup::Changed{items} => items,
            ItemGroup::New{items, ..} => items,
        }
    }
}
impl<M, SN, SL, IN, IL> SChildren<M, SN, SL, IN, IL>
where
    M: PartialEq + Clone + Debug,
    SN: PartialEq + Debug,
    SL: PartialEq + Debug,
    IN: PartialEq + Debug,
    IL: PartialEq + Debug
{
    pub fn unchanged(&self, api: &TreeApi<M, SN, SL, IN, IL>, other: &IChildren<IN, IL>) -> bool {
        if self.data.len() == other.0.len() {
            self.data
                .iter()
                .zip(other.0.iter())
                .all(|(x, y)| x.unchanged(api, y))
        } else {
            false
        }
    }
    pub fn recyclable(&self, api: &TreeApi<M, SN, SL, IN, IL>, other: &IChildren<IN, IL>) -> bool {
        if self.data.len() == other.0.len() {
            self.data
                .iter()
                .zip(other.0.iter())
                .all(|(x, y)| x.recyclable(api, y))
        } else {
            false
        }
    }
    pub fn sync(&mut self, api: &TreeApi<M, SN, SL, IN, IL>, parent: &M, new: IChildren<IN, IL>) {
        let mut xs = new.0
            .into_iter()
            .map(|new| -> Item<STree<M, SN, SL, IN, IL>> {
                let mut unchanged = |olds| remove_item_by(olds, |old| {
                    match (&new, &old) {
                        (ITree::Leaf(x), STree::Leaf(y)) => {
                            api.leaf_unchanged(&x.data, &y.data)
                        }
                        (ITree::Node(x), STree::Node(y)) => {
                            api.node_unchanged(&x.data, &y.data)
                        }
                        _ => false
                    }
                });
                let mut changed = |olds| remove_item_by(olds, |old| {
                    match (&new, &old) {
                        (ITree::Leaf(x), STree::Leaf(y)) => {
                            api.leaf_recyclable(&x.data, &y.data)
                        }
                        (ITree::Node(x), STree::Node(y)) => {
                            api.node_recyclable(&x.data, &y.data)
                        }
                        _ => false
                    }
                });
                if let Some(unchanged) = unchanged(&mut self.data) {
                    Item::Unchanged(unchanged)
                } else {
                    if let Some(mut changed) = changed(&mut self.data) {
                        changed.sync(api, parent, new);
                        Item::Changed(changed)
                    } else {
                        Item::New(create_tree(api, parent, new))
                    }
                }
            })
            .group_by(|x| x.get_type())
            .into_iter()
            .map(|(key, group)| -> (ItemType, Vec<Item<STree<M, SN, SL, IN, IL>>>) {
                (key, group.collect_vec())
            })
            .collect_vec();
        let mut ys: Vec<(ItemType, Vec<M>)> = xs
            .iter()
            .map(|(key, group)| -> (ItemType, Vec<M>) {
                let group_ms = group
                    .iter()
                    .map(|x| -> M {
                        match x {
                            Item::Unchanged(x) => api.get_meta(x.to_either_inner()),
                            Item::Changed(x) => api.get_meta(x.to_either_inner()),
                            Item::New(x) => api.get_meta(x.to_either_inner()),
                        }
                    })
                    .collect_vec();
                (key.clone(), group_ms)
            })
            .collect_vec();
        let mut xs = {
            if self.data.is_empty() && xs.len() == 1 {
                let (key, group) = xs.remove(0);
                let group_ms: Vec<M> = group
                    .iter()
                    .map(|x| -> M {
                        match x {
                            Item::Unchanged(x) => api.get_meta(x.to_either_inner()),
                            Item::Changed(x) => api.get_meta(x.to_either_inner()),
                            Item::New(x) => api.get_meta(x.to_either_inner()),
                        }
                    })
                    .collect_vec();
                let insert_op = InsertOp::Append{
                    parent: parent.clone(),
                    new: group_ms,
                };
                let items: Vec<STree<M, SN, SL, IN, IL>> = group
                    .into_iter()
                    .map(|x| {
                        match x {
                            Item::Unchanged(x) => x,
                            Item::Changed(x) => x,
                            Item::New(x) => x,
                        }
                    })
                    .collect_vec();
                api.insert(insert_op.clone());
                vec![items]
            } else {
                xs  .into_iter()
                    .enumerate()
                    .map(|(ix, (key, group))| -> Vec<STree<M, SN, SL, IN, IL>> {
                        let group_ms: Vec<M> = group
                            .iter()
                            .map(|x| -> M {
                                match x {
                                    Item::Unchanged(x) => api.get_meta(x.to_either_inner()),
                                    Item::Changed(x) => api.get_meta(x.to_either_inner()),
                                    Item::New(x) => api.get_meta(x.to_either_inner()),
                                }
                            })
                            .collect_vec();
                        let insert_op: InsertOp<M> = {
                            if ix == 0 {
                                match ys.get(ix + 1) {
                                    Some((_, after)) => {
                                        match after.first() {
                                            Some(old) => InsertOp::InsertBefore{
                                                new: group_ms,
                                                old: old.clone(),
                                            },
                                            None => panic!()
                                        }
                                    }
                                    None => {
                                        
                                        
                                        panic!()
                                    }
                                }
                            } else {
                                match ys.get(ix - 1) {
                                    Some((_, before)) => {
                                        match before.last() {
                                            Some(old) => InsertOp::InsertAfter{
                                                new: group_ms,
                                                old: old.clone(),
                                            },
                                            None => panic!()
                                        }
                                    }
                                    None => match ys.get(ix + 1) {
                                        Some((_, after)) => {
                                            match after.first() {
                                                Some(old) => InsertOp::InsertBefore{
                                                    new: group_ms,
                                                    old: old.clone(),
                                                },
                                                None => panic!()
                                            }
                                        }
                                        None => panic!()
                                    }
                                }
                            }
                        };
                        let items: Vec<STree<M, SN, SL, IN, IL>> = group
                            .into_iter()
                            .map(|x| {
                                match x {
                                    Item::Unchanged(x) => x,
                                    Item::Changed(x) => x,
                                    Item::New(x) => x,
                                }
                            })
                            .collect_vec();
                        match &key {
                            ItemType::New => {
                                console::log(format!("tree: {:#?}", (key, insert_op.clone())));
                                api.insert(insert_op.clone());
                            }
                            _ => {}
                        };
                        items
                    })
                    .collect_vec()
            }
        };
        
        let mut removed = Vec::new();
        std::mem::swap(&mut self.data, &mut removed);
        for r in removed {
            api.remove(api.get_meta(r.to_either_inner()));
        }
        
        for mut group in xs {
            self.data.append(&mut group);
        }
    }
}
pub fn remove_item_by<T: PartialEq>(xs: &mut Vec<T>, f: impl Fn(&T)->bool) -> Option<T> {
    let pos = xs.iter().position(|x| f(x))?;
    Some(xs.remove(pos))
}
pub fn unsafe_unpack_same_type<M, SN, SL, IN, IL>(
    new: ITree<IN, IL>,
    old: STree<M, SN, SL, IN, IL>
) -> Either<(INode<IN, IL>, SNode<M, SN, SL, IN, IL>), (ILeaf<IL>, SLeaf<M, SN, SL, IN, IL>)>
where
    M: PartialEq + Clone + Debug,
    SN: PartialEq + Debug,
    SL: PartialEq + Debug,
    IN: PartialEq + Debug,
    IL: PartialEq + Debug
{
    match new {
        ITree::Leaf(new) => {
            match old {
                STree::Leaf(old) => {
                    Either::Right((new, old))
                }
                old @ STree::Node(_) => {
                    panic!()
                }
            }
        }
        ITree::Node(new) => {
            match old {
                STree::Node(old) => {
                    Either::Left((new, old))
                }
                old @ STree::Leaf(_) => {
                    panic!()
                }
            }
        }
    }   
}
pub fn create_tree<M, SN, SL, IN, IL>(api: &TreeApi<M, SN, SL, IN, IL>, parent: &M, new: ITree<IN, IL>) -> STree<M, SN, SL, IN, IL>
where
    M: PartialEq + Clone + Debug,
    SN: PartialEq + Debug,
    SL: PartialEq + Debug,
    IN: PartialEq + Debug,
    IL: PartialEq + Debug
{
    match new {
        ITree::Leaf(leaf) => {
            let data = api.leaf_crate(leaf.data);
            STree::Leaf(SLeaf {
                mark: PhantomData,
                data,
            })
        }
        ITree::Node(node) => {
            let data = api.node_crate(node.data);
            let mut children_ms = Vec::new();
            let children = node.children.0
                .into_iter()
                .map(|c| {
                    let c = create_tree(api, &api.get_meta(Left(&data)), c);
                    children_ms.push(c.get_meta(api));
                    c
                })
                .collect_vec();
            let children_insert_op = InsertOp::Append {
                parent: api.get_meta(Left(&data)),
                new: children_ms,
            };
            api.insert(children_insert_op);
            let mut children = SChildren{data: children};
            STree::Node(SNode {
                mark: PhantomData,
                data,
                children,
            })
        }
    }
}