use {
    r3vi::{view::{OuterViewPort, singleton::*}, buffer::{singleton::*}},
    laddertypes::{TypeDict, TypeTerm, TypeID, MorphismType, MorphismBase, Morphism},
    crate::{
        repr_tree::{ReprTree, ReprTreeExt, GenericReprTreeMorphism},
        edit_tree::EditTree
    },
    std::{
        collections::HashMap,
        sync::{Arc, RwLock},
    }
};

//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>

#[derive(Clone)]
pub struct Context {
    /// assigns a name to every type
    pub type_dict: Arc<RwLock<TypeDict>>,

    pub morphisms: laddertypes::morphism::MorphismBase< GenericReprTreeMorphism >,

    /// named vertices of the graph
    nodes: HashMap< String, Arc<RwLock<ReprTree>> >,

    /// todo: beautify
    /// types that can be edited as lists
    /// do we really need this?
    pub list_types: Vec< TypeID >,
    pub meta_chars: Vec< char >,

    edittree_hook: Arc< dyn Fn(&mut EditTree, TypeTerm) + Send +Sync +'static >,

    /// recursion
    parent: Option<Arc<RwLock<Context>>>,
}

impl Context {
    pub fn with_parent(
        parent: Option<Arc<RwLock<Context>>>
    ) -> Self {
        let mut dict = TypeDict::new();
        let list_typeid = dict.add_typename("List".into());

        Context {
            type_dict: match parent.as_ref() {
                Some(p) => p.read().unwrap().type_dict.clone(),
                None => Arc::new(RwLock::new(dict))
            },
            morphisms: MorphismBase::new( list_typeid ),
            nodes: HashMap::new(),
            list_types: match parent.as_ref() {
                Some(p) => p.read().unwrap().list_types.clone(),
                None => Vec::new()
            },
            meta_chars: match parent.as_ref() {
                Some(p) => p.read().unwrap().meta_chars.clone(),
                None => Vec::new()
            },
            parent,
            edittree_hook: Arc::new(|_et, _t| {})
        }
    }

    pub fn new() -> Self {
        Context::with_parent(None)
    }

    pub fn set_edittree_hook(&mut self, hook: Arc< dyn Fn(&mut EditTree, TypeTerm) + Send +Sync +'static >) {
        self.edittree_hook = hook;
    }

    pub fn depth(&self) -> usize {
        if let Some(parent) = self.parent.as_ref() {
            parent.read().unwrap().depth() + 1
        } else {
            0
        }
    }

    pub fn apply_morphism( &self, rt: &Arc<RwLock<ReprTree>>, ty: &MorphismType ) {
        if let Some(path)
            = self.morphisms.find_morphism_path( ty.clone().normalize() )
        {
            let mut path = path.into_iter();
            if let Some(mut src_type) = path.next() {
                for dst_type in path {
                   if let Some(( m, mut τ, σ )) =
                       self.morphisms.find_morphism_with_subtyping(
                        &laddertypes::MorphismType {
                            src_type: src_type.clone(),
                            dst_type: dst_type.clone()
                        }
                    ) {                   
                        let mut rt = rt.descend( τ ).expect("descend src repr");
                        (m.setup_projection)( &mut rt, &σ );
                    }

                    src_type = dst_type;
                }
            }
        } else {
            eprintln!("no path found");
        }    
    }

    pub fn make_repr(ctx: &Arc<RwLock<Self>>, t: &TypeTerm) -> Arc<RwLock<ReprTree>> {
        let rt = Arc::new(RwLock::new(ReprTree::new( TypeTerm::unit() )));
        ctx.read().unwrap().apply_morphism( &rt, &MorphismType{ src_type: TypeTerm::unit(), dst_type: t.clone() } );
        rt
    }

    pub fn parse(ctx: &Arc<RwLock<Self>>, s: &str) -> TypeTerm {
        ctx.read().unwrap().type_term_from_str(s).expect("could not parse type term")
    }

    pub fn add_typename(&mut self, tn: &str) -> TypeID {
        self.type_dict.write().unwrap().add_typename(tn.to_string())
    }

    pub fn add_varname(&mut self, vn: &str) -> TypeID {
        self.type_dict.write().unwrap().add_varname(vn.to_string())
    }

    pub fn add_synonym(&mut self, new: &str, old: &str) {
        self.type_dict.write().unwrap().add_synonym(new.to_string(), old.to_string());
    }

    pub fn add_list_typename(&mut self, tn: &str) {
        let tid = self.add_typename(tn);
        self.list_types.push( tid );
    }

    pub fn is_list_type(&self, t: &TypeTerm) -> bool {
        match t {
            TypeTerm::TypeID(id) => {
                self.list_types.contains(id)
            }
            TypeTerm::Ladder(args) |
            TypeTerm::App(args) => {
                if args.len() > 0 {
                    if self.is_list_type(&args[0]) {
                        true
                    } else {
                        false
                    }
                } else {
                    false
                }
            }
            _ => false
        }
    }

    pub fn get_typeid(&self, tn: &str) -> Option<TypeID> {
        self.type_dict.read().unwrap().get_typeid(&tn.into())
    }

    pub fn get_fun_typeid(&self, tn: &str) -> Option<u64> {
        match self.get_typeid(tn) {
            Some(TypeID::Fun(x)) => Some(x),
            _ => None
        }
    }

    pub fn get_typename(&self, tid: &TypeID) -> Option<String> {
        self.type_dict.read().unwrap().get_typename(tid)
    }

    pub fn get_var_typeid(&self, tn: &str) -> Option<u64> {
        match self.get_typeid(tn) {
            Some(TypeID::Var(x)) => Some(x),
            _ => None
        }
    }

    pub fn type_term_from_str(&self, tn: &str) -> Result<TypeTerm, laddertypes::parser::ParseError> {
        self.type_dict.write().unwrap().parse(&tn)
    }

    pub fn type_term_to_str(&self, t: &TypeTerm) -> String {
        self.type_dict.read().unwrap().unparse(&t)
    }

    /// adds an object without any representations
    pub fn add_obj(ctx: Arc<RwLock<Context>>, name: String, typename: &str) {
        let type_tag = ctx.read().unwrap()
            .type_dict.write().unwrap()
            .parse(typename).unwrap();
/*
        if let Some(node) = Context::make_node(&ctx, type_tag, SingletonBuffer::new(0).get_port()) {
            ctx.write().unwrap().nodes.insert(name, node);
        }
*/
    }

    pub fn get_obj(&self, name: &String) -> Option< Arc<RwLock<ReprTree>> > {
        if let Some(obj) = self.nodes.get(name) {
            Some(obj.clone())
        } else if let Some(parent) = self.parent.as_ref() {
            parent.read().unwrap().get_obj(name)
        } else {
            None
        }
    }

    pub fn setup_edittree(
        &self,
        rt: Arc<RwLock<ReprTree>>,
        depth: OuterViewPort<dyn SingletonView<Item = usize>>
    ) -> Option<SingletonBuffer<Arc<RwLock<EditTree>>>> {
        if let Some(new_edittree) =
            rt.descend(self.type_term_from_str("EditTree").unwrap())
        {
            let typ = rt.read().unwrap().get_type().clone();
            let buf = new_edittree.singleton_buffer::<Arc<RwLock<EditTree>>>();
            (*self.edittree_hook)(
                &mut *buf.get().write().unwrap(),
                typ
            );
            Some(buf)
        } else {
            eprintln!("cant find edit tree repr {} ~Ψ~ {}",
                self.type_term_to_str(rt.read().unwrap().get_halo_type()),
                self.type_term_to_str(rt.read().unwrap().get_type())
            );
            None
        }
    }
}

//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>