use {
    ariadne::{Color, Label, Report, ReportKind, Source},
    chumsky::{
        prelude::*, text::*
    },
    laddertypes::{
        dict::TypeDict, parser::ParseLadderType, subtype_unify, unparser::UnparseLadderType, BimapTypeDict, Morphism, MorphismType
    },
    std::{any::Any, sync::{Arc, RwLock}},
    tiny_ansi::TinyAnsi
};

/*
*/
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 {
                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
    }
}

#[derive(Clone, Debug)]
struct LdmcPrimCMorphism {
    symbol: String,
    type_args: Vec<(laddertypes::TypeID, String)>,
    src_type: laddertypes::TypeTerm,
    dst_type: laddertypes::TypeTerm,
    c_source: String
}

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
        }
    }
}

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

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)
    }
}

#[derive(Clone)]
enum LdmcMorphism {
    Primitive( LdmcPrimCMorphism ),
    LengthPrefixMap{
        length_prefix_type: laddertypes::TypeTerm,
        item_morph: Box<LdmcPrimCMorphism>
    },
    ValueDelimMap{
        delim: u64,
        item_morph: Box<LdmcPrimCMorphism>
    }
}

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(), true).expect("cant get c-repr type for src type");
                let dst_c_type = get_c_repr_type(dict, prim_morph.dst_type.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, true).expect("cant get c-repr type for src type");
                let dst_c_type = get_c_repr_type(dict, self.get_type().dst_type, 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, σ),
                    '}');
            }
        }
    }
}

impl Morphism for LdmcMorphism {
    fn weight(&self) -> u64 {
        1
    }

    fn get_type(&self) -> laddertypes::MorphismType {
        match self {
            LdmcMorphism::Primitive(prim_morph) =>
                laddertypes::MorphismType {
                    src_type: prim_morph.src_type.clone().normalize(),
                    dst_type: prim_morph.dst_type.clone().normalize()
                },
            LdmcMorphism::LengthPrefixMap{ length_prefix_type, item_morph } => {
                laddertypes::MorphismType {
                    src_type: laddertypes::TypeTerm::App(vec![ length_prefix_type.clone(), item_morph.src_type.clone() ]),
                    dst_type: laddertypes::TypeTerm::App(vec![ length_prefix_type.clone(), item_morph.dst_type.clone() ]),
                }
            },
            LdmcMorphism::ValueDelimMap{ delim, item_morph } => {
                let value_delim_type = laddertypes::TypeTerm::App(vec![]);
                laddertypes::MorphismType {
                    src_type: laddertypes::TypeTerm::App(vec![ value_delim_type.clone(), item_morph.src_type.clone() ]),
                    dst_type: laddertypes::TypeTerm::App(vec![ value_delim_type.clone(), item_morph.dst_type.clone() ]),
                }
            }
        }.normalize()
    }

    fn map_morphism(&self, seq_type: laddertypes::TypeTerm) -> Option< Self > {
        match self {
            LdmcMorphism::Primitive(prim) => {
                let item_morph = Box::new(prim.clone());
/*
                if seq_type == self.length_prefix_type {
                */
                    Some(LdmcMorphism::LengthPrefixMap{
                        length_prefix_type: seq_type,
                        item_morph,
                    })
                    /*
                } else if seq_type == self.value_delim_type {
                    Some(LdmcMorphism::ValueDelimMap { delim, item_morph })
                } else {
                    None
                }
                */
            }
            _ => None
        }
    }
}

/* morphism-base text format:
 *     NAME '(' [TYPE-ARG-NAME ':' KIND]  ')'
 *           SRC-TYPE
 *     '-->' DST-TYPE
 *     ```
 *          TEMPLATE-CODE
 *     ```
 */
fn parser(
    type_dict: Arc<RwLock< BimapTypeDict >>
) -> impl Parser<char, Vec<LdmcPrimCMorphism>, Error = Simple<char>> {

    // morph name
    ident().padded()

    // type args
    .then(
        ident().padded()
        .then_ignore(just(":").padded())
        .then(none_of(",)").repeated().padded())
        .separated_by(just(",").padded())
        .delimited_by(just("("), just(")"))
    )

    // newline
    .then_ignore(just('\n'))

    // src type
    .then(take_until(just("-->").ignored()))
    // dst_type
    .then(take_until(just("```")))

    // c sourcecode template
    .then(take_until(just("```")))

    .map(
        move |((((symbol, type_args), (src_type, _)), (dst_type, _)), (c_source, _))| {
            let c_source = c_source.iter().collect();
            let mut type_dict = type_dict.write().unwrap();
            let type_args : Vec<_> = type_args.into_iter().map(|(v,k)| (v,k.into_iter().collect())).collect();
            let mut ty_args = Vec::new();
            for (var, kind) in type_args.into_iter() {
                let var_id = type_dict.add_varname(var.clone());
                ty_args.push((var_id, kind));
            }

            let mut src_type = type_dict.parse(&src_type.iter().collect::<String>()).expect("couldnt parse src type");
            let mut dst_type = type_dict.parse(&dst_type.iter().collect::<String>()).expect("couldnt parse dst type");

            LdmcPrimCMorphism {
                symbol,
                type_args: ty_args,
                src_type,
                dst_type,
                c_source
            }
        })
        .separated_by(text::newline())
}

fn main() {
    let mut type_dict = Arc::new(RwLock::new(BimapTypeDict::new()));
    let mut morphism_base = laddertypes::MorphismBase::<LdmcMorphism>::new(vec![
        //type_dict.parse("Seq~MsbCont").expect(""),
        //type_dict.parse("Seq~<ValueTerminated '\\0'>").expect(""),
        type_dict.parse("Seq~<LengthPrefix x86.UInt64>").expect("")
    ]);

    let mut args = std::env::args().skip(1);
    let src_type_arg = args.next().expect("src type expected");
    let dst_type_arg = args.next().expect("dst type expected");

    for mb_path in args {
        let src = std::fs::read_to_string(mb_path.clone()).expect("read");
        let result = parser(type_dict.clone()).parse(src.clone());
        match result {
            Ok(morphisms) => {
                eprintln!("[{}] parse ok.", mb_path.bright_yellow());
                for m in morphisms {
                    morphism_base.add_morphism(LdmcMorphism::Primitive(m));
                }
            }
            Err(errs) => {
                errs.into_iter().for_each(|e| {
                        Report::build(ReportKind::Error, (), e.span().start)
                            .with_message(e.to_string())
                            .with_label(
                                Label::new(e.span())
                                    .with_message(e)
                                    .with_color(Color::Red),
                            )
                            .finish()
                            .print(Source::from(&src))
                            .unwrap()
                        });
            }
        }
    }


    let src_type = type_dict.parse( src_type_arg.as_str() ).expect("");
    let dst_type = type_dict.parse( dst_type_arg.as_str() ).expect("");

    let path = morphism_base.find_morphism_path(MorphismType {
        src_type: src_type.clone(),
        dst_type: dst_type.clone(),
    });

    match path {
        Some(path) => {
            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>
#include <array/length-prefix.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(&mut type_dict, &morph_inst.σ);
                        if ! existing_instantiations.contains(&name) {
                            if let Some(s) = item_morph.generate_instantiation(&mut 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(&mut type_dict, &morph_inst.σ);
                        if ! existing_instantiations.contains(&name) {
                            if let Some(s) = item_morph.generate_instantiation(&mut type_dict, &morph_inst.σ) {
                                println!("{}", s);
                            } else {
                                eprintln!("couldnt generate instance {}", name);
                            }

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

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

    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, 1024);
    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(&mut 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,
                    1024
                );
            }


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

            eprintln!("Success: generated C code");
        }
        None => {
            eprintln!("Error: could not find morphism path");
            std::process::exit(-1);
        }
    }
}