use crate::*;
use crate::gen::schema::*;
use crate::syntax::block::Item;
use crate::syntax::block::constructors::*;

use super::context::{ModuleContextMode, ModuleContext, FunctionContext};
use super::names;
use super::types::*;

#[derive(Debug)]
pub struct ParserPlugin;

impl compiler::Plugin for ParserPlugin {
    fn generate(&self, module_ctxt: &mut ModuleContext, definition_name: &str, definition: &Definition) {
        if module_ctxt.mode.is_some() {
            gen_definition_parser(module_ctxt, definition_name, definition)
        }
    }
}

pub fn gen_definition_parser(m: &mut ModuleContext, n: &str, d: &Definition) {
    m.define_function(
        n,
        |mut ctxt| {
            let mut body = vec![];

            match d {
                Definition::Or { pattern_0, pattern_1, pattern_n } => {
                    let mut ps = vec![&**pattern_0, &**pattern_1];
                    ps.extend(pattern_n);
                    for NamedAlternative { variant_label: name, pattern: pat } in ps {
                        let fname = seq![ctxt.m.target_prefix(), "parse_",
                                         names::render_fieldname(n), "_", names::render_fieldname(name)];
                        let ctorname = item(name![names::render_constructor(n), names::render_constructor(name)]);
                        ctxt.m.define_function(
                            &(n.to_owned() + "::" + name),
                            |mut ctxt| {
                                let mut body = Vec::new();
                                let dest = pattern_parser(&mut ctxt, pat, "value", None, &mut body);
                                let dest = dest.as_ref().map(String::as_str);
                                construct(&ctxt, ctorname, false, &pattern_type(pat), dest, &mut body);
                                item(seq!["fn ", fname.clone(), "(value: &", ctxt.m.target(), ") -> ",
                                          "std::result::Result<", names::render_constructor(n), ", _support::ParseError> ",
                                          block(body)])
                            });
                        body.push(item(seq!["if let Ok(r) = ", fname, "(value) { return Ok(r); }"]));
                    }
                    body.push(item(seq![ctxt.err_code()]));
                }
                Definition::And { pattern_0, pattern_1, pattern_n } => {
                    let mut ps = vec![&**pattern_0, &**pattern_1];
                    ps.extend(pattern_n);
                    for e in &ps {
                        named_pattern_parser(&mut ctxt, e, "value", None, &mut body);
                    }
                    construct(&ctxt, item(names::render_constructor(n)), true, &record_type(&ps), None, &mut body);
                }
                Definition::Pattern(p) => {
                    let dest = pattern_parser(&mut ctxt, p, "value", None, &mut body);
                    let dest = dest.as_ref().map(String::as_str);
                    construct(&ctxt, item(names::render_constructor(n)), true, &pattern_type(p), dest, &mut body);
                }
            }

            item(seq!["impl std::convert::TryFrom", anglebrackets![seq!["&", ctxt.m.target()]], " for ",
                      names::render_constructor(n), " ", block![
                          seq!["type Error = _support::ParseError;"],
                          seq!["fn try_from(value: &", ctxt.m.target(), ") -> ",
                               "std::result::Result<Self, _support::ParseError> ",
                               block(body)]]])
        });
}

fn construct(
    ctxt: &FunctionContext,
    ctorname: Item,
    is_struct: bool,
    ty: &TSimple,
    dest: Option<&str>,
    body: &mut Vec<Item>,
) {
    match ty {
        TSimple::Field(TField::Unit) =>
            body.push(item(seq!["Ok(", ctorname, ")"])),
        TSimple::Field(fieldty) =>
            body.push(item(seq!["Ok(", ctorname, parens![store_wrap(is_struct, fieldty, dest.unwrap())], ")"])),
        TSimple::Record(_) =>
            body.push(item(seq!["Ok(", ctorname, " ", braces(
                ctxt.captures.iter().map(
                    |c| item(seq![c.field_name.clone(), ": ", store_wrap(is_struct, &c.ty, &c.source_expr)])).collect()),
                                ")"])),
    }
}

fn store_wrap(is_struct: bool, ty: &TField, expr: &str) -> String {
    match ty {
        TField::Unit
            | TField::Array(_)
            | TField::Set(_)
            | TField::Map(_, _) => expr.to_owned(),
        TField::Ref(_) =>
            if is_struct {
                expr.to_owned()
            } else {
                format!("std::boxed::Box::new({})", expr)
            },
        TField::Base(_) => format!("{}.clone()", expr),
    }
}

fn simple_pattern_parser(
    ctxt: &mut FunctionContext,
    p: &SimplePattern,
    src: &str,
    sequence_base: Option<usize>,
    body: &mut Vec<Item>,
) -> String {
    let dest = ctxt.gentempname();
    match p {
        SimplePattern::Any => {
            match ctxt.m.mode() {
                ModuleContextMode::TargetIOValue =>
                    ctxt.define_atom(body, &dest, item(seq!["_support::decode_embedded", parens![src.to_owned()], "?"])),
                ModuleContextMode::TargetAny =>
                    ctxt.define_atom(body, &dest, item(src.to_owned())),
            }
            dest
        },
        SimplePattern::Atom { atom_kind: k } => {
            let converter = match &**k {
                AtomKind::Boolean => "to_boolean",
                AtomKind::Float => "to_float",
                AtomKind::Double => "to_double",
                AtomKind::SignedInteger => "to_signedinteger",
                AtomKind::String => "to_string",
                AtomKind::ByteString => "to_bytestring",
                AtomKind::Symbol => "to_symbol",
            };
            ctxt.define_atom(body, &dest, item(seq![src.to_owned(), ".value().", converter, "()?"]));
            dest
        },
        SimplePattern::Embedded { .. } => {
            match ctxt.m.mode() {
                ModuleContextMode::TargetIOValue =>
                    ctxt.define_atom(body, &dest, item(seq![
                        "std::sync::Arc::new(_Dom::try_from",
                        parens![seq![src.to_owned(), ".value().to_embedded()?"]],
                        "?)"])),
                ModuleContextMode::TargetAny =>
                    ctxt.define_atom(body, &dest, item(seq![
                        parens![seq![src.to_owned(), ".value().to_embedded()?"]]])),
            }
            dest
        },
        SimplePattern::Lit { value } => {
            body.push(item(seq!["if ", src.to_owned(), " != ", ctxt.m.define_literal(value),
                                " { return ", ctxt.err_code(), "; }"]));
            ctxt.define_atom(body, &dest, item("()"));
            dest
        },
        SimplePattern::Seqof { pattern } => {
            let (src, n) = sequenceify(ctxt, src, sequence_base, body);
            let tmp = ctxt.gentempname();
            let mut inner = Vec::new();
            let item_dest = simple_pattern_parser(ctxt, pattern, &tmp, None, &mut inner);
            inner.push(item(seq![dest.to_owned(), ".push(",
                                 store_wrap(true, &field_type(pattern), &item_dest), ");"]));
            ctxt.declare_compound(body, &dest, item("std::vec::Vec::new()"));
            body.push(item(seq!["for ", tmp.to_owned(),
                                " in &", src.to_owned(), brackets![seq![n.to_string() , ".."]],
                                " ", block(inner)]));
            dest
        },
        SimplePattern::Setof { pattern } => {
            let tmp = ctxt.gentempname();
            let mut inner = Vec::new();
            let item_dest = simple_pattern_parser(ctxt, pattern, &tmp, None, &mut inner);
            inner.push(item(seq![dest.to_owned(), ".insert(",
                                 store_wrap(true, &field_type(pattern), &item_dest), ");"]));
            ctxt.declare_compound(body, &dest, item("preserves::value::Set::new()"));
            body.push(item(seq!["for ", tmp.to_owned(),
                                " in ", src.to_owned(), ".value().to_set()?",
                                " ", block(inner)]));
            dest
        },
        SimplePattern::Dictof { key, value } => {
            let tmp_key = ctxt.gentempname();
            let tmp_value = ctxt.gentempname();
            let mut inner = Vec::new();
            let key_dest = simple_pattern_parser(ctxt, key, &tmp_key, None, &mut inner);
            let value_dest = simple_pattern_parser(ctxt, value, &tmp_value, None, &mut inner);
            inner.push(item(seq![
                dest.to_owned(), ".insert(",
                store_wrap(true, &field_type(key), &key_dest), ", ",
                store_wrap(true, &field_type(value), &value_dest), ");"]));
            ctxt.declare_compound(body, &dest, item("preserves::value::Map::new()"));
            body.push(item(seq!["for (", tmp_key.to_owned(), ", ", tmp_value.to_owned(), ")",
                                " in ", src.to_owned(), ".value().to_dictionary()?",
                                " ", block(inner)]));
            dest
        },
        SimplePattern::Ref(r) => {
            let tf = name![ctxt.m.render_ref(&**r), "try_from"];
            ctxt.define_atom(body,
                             &dest,
                             item(seq![tf, parens![seq![src.to_owned()]], "?"]));
            dest
        },
    }
}

fn sequenceify(
    ctxt: &mut FunctionContext,
    src: &str,
    sequence_base: Option<usize>,
    body: &mut Vec<Item>,
) -> (String, usize) {
    match sequence_base {
        Some(n) => (src.to_owned(), n),
        None => {
            let tmp = ctxt.gentempname();
            ctxt.define_atom(body, &tmp, item(seq![src.to_owned(), ".value().to_sequence()?"]));
            (tmp, 0)
        }
    }
}

fn fixed_sequence_parser(
    ctxt: &mut FunctionContext,
    base: usize,
    ps: &[NamedPattern],
    src: &str,
    body: &mut Vec<Item>,
) {
    let mut i = base;
    let required_count = ps.len();
    if required_count > 0 {
        body.push(item(seq!["if ", src.to_owned(), ".len()",
                            if base > 0 { seq![" - ", base.to_string()] } else { seq![] },
                            " < ", required_count.to_string(),
                            " { return ", ctxt.err_code(), "; }"]));
    }
    for p in ps {
        named_pattern_parser(ctxt, p, &format!("(&{}[{}])", src, i), None, body);
        i += 1;
    }
}

fn named_pattern_parser(
    ctxt: &mut FunctionContext,
    p: &NamedPattern,
    src: &str,
    sequence_base: Option<usize>,
    body: &mut Vec<Item>,
) {
    match p {
        NamedPattern::Anonymous(p) => {
            pattern_parser(ctxt, p, src, sequence_base, body);
        },
        NamedPattern::Named(b) => {
            let Binding { name, pattern} = &**b;
            let dest = simple_pattern_parser(ctxt, pattern, src, sequence_base, body);
            let capture_ty = field_type(pattern);
            ctxt.capture(names::render_fieldname(name), capture_ty, dest);
        }
    }
}

fn pattern_parser(
    ctxt: &mut FunctionContext,
    p: &Pattern,
    src: &str,
    sequence_base: Option<usize>,
    body: &mut Vec<Item>,
) -> Option<String> {
    match p {
        Pattern::SimplePattern(s) =>
            Some(simple_pattern_parser(ctxt, s, src, sequence_base, body)),
        Pattern::CompoundPattern(c) => {
            match &**c {
                CompoundPattern::Rec { label, fields } => {
                    let rtmp = ctxt.gentempname();
                    ctxt.define_atom(body, &rtmp, item(seq![src.to_owned(), ".value().to_record(None)?"]));
                    named_pattern_parser(ctxt, &**label, &format!("{}.label()", rtmp), None, body);
                    named_pattern_parser(ctxt, &**fields, &format!("{}.fields()", rtmp), Some(0), body);
                },
                CompoundPattern::Tuple { patterns } => {
                    let (src, n) = sequenceify(ctxt, src, sequence_base, body);
                    fixed_sequence_parser(ctxt, n, patterns, &src, body);
                },
                CompoundPattern::TuplePrefix { fixed, variable } => {
                    let (src, n) = sequenceify(ctxt, src, sequence_base, body);
                    fixed_sequence_parser(ctxt, n, fixed, &src, body);
                    named_pattern_parser(ctxt, &promote(variable), &src, Some(n + fixed.len()), body);
                },
                CompoundPattern::Dict { entries } => {
                    let dtmp = ctxt.gentempname();
                    ctxt.define_atom(body, &dtmp, item(seq![src.to_owned(), ".value().to_dictionary()?"]));
                    for (key_lit, value_pat) in entries.0.iter() {
                        let vtmp = ctxt.gentempname();
                        let init_expr = item(seq![
                            dtmp.to_owned(), ".get", parens![ctxt.m.define_literal(key_lit)],
                            ".ok_or_else(|| ", ctxt.conformance_err_code(), ")?"]);
                        ctxt.define_atom(body, &vtmp, init_expr);
                        named_pattern_parser(ctxt, &promote(value_pat), &vtmp, None, body);
                    }
                }
            }
            None
        },
    }
}
