//! Handling of repetition, and also useful methods on context

use super::framework::*;

pub const NESTING_LIMIT: u16 = 100;

pub use RepeatOver as RO;

#[derive(Debug, Copy, Clone, Eq, PartialEq, Display)]
#[strum(serialize_all = "snake_case")]
pub enum RepeatOver {
    Variants,
    Fields,
}

#[derive(Debug, Clone)]
struct RepeatOverInference {
    over: RepeatOver,
    span: Span,
}

#[derive(Default, Debug, Clone)]
pub struct RepeatAnalysisVisitor {
    over: Option<RepeatOverInference>,
}

pub trait AnalyseRepeat {
    fn analyse_repeat(
        &self,
        visitor: &mut RepeatAnalysisVisitor,
    ) -> syn::Result<()>;
}

/// What would `${fname}` expand to?  As type from [`syn`].
///
/// Implements [`quote::IdentFragment`] and [`ToTokens`].
#[derive(Debug, Eq, PartialEq)]
pub enum Fname<'r> {
    Name(&'r syn::Ident),
    Index(syn::Index),
}

impl RepeatAnalysisVisitor {
    fn set_over(&mut self, over: RepeatOverInference) -> syn::Result<()> {
        match &self.over {
            None => self.over = Some(over),
            Some(already) => {
                if already.over != over.over {
                    let mut e1 = already.span.error(format_args!(
 "inconsistent repetition depth: firstly, {} inferred here",
                            already.over,
                        ));
                    let e2 = over.span.error(format_args!(
 "inconsistent repetition depth: secondly, {} inferred here",
                            over.over,
                        ));
                    e1.combine(e2);
                    return Err(e1);
                }
            }
        }
        Ok(())
    }

    pub fn finish(self, start: DelimSpan) -> Result<RepeatOver, syn::Error> {
        Ok(self
            .over
            .ok_or_else(|| {
                start.error(
            "no contained expansion field determined what to repeat here"
        )
            })?
            .over)
    }
}

impl<O: SubstParseContext> AnalyseRepeat for SubstIf<O> {
    fn analyse_repeat(
        &self,
        visitor: &mut RepeatAnalysisVisitor,
    ) -> syn::Result<()> {
        for (cond, _) in &self.tests {
            cond.analyse_repeat(visitor)?;
            // We don't analyse the consequents.  We would have to scan
            // them all unconditionally, which is rather strange.  It might
            // even cause trouble: the template author might have used
            // conditions (including for example `is_enum` to try to select an
            // expansion appropriate for the context.
            //
            // It seems less confusing to avoid this, and require the
            // template author to use `${for ...}`.
        }
        if let Some(consequence) = &self.otherwise {
            consequence.analyse_repeat(visitor)?;
        }
        Ok(())
    }
}

impl<O: SubstParseContext> AnalyseRepeat for Template<O> {
    /// Analyses a template section to be repeated
    fn analyse_repeat(
        &self,
        visitor: &mut RepeatAnalysisVisitor,
    ) -> syn::Result<()> {
        for element in &self.elements {
            element.analyse_repeat(visitor)?;
        }
        Ok(())
    }
}

impl<O: SubstParseContext> AnalyseRepeat for TemplateElement<O> {
    fn analyse_repeat(
        &self,
        visitor: &mut RepeatAnalysisVisitor,
    ) -> syn::Result<()> {
        match self {
            TE::Ident(_) => {}
            TE::Literal(..) => {}
            TE::LitStr(_) => {}
            TE::Punct(..) => {}
            TE::Repeat(_) => {}
            TE::Group { template, .. } => template.analyse_repeat(visitor)?,
            TE::Subst(exp) => exp.analyse_repeat(visitor)?,
        }
        Ok(())
    }
}

impl<O: SubstParseContext> AnalyseRepeat for Subst<O> {
    fn analyse_repeat(
        &self,
        visitor: &mut RepeatAnalysisVisitor,
    ) -> syn::Result<()> {
        macro_rules! recurse { { $v:expr } => { {
            $v.analyse_repeat(visitor)?;
            None
        } } }

        let over = match &self.sd {
            SD::tname(..) => None,
            SD::vname(..) => Some(RO::Variants),
            SD::fname(..) => Some(RO::Fields),
            SD::ttype(..) => None,
            SD::tdeftype(..) => None,
            SD::ftype(..) => Some(RO::Fields),
            SD::fpatname(_) => Some(RO::Fields),
            SD::Vis(SubstVis::T, ..) => None,
            SD::Vis(SubstVis::F, ..) => Some(RO::Fields),
            SD::Vis(SubstVis::FD, ..) => Some(RO::Fields),
            SD::Xmeta(sm) => sm.repeat_over(),
            SD::tattrs(..) => None,
            SD::vattrs(..) => Some(RO::Variants),
            SD::fattrs(..) => Some(RO::Fields),
            SD::tgens(..) => None,
            SD::tdefgens(..) => None,
            SD::tgnames(..) => None,
            SD::twheres(..) => None,
            SD::vpat(..) => Some(RO::Variants),
            SD::vtype(..) => Some(RO::Variants),
            SD::tdefkwd(..) => None,
            SD::is_struct(..) => None,
            SD::is_enum(..) => None,
            SD::is_union(..) => None,
            SD::v_is_unit(..) => Some(RO::Variants),
            SD::v_is_tuple(..) => Some(RO::Variants),
            SD::v_is_named(..) => Some(RO::Variants),
            SD::tdefvariants(..) => None,
            SD::fdefine(..) => Some(RO::Fields),
            SD::vdefbody(..) => Some(RO::Variants),
            SD::paste(body, ..) => recurse!(body),
            SD::ChangeCase(body, ..) => recurse!(body),
            SD::when(..) => None, // out-of-place when, ignore it
            SD::define(..) => None,
            SD::defcond(..) => None,
            SD::UserDefined(..) => None,
            SD::is_empty(_, content) => recurse!(content),
            SD::approx_equal(_, ab) => {
                for x in ab {
                    x.analyse_repeat(visitor)?;
                }
                None
            }
            SD::not(cond, _) => recurse!(cond),
            SD::If(conds, ..) | SD::select1(conds, ..) => recurse!(conds),
            SD::any(conds, _) | SD::all(conds, _) => {
                for c in conds.iter() {
                    c.analyse_repeat(visitor)?;
                }
                None
            }
            // Has a RepeatOver, but does not imply anything about its context.
            SD::For(..) => None,
            SD::False(..) | SD::True(..) => None, // condition: ignore.
            SD::error(..) => None,
            SD::ignore(content, _) => recurse!(content),
            SD::dbg(ddr) => recurse!(ddr.content_parsed),
            SD::dbg_all_keywords(..) => None,
            SD::Crate(..) => None,
        };
        if let Some(over) = over {
            let over = RepeatOverInference {
                over,
                span: self.kw_span,
            };
            visitor.set_over(over)?;
        }
        Ok(())
    }
}

/// Implemented for [`WithinVariant`] and [`WithinField`]
///
/// For combining code that applies similarly for different repeat levels.
pub trait WithinRepeatLevel<'w>: 'w {
    fn level_display_name() -> &'static str;

    fn current(ctx: &'w Context) -> Option<&'w Self>;

    /// Iterate over all the things at this level
    ///
    /// If it needs a current container of the next level up (eg, a
    /// field needing a variant) and there is none current, iterates
    /// over containing things too.
    fn for_each<'c, F, E>(ctx: &'c Context<'c>, call: F) -> Result<(), E>
    where
        'c: 'w,
        F: FnMut(&Context, &Self) -> Result<(), E>;
}

impl<'w> WithinRepeatLevel<'w> for WithinVariant<'w> {
    fn level_display_name() -> &'static str {
        "variant"
    }

    fn current(ctx: &'w Context) -> Option<&'w WithinVariant<'w>> {
        ctx.variant
    }

    fn for_each<'c, F, E>(ctx: &'c Context<'c>, mut call: F) -> Result<(), E>
    where
        'c: 'w,
        F: FnMut(&Context, &WithinVariant<'w>) -> Result<(), E>,
    {
        let mut within_variant = |variant, ppv: &'c PreprocessedVariant| {
            let fields = &ppv.fields;
            let pmetas = &ppv.pmetas;
            let pfields = &ppv.pfields;
            let wv = WithinVariant {
                variant,
                fields,
                pmetas,
                pfields,
            };
            let wv = &wv;
            let ctx = Context {
                variant: Some(wv),
                ..*ctx
            };
            call(&ctx, wv)
        };
        match &ctx.top.data {
            syn::Data::Enum(syn::DataEnum { variants, .. }) => {
                for (variant, pvariant) in izip!(variants, ctx.pvariants) {
                    within_variant(Some(variant), pvariant)?;
                }
            }
            syn::Data::Struct(_) | syn::Data::Union(_) => {
                within_variant(None, &ctx.pvariants[0])?;
            }
        }
        Ok(())
    }
}

impl<'w> WithinRepeatLevel<'w> for WithinField<'w> {
    fn level_display_name() -> &'static str {
        "field"
    }

    fn current(ctx: &'w Context) -> Option<&'w WithinField<'w>> {
        ctx.field
    }

    fn for_each<'c, F, E>(ctx: &'c Context<'c>, mut call: F) -> Result<(), E>
    where
        'c: 'w,
        F: FnMut(&Context, &WithinField<'w>) -> Result<(), E>,
    {
        ctx.for_with_within(|ctx, variant: &WithinVariant| {
            for (index, (field, pfield)) in
                izip!(variant.fields, variant.pfields,).enumerate()
            {
                // Ideally WithinField would contain a within_variant field
                // but the genercity of the lifetimes is hard to express.
                // I haven't found a version that compiles, unless we
                // *copy* the WithinVariant for each field.
                let index = index.try_into().expect(">=2^32 fields!");
                let wf = WithinField {
                    field,
                    index,
                    pfield,
                };
                let wf = &wf;
                let ctx = Context {
                    field: Some(wf),
                    ..*ctx
                };
                call(&ctx, wf)?;
            }
            Ok(())
        })
    }
}

impl<'c> Context<'c> {
    /// Returns an [`ErrorLoc`] for the current part of the driver
    pub fn error_loc(&self) -> (Span, &'static str) {
        if let Some(field) = &self.field {
            (field.field.span(), "in this field")
        } else if let Some(variant) =
            &self.variant.and_then(|variant| variant.variant.as_ref())
        {
            (variant.span(), "in this variant")
        } else {
            (self.top.span(), "in this data structure")
        }
    }

    /// Obtains the relevant `Within`(s), and calls `call` for each one
    ///
    /// If there is a current `W`, simply calls `call`.
    /// Otherwise, iterates over all of them and calls `call` for each one.
    pub fn for_with_within<'w, W, F, E>(&'c self, mut call: F) -> Result<(), E>
    where
        'c: 'w,
        W: WithinRepeatLevel<'w>,
        F: FnMut(&Context, &W) -> Result<(), E>,
    {
        let ctx = self;
        if let Some(w) = W::current(ctx) {
            call(ctx, w)?;
        } else {
            W::for_each(ctx, call)?;
        }
        Ok(())
    }

    /// Obtains the current `Within` of type `W`
    ///
    /// Demands that there actually is a current container `W`.
    /// If we aren't, calls it an error.
    fn within_level<W>(&'c self, why: &dyn Spanned) -> syn::Result<&W>
    where
        W: WithinRepeatLevel<'c>,
    {
        W::current(self).ok_or_else(|| {
            why.span().error(format_args!(
                "must be within a {} (so, in a repeat group)",
                W::level_display_name(),
            ))
        })
    }

    /// Obtains the current field (or calls it an error)
    pub fn field(&self, why: &dyn Spanned) -> syn::Result<&WithinField> {
        self.within_level(why)
    }
    /// Obtains the current variant (or calls it an error)
    pub fn variant(&self, why: &dyn Spanned) -> syn::Result<&WithinVariant> {
        self.within_level(why)
    }
    /// Obtains the current variant as a `syn::Variant`
    pub fn syn_variant(
        &self,
        why: &dyn Spanned,
    ) -> syn::Result<&syn::Variant> {
        let r = self.variant(why)?.variant.as_ref().ok_or_else(|| {
            why.span().error("expansion only valid in enums")
        })?;
        Ok(r)
    }

    pub fn display_for_dbg(&self) -> impl Display + '_ {
        struct Adapter<'a>(&'a Context<'a>);

        impl Display for Adapter<'_> {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                let ctx = self.0;
                write!(f, "for {}", &ctx.top.ident)?;
                if let Some(wv) = &ctx.variant {
                    if let Some(v) = &wv.variant {
                        write!(f, "::{}", &v.ident)?;
                    }
                }
                if let Some(wf) = &ctx.field {
                    // we're just using the Display impl, any span will do
                    let span = Span::call_site();
                    write!(f, ".{}", &wf.fname(span))?;
                }
                if let Some(templ) = &ctx.template_name {
                    let templ = templ.to_token_stream().to_string();
                    write!(f, " from {}", templ)?;
                }
                Ok::<_, fmt::Error>(())
            }
        }

        Adapter(self)
    }
}

//---------- Fname ----------

impl<'w> WithinField<'w> {
    /// What would `${fname}` expand to?
    pub fn fname(&self, tspan: Span) -> Fname {
        if let Some(fname) = &self.field.ident {
            // todo is this the right span to emit?
            Fname::Name(fname)
        } else {
            Fname::Index(syn::Index {
                index: self.index,
                span: tspan,
            })
        }
    }
}

impl IdentFrag for Fname<'_> {
    type BadIdent = IdentFragInfallible;
    fn frag_to_tokens(
        &self,
        out: &mut TokenStream,
    ) -> Result<(), IdentFragInfallible> {
        Ok(self.to_tokens(out))
    }
    fn fragment(&self) -> String {
        self.to_string()
    }
}
impl Display for Fname<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Fname::Name(v) => quote::IdentFragment::fmt(v, f),
            Fname::Index(v) => quote::IdentFragment::fmt(v, f),
        }
    }
}
impl ToTokens for Fname<'_> {
    fn to_tokens(&self, out: &mut TokenStream) {
        match self {
            Fname::Name(v) => v.to_tokens(out),
            Fname::Index(v) => v.to_tokens(out),
        }
    }
}

//---------- useful impl ----------

impl<'w> WithinVariant<'w> {
    pub fn is_struct_toplevel_as_variant(&self) -> bool {
        self.variant.is_none()
    }
}

//---------- definitions (user-defined keywords) ----------

impl<'c> Context<'c> {
    pub fn find_definition<B>(
        &'c self,
        call: &'c DefinitionName,
    ) -> syn::Result<Option<(&'c Definition<B>, Context<'c>)>>
    where
        Definitions<'c>: AsRef<[&'c Definition<B>]>,
        B: 'static,
    {
        let def = match self.definitions.find_raw(call) {
            Some(y) => y,
            None => return Ok(None),
        };

        let ctx = self.deeper(&def.name, call)?;
        Ok(Some((def, ctx)))
    }

    fn deeper(
        &'c self,
        def: &'c DefinitionName,
        call: &'c DefinitionName,
    ) -> syn::Result<Context<'c>> {
        let nesting_depth = self.nesting_depth + 1;
        let stack_entry = (self, call);
        if nesting_depth > NESTING_LIMIT {
            // We report the definition site of the innermost reference:
            let mut errs = def.error(format_args!(
 "probably-recursive user-defined expansion/condition (more than {} deep)",
                NESTING_LIMIT
            ));
            // And the unique reference sites from the call stack
            let calls = {
                let mut ascend = Some(stack_entry);
                iter::from_fn(|| {
                    let (ctx, call) = ascend?;
                    ascend = ctx.nesting_parent;
                    Some((call, ctx.nesting_depth))
                })
                .collect_vec()
            };

            // Collect and reverse because we preferentially want
            // to display less deep entries (earlier ones), which are
            // furthest away in the stack chain.

            let calls = calls
                .iter()
                .rev()
                .unique_by(
                    // De-dup by pointer identity on the name as found
                    // at the call site.  These are all references
                    // nodes in our template AST, so this is correct.
                    |(call, _)| *call as *const DefinitionName,
                )
                .collect_vec();

            // We report the deepest errors first, since they're
            // definitely implicated in the cycle and more interesting.
            // (So this involves collecting and reversing again.)
            for (call, depth) in calls.iter().rev() {
                errs.combine(call.error(format_args!(
             "reference involved in too-deep expansion/condition, depth {}",
                    depth,
                )));
            }
            return Err(errs);
        }
        Ok(Context {
            nesting_depth,
            nesting_parent: Some(stack_entry),
            ..*self
        })
    }
}

pub struct DefinitionsIter<'c, B>(
    Option<&'c Definitions<'c>>,
    PhantomData<&'c B>,
);

impl<'c, B> Iterator for DefinitionsIter<'c, B>
where
    Definitions<'c>: AsRef<[&'c Definition<B>]>,
{
    type Item = &'c [&'c Definition<B>];
    fn next(&mut self) -> Option<Self::Item> {
        let here = self.0?;
        let r = here.as_ref();
        self.0 = here.earlier;
        Some(r)
    }
}

impl<'c> Definitions<'c> {
    pub fn iter<B>(&'c self) -> DefinitionsIter<'c, B>
    where
        Definitions<'c>: AsRef<[&'c Definition<B>]>,
    {
        DefinitionsIter(Some(self), PhantomData)
    }

    /// Find the definition of `name` as a `B`, without recursion checking
    ///
    /// The caller is responsible for preventing unbounded recursion.
    pub fn find_raw<B>(
        &'c self,
        name: &DefinitionName,
    ) -> Option<&'c Definition<B>>
    where
        Definitions<'c>: AsRef<[&'c Definition<B>]>,
        B: 'static,
    {
        self.iter()
            .map(|l| l.iter().rev())
            .flatten()
            .find(|def| &def.name == name)
            .cloned()
    }
}

impl<'c> AsRef<[&'c Definition<DefinitionBody>]> for Definitions<'c> {
    fn as_ref(&self) -> &[&'c Definition<DefinitionBody>] {
        self.here
    }
}
impl<'c> AsRef<[&'c Definition<DefCondBody>]> for Definitions<'c> {
    fn as_ref(&self) -> &[&'c Definition<DefCondBody>] {
        self.conds
    }
}
