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

use preserves::value::AtomClass;
use preserves::value::CompoundClass;
use preserves::value::IOValue;
use preserves::value::NestedValue;
use preserves::value::ValueClass;

use super::context::FunctionContext;
use super::context::ModuleContext;
use super::context::ModuleContextMode;
use super::context::RefRenderStyle;
use super::names;
use super::types::*;

#[derive(Debug)]
pub struct ReaderPlugin;

impl compiler::Plugin for ReaderPlugin {
    fn generate_definition(&self, module_ctxt: &mut ModuleContext, definition_name: &str, definition: &Definition) {
        if let ModuleContextMode::TargetGeneric = module_ctxt.mode {
            gen_definition_reader(module_ctxt, definition_name, definition)
        }
    }
}

#[derive(Clone)]
struct BoundaryTracker {
    tracker_name: String,
    item_expr: &'static str,
}

impl BoundaryTracker {
    fn unwrap(
        ctxt: &mut FunctionContext,
        body: &mut Vec<Item>,
        open_expr: &'static str,
        e: Option<&BoundaryTracker>,
    ) -> Self {
        match e {
            None => Self::new(ctxt, body, open_expr, "_support::B::Item::SequenceValue"),
            Some(b) => b.clone(),
        }
    }

    fn new(
        ctxt: &mut FunctionContext,
        body: &mut Vec<Item>,
        open_expr: &'static str,
        item_expr: &'static str,
    ) -> Self {
        let tracker_name = ctxt.gentempname();
        body.push(item(open_expr));
        body.push(item(seq!["let mut ", tracker_name.clone(), " = _support::B::Type::default();"]));
        BoundaryTracker {
            tracker_name,
            item_expr,
        }
    }

    fn emit_boundary(&self, body: &mut Vec<Item>) {
        body.push(item(seq![self.tracker_name.clone(), ".shift(Some(", self.item_expr, "));"]));
        body.push(item(seq!["r.boundary(&", self.tracker_name.clone(), ")?;"]));
    }

    fn emit_loop(&self, body: &mut Vec<Item>, inner: Vec<Item>) {
        body.push(item(seq![
            "while !r.close_compound", parens![
                seq!["&mut ", self.tracker_name.clone()],
                seq!["&", self.item_expr]], "? ",
            codeblock(inner)]))
    }
}

pub fn gen_definition_reader(m: &mut ModuleContext, n: &str, d: &Definition) {
    let ty = definition_type(&m.module_path, Purpose::Codegen, n, d);

    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);
                    ctxt.define_atom(&mut body, "_mark", item("r.mark()?"));
                    for NamedAlternative { variant_label: name, pattern: pat } in ps {
                        let fname = seq!["read_", 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_reader(&mut ctxt, pat, 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(), anglebrackets![
                                        "'de",
                                        seq![ctxt.m.any_type(), ": preserves::value::NestedValue"],
                                        seq!["R: _support::Reader<'de, ", ctxt.m.any_type(), ">"]],
                                    "(r: &mut R) -> ",
                                    "std::result::Result<",
                                    names::render_constructor(n), ty.generic_arg(ctxt.m),
                                    ", _support::ParseError> ",
                                    codeblock(body)])
                            });
                        body.push(item(seq![
                            "match ", fname, "(r) { ",
                            "Err(e) if e.is_conformance_error() => r.restore(&_mark)?, ",
                            "result => return result }"]));
                    }
                    body.push(item(seq![ctxt.err_code()]));
                }
                Definition::And { pattern_0, pattern_1, pattern_n } => {
                    let mut ps = vec![&**pattern_0, &**pattern_1];
                    let mut need_restore = false;
                    ps.extend(pattern_n);
                    ctxt.define_atom(&mut body, "_mark", item("r.mark()?"));
                    for e in &ps {
                        if need_restore {
                            body.push(item("r.restore(&_mark)?;"));
                        } else {
                            need_restore = true;
                        }
                        named_pattern_reader(&mut ctxt, e, None, &mut body);
                    }
                    construct(&ctxt, item(names::render_constructor(n)), true, &record_type(&ps), None, &mut body);
                }
                Definition::Pattern(p) => {
                    let dest = pattern_reader(&mut ctxt, p, 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", anglebrackets![seq![ctxt.m.any_type(), ": preserves::value::NestedValue"]],
                " _support::Deserialize", anglebrackets![ctxt.m.any_type()], " for ",
                names::render_constructor(n), ty.generic_arg(ctxt.m), " ", codeblock![
                    seq!["fn deserialize",
                         anglebrackets!["'de",
                                        seq!["R: _support::Reader<'de, ", ctxt.m.any_type(), ">"]],
                         "(r: &mut R) -> ",
                         "std::result::Result<Self, _support::ParseError> ",
                         codeblock(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(_) | TField::Any | TField::Embedded => expr.to_owned(),
    }
}

fn group_by<T, K, Key>(mut items: Vec<T>, mut key: Key) -> Vec<(K, Vec<T>)>
where
    K: Ord + Clone,
    Key: FnMut(&T) -> K,
{
    let mut result = Vec::new();
    let mut current_key: Option<K> = None;
    let mut buf = Vec::new();
    items.sort_by(|a, b| key(a).cmp(&key(b)));
    for (k, v) in items.into_iter().map(|t| (key(&t), t)) {
        match current_key.cmp(&Some(k.clone())) {
            std::cmp::Ordering::Equal => (),
            std::cmp::Ordering::Less |
            std::cmp::Ordering::Greater => {
                if let Some(k) = current_key {
                    result.push((k, std::mem::take(&mut buf)));
                }
                current_key = Some(k);
            }
        }
        buf.push(v)
    }
    if let Some(k) = current_key {
        result.push((k.clone(), std::mem::take(&mut buf)));
    }
    result
}

type LiteralContinuation = Box<dyn FnOnce(&mut FunctionContext, &mut Vec<Item>) -> ()>;
type LiteralCases = Vec<(IOValue, LiteralContinuation)>;
type LiteralSeqCases = Vec<(Vec<IOValue>, LiteralContinuation)>;

fn read_expected_literal_seqs(
    ctxt: &mut FunctionContext,
    body: &mut Vec<Item>,
    possibilities: LiteralSeqCases,
) {
    let grouped = group_by(possibilities, |(vs, _f)| if vs.is_empty() {
        None
    } else {
        Some(vs[0].clone())
    });
    let mut cases = Vec::new();
    let mut nested: LiteralCases = Vec::new();
    for (head, group) in grouped.into_iter() {
        match head {
            None => {
                let mut inner = Vec::new();
                group.into_iter().next().unwrap().1(ctxt, &mut inner);
                cases.push(item(seq!["preserves::value::Token::End => ", codeblock(inner)]));
            },
            Some(h) => {
                let tails = group.into_iter().map(|(mut vs, f)| {
                    vs.remove(0);
                    (vs, f)
                }).collect();
                nested.push((h, Box::new(|ctxt: &mut FunctionContext, b: &'_ mut Vec<Item>| {
                    read_expected_literal_seqs(ctxt, b, tails)
                })));
            }
        }
    }
    cases.extend(read_expected_literals_cases(ctxt, nested));
    body.push(item(seq!["match r.next_token(true)? ", codeblock(cases)]));
}

fn read_expected_literals_cases(
    ctxt: &mut FunctionContext,
    possibilities: LiteralCases,
) -> Vec<Item> {
    let grouped = group_by(possibilities, |(v, _f)| v.value_class());
    let mut cases = grouped.into_iter().map(|(n, group)| {
        match n {
            ValueClass::Atomic(cls) => {
                let mut subcases = Vec::new();
                for p in group {
                    let mut inner = Vec::new();
                    p.1(ctxt, &mut inner);
                    subcases.push(item(seq![
                        format!("preserves::value::Value::{:?}(w)", cls),
                        match cls {
                            AtomClass::Boolean => match p.0.value().to_boolean().unwrap() {
                                true => " if *w".to_owned(),
                                false => " if !*w".to_owned(),
                            },
                            AtomClass::Float |
                            AtomClass::Double =>
                                format!(" if w.0 == {:?}", p.0),
                            AtomClass::SignedInteger =>
                                format!(" if *w == ({:?}).into()", p.0),
                            AtomClass::String =>
                                format!(" if w == {}", escape_string(p.0.value().to_string().unwrap())),
                            AtomClass::ByteString =>
                                format!(" if w == {}", escape_bytes(p.0.value().to_bytestring().unwrap())),
                            AtomClass::Symbol =>
                                format!(" if w == {}", escape_string(p.0.value().to_symbol().unwrap())),
                        },
                        " => ",
                        codeblock(inner)]));
                }
                subcases.push(item(seq!["_ => return ", ctxt.err_code(), "?,"]));
                item(seq!["preserves::value::Token::Atom(v) => match v.value() ", codeblock(subcases)])
            }
            ValueClass::Compound(CompoundClass::Record) => {
                let mut subcases = Vec::new();
                read_expected_literal_seqs(ctxt, &mut subcases, group.into_iter().map(|(v, f)| {
                    let r = v.value().to_record(None).unwrap();
                    (r.0.clone(), f)
                }).collect());
                item(seq![
                    "preserves::value::Token::Compound(preserves::value::CompoundClass::Record) => ",
                    codeblock(subcases)])
            }
            ValueClass::Compound(CompoundClass::Sequence) => {
                let mut subcases = Vec::new();
                read_expected_literal_seqs(ctxt, &mut subcases, group.into_iter().map(|(v, f)| {
                    let s = v.value().to_sequence().unwrap().clone();
                    (s, f)
                }).collect());
                item(seq![
                    "preserves::value::Token::Compound(preserves::value::CompoundClass::Sequence) => ",
                    codeblock(subcases)])
            }
            ValueClass::Compound(CompoundClass::Set) => {
                panic!("Sets in literal constants in Schema not yet supported");
            }
            ValueClass::Compound(CompoundClass::Dictionary) => {
                panic!("Dictionaries in literal constants in Schema not yet supported");
            }
            ValueClass::Embedded => {
                panic!("Embedded values in literal constants in Schema not yet supported");
            }
        }
    }).collect::<Vec<_>>();
    cases.push(item(seq!["_ => return ", ctxt.err_code(), "?,"]));
    cases
}

fn read_expected_literals(
    ctxt: &mut FunctionContext,
    body: &mut Vec<Item>,
    possibilities: LiteralCases,
) {
    let cases = read_expected_literals_cases(ctxt, possibilities);
    body.push(item(seq!["match r.next_token(true)? ", codeblock(cases)]));
}

fn simple_pattern_reader(
    ctxt: &mut FunctionContext,
    p: &SimplePattern,
    boundary_tracker: Option<&BoundaryTracker>,
    body: &mut Vec<Item>,
) -> String {
    let dest = ctxt.gentempname();
    match p {
        SimplePattern::Any => {
            ctxt.define_atom(body, &dest, item("r.demand_next(true)?"));
            dest
        },
        SimplePattern::Atom { atom_kind: k } => {
            let reader = match &**k {
                AtomKind::Boolean => "r.next_boolean()?",
                AtomKind::Float => "r.next_float()?",
                AtomKind::Double => "r.next_double()?",
                AtomKind::SignedInteger => "r.next_signedinteger()?",
                AtomKind::String => "r.next_str()?.into_owned()",
                AtomKind::ByteString => "r.next_bytestring()?.into_owned()",
                AtomKind::Symbol => "r.next_symbol()?.into_owned()",
            };
            ctxt.define_atom(body, &dest, item(reader.to_owned()));
            dest
        },
        SimplePattern::Embedded { .. } => {
            ctxt.define_atom(body, &dest, item("r.demand_next(true)?.value().to_embedded()?.clone()"));
            dest
        },
        SimplePattern::Lit { value } => {
            let f = Box::new(|_ctxt: &mut FunctionContext, _: &'_ mut Vec<Item>| ());
            read_expected_literals(ctxt, body, vec![(value.clone(), f)]);
            ctxt.define_atom(body, &dest, item("()"));
            dest
        },
        SimplePattern::Seqof { pattern } => {
            let compound_dest = ctxt.gentempname();
            ctxt.with_definite_mode(|ctxt| {
                let boundary_tracker = BoundaryTracker::unwrap(
                    ctxt, body, "r.open_sequence()?;", boundary_tracker);
                let mut inner = Vec::new();
                boundary_tracker.emit_boundary(&mut inner);
                let item_dest = simple_pattern_reader(ctxt, pattern, None, &mut inner);
                inner.push(item(seq![compound_dest.to_owned(), ".push(",
                                     store_wrap(true, &field_type(pattern), &item_dest), ");"]));
                ctxt.declare_compound(body, &compound_dest, item("std::vec::Vec::new()"));
                boundary_tracker.emit_loop(body, inner);
            });
            ctxt.define_atom(body, &dest, item(compound_dest));
            dest
        },
        SimplePattern::Setof { pattern } => {
            let compound_dest = ctxt.gentempname();
            ctxt.with_definite_mode(|ctxt| {
                let boundary_tracker = BoundaryTracker::new(
                    ctxt, body, "r.open_set()?;", "_support::B::Item::SetValue");
                let mut inner = Vec::new();
                boundary_tracker.emit_boundary(&mut inner);
                let item_dest = simple_pattern_reader(ctxt, pattern, None, &mut inner);
                inner.push(item(seq![compound_dest.to_owned(), ".insert(",
                                     store_wrap(true, &field_type(pattern), &item_dest), ");"]));
                ctxt.declare_compound(body, &compound_dest, item("preserves::value::Set::new()"));
                boundary_tracker.emit_loop(body, inner);
            });
            ctxt.define_atom(body, &dest, item(compound_dest));
            dest
        },
        SimplePattern::Dictof { key, value } => {
            let compound_dest = ctxt.gentempname();
            ctxt.with_definite_mode(|ctxt| {
                let mut boundary_tracker = BoundaryTracker::new(
                    ctxt, body, "r.open_dictionary()?;", "_support::B::Item::DictionaryKey");
                let mut inner = Vec::new();
                boundary_tracker.emit_boundary(&mut inner);
                let key_dest = simple_pattern_reader(ctxt, key, None, &mut inner);
                boundary_tracker.item_expr = "_support::B::Item::DictionaryValue";
                boundary_tracker.emit_boundary(&mut inner);
                let value_dest = simple_pattern_reader(ctxt, value, None, &mut inner);
                inner.push(item(seq![
                    compound_dest.to_owned(), ".insert(",
                    store_wrap(true, &field_type(key), &key_dest), ", ",
                    store_wrap(true, &field_type(value), &value_dest), ");"]));
                ctxt.declare_compound(body, &compound_dest, item("preserves::value::Map::new()"));
                boundary_tracker.item_expr = "_support::B::Item::DictionaryKey";
                boundary_tracker.emit_loop(body, inner);
            });
            ctxt.define_atom(body, &dest, item(compound_dest));
            dest
        },
        SimplePattern::Ref(r) => {
            let tf = name![ctxt.m.render_ref(&**r, RefRenderStyle::Bare), "deserialize"];
            ctxt.define_atom(body, &dest, item(seq![tf, "(r)?"]));
            dest
        },
    }
}

fn named_pattern_reader(
    ctxt: &mut FunctionContext,
    p: &NamedPattern,
    boundary_tracker: Option<&BoundaryTracker>,
    body: &mut Vec<Item>,
) {
    match p {
        NamedPattern::Anonymous(p) => {
            pattern_reader(ctxt, p, boundary_tracker, body);
        },
        NamedPattern::Named(b) => {
            let Binding { name, pattern} = &**b;
            let dest = simple_pattern_reader(ctxt, pattern, boundary_tracker, body);
            let capture_ty = field_type(pattern);
            ctxt.capture(names::render_fieldname(name), capture_ty, dest);
        }
    }
}

fn pattern_reader(
    ctxt: &mut FunctionContext,
    p: &Pattern,
    boundary_tracker: Option<&BoundaryTracker>,
    body: &mut Vec<Item>,
) -> Option<String> {
    match p {
        Pattern::SimplePattern(s) =>
            Some(simple_pattern_reader(ctxt, s, boundary_tracker, body)),
        Pattern::CompoundPattern(c) => {
            match &**c {
                CompoundPattern::Rec { label, fields } => {
                    let mut boundary_tracker = BoundaryTracker::new(
                        ctxt, body, "r.open_record(None)?;", "_support::B::Item::RecordLabel");
                    boundary_tracker.emit_boundary(body);
                    boundary_tracker.item_expr = "_support::B::Item::RecordField";
                    named_pattern_reader(ctxt, &**label, None, body);
                    named_pattern_reader(ctxt, &**fields, Some(&boundary_tracker), body);
                },
                CompoundPattern::Tuple { patterns } => {
                    let boundary_tracker = BoundaryTracker::unwrap(
                        ctxt, body, "r.open_sequence()?;", boundary_tracker);
                    for p in patterns {
                        boundary_tracker.emit_boundary(body);
                        named_pattern_reader(ctxt, p, None, body);
                    }
                    body.push(item(seq!["r.ensure_complete", parens![
                        boundary_tracker.tracker_name.clone(),
                        seq!["&", boundary_tracker.item_expr]], "?;"]));
                },
                CompoundPattern::TuplePrefix { fixed, variable } => {
                    let boundary_tracker = BoundaryTracker::unwrap(
                        ctxt, body, "r.open_sequence()?;", boundary_tracker);
                    for p in fixed {
                        boundary_tracker.emit_boundary(body);
                        named_pattern_reader(ctxt, p, None, body);
                    }
                    named_pattern_reader(ctxt, &promote(variable), Some(&boundary_tracker), body);
                },
                CompoundPattern::Dict { entries } => {
                    let boundary_tracker = BoundaryTracker::new(
                        ctxt, body, "r.open_dictionary()?;", "_support::B::Item::DictionaryKey");
                    let mut inner = Vec::new();
                    boundary_tracker.emit_boundary(&mut inner);
                    let mut val_boundary_tracker = boundary_tracker.clone();
                    val_boundary_tracker.item_expr = "_support::B::Item::DictionaryValue";
                    body.extend(ctxt.with_indefinite_mode(|ctxt| {
                        read_expected_literals(ctxt, &mut inner, entries.0.iter().map(move |(key_lit, value_pat)| {
                            let value_pat = value_pat.clone();
                            let val_boundary_tracker = val_boundary_tracker.clone();
                            let f: LiteralContinuation = Box::new(
                                move |ctxt: &mut FunctionContext, innerinner: &mut Vec<Item>| {
                                    val_boundary_tracker.emit_boundary(innerinner);
                                    named_pattern_reader(ctxt, &promote(&value_pat), None, innerinner);
                                    innerinner.push(item("continue;"));
                                });
                            (key_lit.clone(), f)
                        }).collect());
                    }));
                    boundary_tracker.emit_loop(body, inner);
                }
            }
            None
        },
    }
}
