use {
    laddertypes::{TypeDict, TypeTerm, parser::*, unparser::*, morphism::{Morphism, MorphismInstance}},
    crate::morphism::{LdmcPrimCMorphism, LdmcMorphism}
};

/*
*/
pub fn get_c_repr_kind(kind: String) -> Option<String> {
    match kind.as_str() {
        "ℤ" => Some("uint64_t".into()),
        _ => None
    }
}

/* for a given ladder type `t`, get the corresponding C type
 */
pub fn get_c_repr_type(dict: &mut impl TypeDict, t: laddertypes::TypeTerm, skip_pointer: bool) -> Option<String> {
    let lnf = t.normalize().decurry().get_lnf_vec();

    match lnf.last() {
        Some(t) => {
            if t == &dict.parse("Byte").expect("parse")
            || t == &dict.parse("x86.UInt8").expect("parse")
            || t == &dict.parse("<StaticLength 8 Bit>").expect("parse")
            {
                Some("uint8_t".into())
            } else if t == &dict.parse("x86.UInt16").expect("parse") {
                Some("uint16_t".into())
            } else if t == &dict.parse("x86.UInt32").expect("parse") {
                Some("uint32_t".into())
            } else if t == &dict.parse("x86.UInt64").expect("parse") {
                Some("uint64_t".into())
            } else if t == &dict.parse("native.Float").expect("parse") {
                Some("float".into())
            } else if t == &dict.parse("native.Double").expect("parse") {
                Some("double".into())
            } else {
                match t {
                    laddertypes::TypeTerm::App(args) => {
                        if args[0] == laddertypes::TypeTerm::TypeID(dict.get_typeid(&"LengthPrefix".into()).unwrap())
                        {
                            let _length_type = args[1].clone();
                            let item_c_type : String = get_c_repr_type(dict, args[2].clone(), false)?;
                            match item_c_type.as_str() {
                                "uint8_t" => Some(format!("struct LengthPrefixUInt8Array")),
                                "uint16_t" => Some(format!("struct LengthPrefixUInt16Array")),
                                "uint32_t" => Some(format!("struct LengthPrefixUInt32Array")),
                                "uint64_t" => Some(format!("struct LengthPrefixUInt64Array")),
                                _ => None
                            }
                        }
                        else if args[0] == laddertypes::TypeTerm::TypeID(dict.get_typeid(&"ValueTerminated".into()).unwrap())
                        {
                            let _delim_val = args[1].clone();
                            let c_type = get_c_repr_type(dict, args[2].clone(), false)?;
                            if skip_pointer {
                                Some(c_type)
                            } else {
                                Some(format!("{} *", c_type))
                            }
                        }
                        else if args[0] == laddertypes::TypeTerm::TypeID(dict.get_typeid(&"MsbCont".into()).unwrap())
                        {
                            let c_type = get_c_repr_type(dict, args[1].clone(), false)?;
                            if skip_pointer {
                                Some(c_type)
                            } else {
                                Some(format!("{} *", c_type))
                            }
                        }
                        else {
                            None
                        }
                    }

                    _ => None
                }
            }
        }
        None => None
    }
}


pub fn encode_type_to_symbol(dict: &mut impl TypeDict, t: &laddertypes::TypeTerm)-> String {
    match t {
        laddertypes::TypeTerm::Char(c) => {
            match c {
                '.' => format!("_dot_"),
                _ =>
                format!("{}", (*c as u64))
            }
        },
        laddertypes::TypeTerm::Num(n) => {
            format!("{}", n)
        }
        laddertypes::TypeTerm::TypeID(ty_id) => {
            if let Some(name) = dict.get_typename(ty_id) {
                format!("{}", name.replace(".", "_dot_"))
            } else {
                format!("")
            }
        }
        laddertypes::TypeTerm::Ladder(rs) |
        laddertypes::TypeTerm::App(rs) => {
            let mut s = String::new();
            for r in rs {
                s.push('_');
                s.push_str(&encode_type_to_symbol(dict, r));
            }
            s
        }
    }
}

pub fn encode_type_to_value(dict: &mut impl TypeDict, t: &laddertypes::TypeTerm) -> String {
    dict.unparse(t)
}


pub fn generate_main(type_dict: &mut impl TypeDict, path: Vec<MorphismInstance<LdmcMorphism>>, src_type: TypeTerm, dst_type: TypeTerm) {
    let mut i = 0;

    /* todo: collect include files from morphism base */
    println!(r#"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
"#);

    let mut existing_instantiations = Vec::new();
    for morph_inst in path.iter() {
        match &morph_inst.m {
            LdmcMorphism::Primitive( item_morph ) => {
                let name = item_morph.instantiated_symbol_name(type_dict, &morph_inst.σ);
                if ! existing_instantiations.contains(&name) {
                    if let Some(s) = item_morph.generate_instantiation(type_dict, &morph_inst.σ) {
                        println!("{}", s);
                    } else {
                        eprintln!("couldnt generate instance {}", name);
                    }
                    existing_instantiations.push( name );
                }
            }
            LdmcMorphism::LengthPrefixMap { length_prefix_type, item_morph } => {
                let name = item_morph.instantiated_symbol_name(type_dict, &morph_inst.σ);
                if ! existing_instantiations.contains(&name) {
                    if let Some(s) = item_morph.generate_instantiation(type_dict, &morph_inst.σ) {
                        println!("{}", s);
                    } else {
                        eprintln!("couldnt generate instance {}", name);
                    }

                    existing_instantiations.push( name );
                }
            }
            _ => {}
        }
    }

    println!(r#"
int main() {{
uint8_t bufA[128];
uint8_t bufB[128];

memset(bufA, 0, sizeof(bufA));
memset(bufB, 0, sizeof(bufB));

char in_str[] = "read :: {} \n";
char out_str[]= "write:: {} \n";
write(2, in_str, strlen(in_str));
write(2, out_str, strlen(out_str));

int l = read(0, bufA, sizeof(bufA));
fprintf(stderr, "read  %d bytes\n", l);

        "#,
        type_dict.unparse(&src_type).replace("\\", "\\\\"),
        type_dict.unparse(&dst_type).replace("\\", "\\\\") );

    for morph_inst in path.iter() {
        println!(r#"
/* morph to {}

...with
morph {}
---> {},
subst σ = {:?},
halo  Ψ = {}
*/"#,
            type_dict.unparse(&morph_inst.get_type().dst_type.param_normalize().decurry()),
            type_dict.unparse(&morph_inst.m.get_type().src_type),
            type_dict.unparse(&morph_inst.m.get_type().dst_type),
            morph_inst.σ,
            type_dict.unparse(&morph_inst.halo),
        );
        morph_inst.m.generate_call(type_dict, &morph_inst.σ, i);
        i += 1;
    }

    let out_buf = if i%2==0 { "bufA" } else { "bufB" };

    let is_string = false;
    if let Ok((halo, σ)) = laddertypes::subtype_unify(
        &dst_type,
        &type_dict.parse("<Seq~<ValueTerminated 0> x86.UInt8>").unwrap()
    ) {
        println!(r#"
printf("%s\n", {});"#, out_buf);
    } else if let Ok((halo, σ)) = laddertypes::subtype_unify(
        &dst_type,
        &type_dict.parse("<Seq~<LengthPrefix x86.UInt64> x86.UInt8>").unwrap()
    ) {
        println!(r#"
        /* write output
         */
        {{
            struct LengthPrefixUInt8Array * buf = (void*){};
            write(1, {}, sizeof(uint64_t) + buf->len);
        }}"#, out_buf, out_buf);
    } else {
        println!(r#"
write(1, {}, {});"#,
            out_buf,
            "sizeof(bufA)"
        );
    }


    println!(r#"
return 0;
}}
    "#);

    eprintln!("Success: generated C code");
}

impl LdmcPrimCMorphism {
    pub fn instantiated_symbol_name(&self, dict: &mut impl TypeDict,
        σ: &std::collections::HashMap<laddertypes::TypeID, laddertypes::TypeTerm>) -> 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 {} ({})", k, var_id);
                }
            }
        }
        s
    }

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

    pub fn generate_instantiation(&self, dict: &mut impl TypeDict, σ: &std::collections::HashMap<laddertypes::TypeID, laddertypes::TypeTerm>) -> Option<String> {
        let mut s = String::new();

        s.push_str(&format!(r#"
int {} ( {} const * restrict src, {} * restrict dst ) {{
"#,
            self.instantiated_symbol_name(dict, &σ),
            get_c_repr_type(dict, self.src_type.clone().apply_substitution(&|k| σ.get(k).cloned()).clone(), true).expect("cant get c-repr-type"),
            get_c_repr_type(dict, self.dst_type.clone().apply_substitution(&|k| σ.get(k).cloned()).clone(), true).expect("cant get c-repr-type"),
        ));
        for (ty_id, kind) in self.type_args.iter() {
            if let Some(val) = σ.get(ty_id) {
                s.push_str(&format!("    #define {} {}\n", dict.get_typename(&ty_id).unwrap(), encode_type_to_value(dict, val)));
            }
        }

        s.push_str(&self.c_source);

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

        s.push_str(&format!(r#"
}}
"#));
        Some(s)
    }
}

impl LdmcMorphism {
    pub fn generate_call(&self, dict: &mut impl TypeDict, σ: &std::collections::HashMap<laddertypes::TypeID, laddertypes::TypeTerm>, i: u64) {
        match self {
            LdmcMorphism::Primitive(prim_morph) => {
                let src_c_type = get_c_repr_type(dict, prim_morph.src_type.clone().apply_substitution(&|k| σ.get(k).cloned()).clone(), true).expect("cant get c-repr type for src type");
                let dst_c_type = get_c_repr_type(dict, prim_morph.dst_type.clone().apply_substitution(&|k| σ.get(k).cloned()).clone(), true).expect("cant get c-repr type for dst type");

                let src_buf = if i%2 == 0 { "bufA" } else { "bufB" };
                let dst_buf = if i%2 == 0 { "bufB" } else { "bufA" };

                println!(r#"
    {}
            {} const * restrict src = (void*) {};
            {} * restrict dst = (void*) {};
            {} ( src, dst );
    {}"#,
                    '{',
                    src_c_type, src_buf,
                    dst_c_type, dst_buf,
                    prim_morph.instantiated_symbol_name(dict, σ),
                    '}');
            }
            LdmcMorphism::LengthPrefixMap { length_prefix_type, item_morph } => {
                let src_c_type = get_c_repr_type(dict, self.get_type().src_type.clone().apply_substitution(&|k| σ.get(k).cloned()).clone(), true).expect("cant get c-repr type for src type");
                let dst_c_type = get_c_repr_type(dict, self.get_type().dst_type.clone().apply_substitution(&|k| σ.get(k).cloned()).clone(), true).expect("cant get c-repr type for dst type");

                let map_fn = match (src_c_type.as_str(), dst_c_type.as_str()) {
                    ("struct LengthPrefixUInt64Array", "struct LengthPrefixUInt64Array") => "length_prefix_array_map_64_to_64",
                    ("struct LengthPrefixUInt8Array", "struct LengthPrefixUInt64Array") => "length_prefix_array_map_8_to_64",
                    ("struct LengthPrefixUInt64Array", "struct LengthPrefixUInt8Array") => "length_prefix_array_map_64_to_8",
                    ("struct LengthPrefixUInt8Array", "struct LengthPrefixUInt8Array") => "length_prefix_array_map_8_to_8",
                    _ => {
                        "{{ ERROR: no map function implemented }}"
                    }
                };

                let src_buf = if i%2 == 0 { "bufA" } else { "bufB" };
                let dst_buf = if i%2 == 0 { "bufB" } else { "bufA" };
                println!(r#"
    {}
            {} const * restrict src = (void*) {};
            {} * restrict dst = (void*) {};
            {} ( {}, src, dst );
    {}"#,
            '{',
                    src_c_type, src_buf,
                    dst_c_type, dst_buf,
                    map_fn,
                    item_morph.instantiated_symbol_name(dict, σ),
                    '}');
            }
            LdmcMorphism::ValueDelimMap { delim, item_morph } => {
                let src_c_type = get_c_repr_type(dict, item_morph.src_type.clone(), false).expect("cant get c-repr type for src type");
                let dst_c_type = get_c_repr_type(dict, item_morph.dst_type.clone(), false).expect("cant get c-repr type for dst type");

                let src_buf = if i%2 == 0 { "bufA" } else { "bufB" };
                let dst_buf = if i%2 == 0 { "bufB" } else { "bufA" };
                println!(r#"
    {}
            {} const * restrict src = (void*) {};
            {} * restrict dst = (void*) {};
            value_delim_array_map_8_to_64( {}, src, dst );
    {}"#,
            '{',
                    src_c_type, src_buf,
                    dst_c_type, dst_buf,
                    item_morph.instantiated_symbol_name(dict, σ),
                    '}');
            }
        }
    }
}