use {
    crate::{c_gen_types::{encode_morph_type_to_symbol, encode_type_to_symbol, encode_type_to_value, get_c_repr_type}, morphism::LdmcPrimMorph, struct_layout::{get_type_size, LayoutType, ObjectSize, StructLayout}}, chumsky::{chain::Chain, primitive::Container}, laddertypes::{morphism::{Morphism, MorphismInstance}, morphism_sugared::{MorphismInstance2, SugaredMorphism, SugaredMorphismType}, parser::*, substitution_sugared::SugaredSubstitution, unparser::*, Substitution, SugaredEnumVariant, SugaredStructMember, SugaredTypeTerm, TypeDict, TypeTerm}, std::collections::HashMap
};

impl LdmcPrimMorph {
    pub fn instantiated_symbol_name(&self, dict: &mut impl TypeDict, σ: &impl SugaredSubstitution) -> String {
        let mut s = self.symbol.clone();
        for (k_id,v) in self.type_args.iter() {
            if let Some(val_trm) = σ.get(k_id) {
                if let laddertypes::TypeID::Var(var_id) = k_id {
                    let name = dict.get_varname(*var_id).unwrap();
                    let val_str = encode_type_to_symbol(dict, &val_trm);
                    s.push_str(&format!("_{}_{}", name, val_str));
                }
            } else {
                if let laddertypes::TypeID::Var(var_id) = k_id {
                    let k = dict.get_varname(*var_id).unwrap();
                    s.push_str(&format!("_{:?}_MISSING", k));
                    eprintln!("INCOMPLETE MORPHISM INSTANTIATION, missing type parameter {} ({})", k, var_id);
                }
            }
        }
        s
    }

    pub fn expected_c_type_signature(&self, dict: &mut impl TypeDict,
        σ: &std::collections::HashMap<laddertypes::TypeID, laddertypes::SugaredTypeTerm>
    ) -> String {
        format!("int {} ({} const * restrict src, {} * restrict dst);",
            self.instantiated_symbol_name(dict, σ),
            get_c_repr_type(dict, self.ty.src_type.clone(), true).expect("cant get c-repr type for src type"),
            get_c_repr_type(dict, self.ty.dst_type.clone(), true).expect("cant get c-repr type for dst type"))
    }

    pub fn generate_instantiation(&self, dict: &mut impl TypeDict, σ: &impl SugaredSubstitution) -> Option<String> {
        let mut s = String::new();
        let symbol = self.instantiated_symbol_name(dict, σ);
        let src_c_symbol = encode_type_to_symbol(dict, &self.ty.strip_halo().src_type);
        let dst_c_symbol = encode_type_to_symbol(dict, &self.ty.strip_halo().dst_type);

        s.push_str(&format!(r#"
int {} ( {} const * restrict src, {} * restrict dst ) {{
"#,
            symbol, src_c_symbol, dst_c_symbol,
        ));
        s.push_str(&self.c_source);
        s.push_str(&format!(r#"
    return 0;
}}
"#));
        Some(s)
    }
}





pub struct LdmcCTargetMorph {
    includes: Vec< String >,
    active_types: Vec< SugaredTypeTerm >,
    active_instantiations: Vec< LdmcPrimMorph >,
}

impl LdmcCTargetMorph {
    pub fn new() -> Self {
        LdmcCTargetMorph {
            includes: vec![
                "#include <stdint.h>".into(),
                "#define FUSE(x) { int result = x; if(result) return result; }".into()
            ],
            active_instantiations: Vec::new(),
            active_types: Vec::new(),
        }
    }

    pub fn add_instantiation(
        &mut self,
        dict: &mut impl TypeDict,
        morph: MorphismInstance2<LdmcPrimMorph>,
    ) -> Result<LdmcPrimMorph, ()> {
        let new_inst = self.bake_morphism(dict, morph)?;
        if ! self.active_instantiations.contains(&new_inst) {
            self.active_instantiations.push(new_inst.clone());
        }

        let ty = new_inst.get_type().strip_halo();
        if ! self.active_types.contains(&ty.src_type) {
            self.active_types.push(ty.src_type);
        }
        if ! self.active_types.contains(&ty.dst_type) {
            self.active_types.push(ty.dst_type);
        }

        Ok(new_inst)
    }

    pub fn into_c_source(self, dict: &mut impl TypeDict) -> String {
        let mut source = String::new();
        for inc in self.includes {
            source.push_str(&inc);
            source.push('\n');
        }

        for ty in self.active_types {
            source.push_str(&format!("
    typedef {} {};
",
                get_c_repr_type(dict, ty.clone(), false).expect("cant get c-repr type"),
                encode_type_to_symbol(dict, &ty)));
        }

        for m in self.active_instantiations {
            source.push_str(&m.generate_instantiation(dict, &HashMap::new()).expect("cant create function"));
        }

        source
    }

    pub fn bake_morphism(
        &mut self,
        dict: &mut impl laddertypes::TypeDict,
        morph_inst: MorphismInstance2<LdmcPrimMorph>,
    ) -> Result<LdmcPrimMorph, ()> {

        match &morph_inst {
            MorphismInstance2::Primitive { ψ, σ, morph } => {
                let mut c_source = String::new();
                for (ty_id, kind) in morph.type_args.iter() {
                    if let Some(val) = σ.get(ty_id) {
                        let type_var_value =
                            if let Some(c_repr_type) = get_c_repr_type(dict, val.clone(), true) {
                                c_repr_type
                            } else {
                                encode_type_to_value(dict, &val)
                            };

                        c_source.push_str(&format!("    #define {} {}\n", dict.get_typename(&ty_id).unwrap(), type_var_value));
                    }
                }
                c_source.push_str(&morph.c_source);
                for (ty_id, kind) in morph.type_args.iter() {
                    c_source.push_str(&format!("\n    #undef {}", dict.get_typename(&ty_id).unwrap()));
                }

                Ok(LdmcPrimMorph{
                    symbol: morph.instantiated_symbol_name(dict, σ),
                    type_args: Vec::new(),
                    ty: morph_inst.get_haloless_type(),
                    c_source
                })
            },
            MorphismInstance2::Chain { path } => {
                let ty = morph_inst.get_type().strip_halo();
                let symbol = encode_morph_type_to_symbol(dict, &morph_inst.get_haloless_type());

                let mut c_source = String::new();

                if path.len() > 1 {
                    c_source.push_str(r#"
            uint8_t bufA[128];
            uint8_t bufB[128];
    "#);
                }

                for (i,morph) in path.iter().enumerate() {
                    if let Ok(inst) = self.add_instantiation(dict, morph.clone()) {
                        let morph_symbol = inst.instantiated_symbol_name(dict, &morph.get_subst());

                        let src_c_type = encode_type_to_symbol(dict, &inst.get_type().strip_halo().src_type);
                        let dst_c_type = encode_type_to_symbol(dict, &inst.get_type().strip_halo().dst_type);

                        let src_buf = if i == 0 { "src" } else if i%2 == 0 { "(void*)bufA" } else { "(void*)bufB" };
                        let dst_buf = if i+1 == path.len() { "dst" } else if i%2 == 0 { "(void*)bufB" } else { "(void*)bufA" };

                        c_source.push_str(&format!(r#"
            {{
                    {} const * restrict src = {};
                    {} * restrict dst = {};
                    FUSE( {} ( src, dst ) );
            }}"#,
                            src_c_type, src_buf,
                            dst_c_type, dst_buf,
                            morph_symbol
                        ));
                    } else {
                        eprintln!("failed to add instantiation of item morphism");
                    }
                }

                Ok(LdmcPrimMorph {
                    symbol, type_args: Vec::new(), ty,
                    c_source
                })
            }
            MorphismInstance2::MapSeq { ψ, seq_repr, item_morph } => {
                if let Ok(item_morph_inst) = self.add_instantiation(dict, item_morph.as_ref().clone()) {
                    if let Some(seq_repr) = seq_repr {
                        dict.add_varname("LengthType".into());
                        if let Ok(γ) = laddertypes::unification_sugared::unify(
                            &dict.parse("<LengthPrefix LengthType>").expect("parse type template").sugar(dict),
                            seq_repr.as_ref()
                        ) {
                            let length_type = γ.get(&dict.get_typeid(&"LengthType".into()).expect("")).expect("cant get LengthType");
                            let ty = morph_inst.get_type().strip_halo();
                            let symbol = encode_morph_type_to_symbol(dict, &morph_inst.get_haloless_type());
                            let item_morph_symbol = item_morph_inst.instantiated_symbol_name(dict, &HashMap::new());

                            let length_c_type = get_c_repr_type(dict, length_type.clone(), true).expect("cant c-repr type for array length");
                            let src_item_c_type = get_c_repr_type(dict, item_morph.get_haloless_type().src_type, true).expect("cant c-repr type for src item");
                            let dst_item_c_type = get_c_repr_type(dict, item_morph.get_haloless_type().dst_type, true).expect("cant c-repr type for dst item");

                            // todo: self.add_active_length_prefix_map()

                            let map_fn = format!("length_prefix_{}_array_map_{}_to_{}",
                                length_c_type, src_item_c_type, dst_item_c_type
                            );
                            let c_source = format!(r#"
                        return {} ( {}, src, dst );"#,
                                map_fn,
                                item_morph_symbol,
                            );

                            Ok(LdmcPrimMorph{
                                symbol, type_args: Vec::new(), ty,
                                c_source
                            })
                        } else {
                            eprintln!("Error: Unknown Seq- Representation!!");
                            Err(())
                        }
                    } else {
                        eprintln!("Error: missing Seq- Representation!!");
                        Err(())
                    }
                } else {
                    eprintln!("failed to add item-morph instantiation");
                    Err(())
                }
            },
            MorphismInstance2::MapStruct { ψ, struct_repr, member_morph } => {
                let ty = morph_inst.get_type().strip_halo();
                let symbol =encode_morph_type_to_symbol(dict, &morph_inst.get_haloless_type());

                let mut c_source = String::new();

                for (name, morph) in member_morph.iter() {
                    if let Ok(inst) = self.add_instantiation(dict, morph.clone()) {
                        let name = name.replace("-", "_").replace(".","_dot_");
                        c_source.push_str(
                            &format!("
                                FUSE( {} ( &src->{}, &dst->{} ) );
                            ",
                            inst.symbol,
                            name, name
                        ));
                    } else {
                        eprintln!("failed to create morphism instantiation for struct member");
                        return Err(());
                    }
                }

                Ok(LdmcPrimMorph{
                    symbol, type_args: Vec::new(), ty,
                    c_source
                })
            }
            MorphismInstance2::MapEnum { ψ, enum_repr, variant_morph } => {
                todo!();
            }
        }
    }
}