use crate::*;
use crate::syntax::block::{Item, Emittable};
use crate::syntax::block::constructors::*;
use crate::gen::schema::*;

use preserves::value::Set;

use super::context::BundleContext;
use super::context::ModuleContext;
use super::context::ModuleContextMode;
use super::context::RefRenderStyle;
use super::names;

#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub enum Purpose {
    Codegen,
    Xref,
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct TDefinition {
    pub purpose: Purpose,
    pub self_ref: Ref,
    pub body: TDefinitionBody,
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum TDefinitionBody {
        Union(Vec<(String, TSimple)>),
        Simple(TSimple),
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum TSimple {
    Field(TField),
    Record(TRecord),
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum TField {
    Unit,
    Any,
    Embedded,
    Array(Box<TField>),
    Set(Box<TField>),
    Map(Box<TField>, Box<TField>),
    Ref(Ref),
    Base(String),
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct TRecord(pub Vec<(String, TField)>);

#[derive(Debug)]
pub struct TypePlugin;

impl compiler::Plugin for TypePlugin {
    fn generate_module(&self, m: &mut ModuleContext) {
        if let EmbeddedTypeName::Ref(r) = &m.schema.embedded_type {
            m.define_type(item(vertical(false, seq![
                seq!["pub type _Dom = ", m.render_ref(&*r, RefRenderStyle::Bare), ";"],
                seq!["pub type _Ptr = std::sync::Arc<_Dom>;"],
                seq!["pub type _Any = preserves::value::ArcValue<_Ptr>;"]
            ])));
        }
    }

    fn generate_definition(&self, m: &mut ModuleContext, n: &str, d: &Definition) {
        if let ModuleContextMode::TargetGeneric = m.mode {
            let ty = definition_type(&m.module_path, Purpose::Codegen, n, d);
            m.define_type(item(ty.render(m, n)));
            m.define_type(item(seq![
                "impl", ty.generic_decl(m), " preserves::value::Domain for ",
                names::render_constructor(n), ty.generic_arg(m), " {}"]));
        }
    }
}

pub fn definition_type(module: &ModulePath, purpose: Purpose, n: &str, d: &Definition) -> TDefinition {
    TDefinition {
        purpose,
        self_ref: Ref { module: module.clone(), name: n.to_owned() },
        body: match d {
            Definition::Or { pattern_0, pattern_1, pattern_n } =>
                TDefinitionBody::Union(or_definition_type(pattern_0, pattern_1, pattern_n)),
            Definition::And { pattern_0, pattern_1, pattern_n } =>
                TDefinitionBody::Simple(and_definition_type(pattern_0, pattern_1, pattern_n)),
            Definition::Pattern(p) =>
                TDefinitionBody::Simple(pattern_type(p)),
        }
    }
}

pub fn or_definition_type(
    p0: &NamedAlternative,
    p1: &NamedAlternative,
    pn: &Vec<NamedAlternative>,
) -> Vec<(String, TSimple)> {
    let mut entries = Vec::new();
    entries.push((p0.variant_label.to_owned(), pattern_type(&p0.pattern)));
    entries.push((p1.variant_label.to_owned(), pattern_type(&p1.pattern)));
    for e in pn {
        entries.push((e.variant_label.to_owned(), pattern_type(&e.pattern)));
    }
    entries
}

pub fn and_definition_type(
    p0: &NamedPattern,
    p1: &NamedPattern,
    pn: &Vec<NamedPattern>,
) -> TSimple {
    let mut arms = vec![p0, p1];
    arms.extend(pn);
    record_type(&arms)
}

pub fn pattern_type(p: &Pattern) -> TSimple {
    match p {
        Pattern::SimplePattern(p) =>
            TSimple::Field(field_type(p)),
        Pattern::CompoundPattern(_) =>
            record_type(&vec![&NamedPattern::Anonymous(Box::new(p.clone()))]),
    }
}

pub fn record_type(ps: &Vec<&NamedPattern>) -> TSimple {
    let fs = gather_fields(ps, Vec::new());
    if fs.is_empty() {
        TSimple::Field(TField::Unit)
    } else {
        TSimple::Record(TRecord(fs))
    }
}

pub fn gather_fields(ps: &Vec<&NamedPattern>, mut fs: Vec<(String, TField)>) -> Vec<(String, TField)>
{
    for p in ps.iter() {
        fs = gather_field(p, fs);
    }
    fs
}

pub fn gather_field(p: &NamedPattern, mut fs: Vec<(String, TField)>) -> Vec<(String, TField)> {
    match p {
        NamedPattern::Named(b) => {
            let Binding { name, pattern } = &**b;
            fs.push((name.to_owned(), field_type(pattern)));
            fs
        },
        NamedPattern::Anonymous(p) => match &**p {
            Pattern::SimplePattern(_) =>
                fs,
            Pattern::CompoundPattern(c) => match &**c {
                CompoundPattern::Rec { label, fields } =>
                    gather_field(&*fields, gather_field(&*label, fs)),
                CompoundPattern::Tuple { patterns } =>
                    gather_fields(&patterns.iter().collect(), fs),
                CompoundPattern::TuplePrefix { fixed, variable } =>
                    gather_field(&promote(&**variable), gather_fields(&fixed.iter().collect(), fs)),
                CompoundPattern::Dict { entries } => {
                    for (_k, p) in &entries.0 {
                        fs = gather_field(&promote(&p), fs);
                    }
                    fs
                }
            }
        }
    }
}

pub fn promote(p: &NamedSimplePattern) -> NamedPattern {
    match p {
        NamedSimplePattern::Anonymous(p) => NamedPattern::Anonymous(Box::new(Pattern::SimplePattern(p.clone()))),
        NamedSimplePattern::Named(n) => NamedPattern::Named(n.clone()),
    }
}

pub fn field_type(p: &SimplePattern) -> TField {
    match p {
        SimplePattern::Any => TField::Any,
        SimplePattern::Atom { atom_kind: k } =>
            match **k {
                AtomKind::Boolean => TField::Base("bool".to_owned()),
                AtomKind::Float => TField::Base("preserves::value::Float".to_owned()),
                AtomKind::Double => TField::Base("preserves::value::Double".to_owned()),
                AtomKind::SignedInteger => TField::Base("preserves::value::signed_integer::SignedInteger".to_owned()),
                AtomKind::String => TField::Base("std::string::String".to_owned()),
                AtomKind::ByteString => TField::Base("std::vec::Vec<u8>".to_owned()),
                AtomKind::Symbol => TField::Base("std::string::String".to_owned()),
            },
        SimplePattern::Embedded { .. } => TField::Embedded,
        SimplePattern::Lit { .. } => TField::Unit,
        SimplePattern::Seqof { pattern: t } => TField::Array(Box::new(field_type(t))),
        SimplePattern::Setof { pattern: t } => TField::Set(Box::new(field_type(t))),
        SimplePattern::Dictof { key: k, value: v } =>
            TField::Map(Box::new(field_type(k)), Box::new(field_type(v))),
        SimplePattern::Ref(r) => TField::Ref((**r).clone()),
    }
}

type WalkState<'a, 'b> = super::cycles::WalkState<&'a BundleContext<'b>>;

impl TField {
    fn render(&self, ctxt: &ModuleContext, box_needed: bool) -> impl Emittable {
        match self {
            TField::Unit => seq!["()"],
            TField::Any => seq![ctxt.any_type()],
            TField::Embedded => seq![ctxt.any_type(), "::Embedded"],
            TField::Array(t) => seq!["std::vec::Vec<", t.render(ctxt, false), ">"],
            TField::Set(t) => seq!["preserves::value::Set<", t.render(ctxt, false), ">"],
            TField::Map(k, v) => seq!["preserves::value::Map",
                                      anglebrackets![k.render(ctxt, false),
                                                     v.render(ctxt, false)]],
            TField::Ref(r) =>
                if box_needed {
                    seq!["std::boxed::Box", anglebrackets![
                        ctxt.render_ref(r, RefRenderStyle::Qualified)]]
                } else {
                    seq![ctxt.render_ref(r, RefRenderStyle::Qualified)]
                },
            TField::Base(n) => seq![n.to_owned()],
        }
    }

    fn language_types(&self, s: &mut WalkState, ts: &mut Set<String>) {
        match self {
            TField::Unit | TField::Any | TField::Embedded | TField::Base(_) => (),
            TField::Array(f) => f.language_types(s, ts),
            TField::Set(f) => f.language_types(s, ts),
            TField::Map(k, v) => {
                k.language_types(s, ts);
                v.language_types(s, ts);
            }
            TField::Ref(r) =>
                s.cycle_check(
                    r,
                    |ctxt, r| ctxt.type_for_name(r),
                    |s, t| match t {
                        Some(ty) if ty.purpose == Purpose::Codegen => ty._language_types(s, ts),
                        Some(_) | None => {
                            let xmts = &s.context.config.external_modules.get(&r.module.0).unwrap()
                                .rust_language_types;
                            if let Some(f) = xmts.definitions.get(&r.name).or(xmts.fallback.as_ref()) {
                                ts.extend(f(s.context.any_type()));
                            }
                        }
                    },
                    || ()),
        }
    }

    fn has_embedded(&self, s: &mut WalkState) -> bool {
        match self {
            TField::Unit | TField::Base(_) => false,
            TField::Any | TField::Embedded => true, // at least potentially true
            TField::Array(f) => f.has_embedded(s),
            TField::Set(f) => f.has_embedded(s),
            TField::Map(k, v) => k.has_embedded(s) || v.has_embedded(s),
            TField::Ref(r) =>
                // v TODO: should the "false" be configurable? cf. ModuleContext::ref_has_embedded.
                s.cycle_check(
                    r,
                    |ctxt, r| ctxt.type_for_name(r),
                    |s, t| t.map(|t| t._has_embedded(s)).unwrap_or(false),
                    || false),
        }
    }
}

impl TSimple {
    pub fn render(&self, ctxt: &ModuleContext, ptr: Item, is_struct: bool, n: &str) -> impl Emittable {
        let semi = if is_struct { seq![";"] } else { seq![] };
        let ppub = if is_struct { "pub " } else { "" };
        seq![names::render_constructor(n), ptr.to_owned(),
             match self {
                 TSimple::Record(TRecord(fs)) => seq![" ", vertical(false, braces(
                     fs.iter().map(|(n, d)| item(
                         seq![ppub, names::render_fieldname(n), ": ", d.render(ctxt, !is_struct)]
                     )).collect()))],
                 TSimple::Field(TField::Unit) => semi,
                 TSimple::Field(t) => seq![parens![seq![ppub, t.render(ctxt, !is_struct)]], semi],
             }]
    }

    fn language_types(&self, s: &mut WalkState, ts: &mut Set<String>) {
        match self {
            TSimple::Field(f) => f.language_types(s, ts),
            TSimple::Record(TRecord(fs)) => fs.iter().for_each(|(_k, v)| v.language_types(s, ts)),
        }
    }

    fn has_embedded(&self, s: &mut WalkState) -> bool {
        match self {
            TSimple::Field(f) => f.has_embedded(s),
            TSimple::Record(TRecord(fs)) => fs.iter().any(|(_k, v)| v.has_embedded(s)),
        }
    }
}

impl TDefinition {
    pub fn generic_decl(&self, ctxt: &ModuleContext) -> Item {
        if self.has_embedded(ctxt.bundle) {
            item(anglebrackets![
                seq![ctxt.any_type(), ": preserves::value::NestedValue"]])
        } else {
            item("")
        }
    }

    pub fn generic_decl_with_defaults(&self, ctxt: &ModuleContext) -> Item {
        if self.has_embedded(ctxt.bundle) {
            item(anglebrackets![
                seq![ctxt.any_type(), ": preserves::value::NestedValue = ",
                     match ctxt.schema.embedded_type {
                         EmbeddedTypeName::False => "preserves::value::IOValue",
                         EmbeddedTypeName::Ref(_) => "_Any",
                     }]])
        } else {
            item("")
        }
    }

    pub fn generic_arg(&self, ctxt: &ModuleContext) -> Item {
        if self.has_embedded(ctxt.bundle) {
            item(anglebrackets![ctxt.any_type()])
        } else {
            item("")
        }
    }

    pub fn render(&self, ctxt: &ModuleContext, n: &str) -> impl Emittable {
        vertical(false, seq![
            "#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Hash)]",
            match &self.body {
                TDefinitionBody::Union(items) =>
                    seq!["pub enum ",
                         names::render_constructor(n), self.generic_decl_with_defaults(ctxt), " ",
                         vertical(false, braces(items.iter().map(
                             |(n, d)| item(d.render(ctxt, item(""), false, n))).collect()))],
                TDefinitionBody::Simple(s) =>
                    seq!["pub struct ", s.render(ctxt, self.generic_decl_with_defaults(ctxt), true, n)],
            }])
    }

    fn walk_state<'a, 'b>(&self, ctxt: &'a BundleContext<'b>) -> WalkState<'a, 'b> {
        WalkState::new(ctxt, self.self_ref.module.clone())
    }

    pub fn language_types(&self, ctxt: &BundleContext) -> Set<String> {
        let mut ts = Set::new();
        self._language_types(&mut self.walk_state(ctxt), &mut ts);
        ts
    }

    fn _language_types(&self, s: &mut WalkState, ts: &mut Set<String>) {
        match &self.body {
            TDefinitionBody::Union(entries) => entries.iter().for_each(|(_k, v)| v.language_types(s, ts)),
            TDefinitionBody::Simple(t) => t.language_types(s, ts),
        }
    }

    pub fn has_embedded(&self, ctxt: &BundleContext) -> bool {
        self._has_embedded(&mut self.walk_state(ctxt))
    }

    fn _has_embedded(&self, s: &mut WalkState) -> bool {
        match &self.body {
            TDefinitionBody::Union(entries) => entries.iter().any(|(_k, v)| v.has_embedded(s)),
            TDefinitionBody::Simple(t) => t.has_embedded(s),
        }
    }
}
