use crate::ast::*;
use crate::errors::ErrorCtx;
use crate::formatter::{FmtCtx, IntoFormatter};
use crate::ids::Vector;
use crate::llbc_ast;
use crate::name_matcher::NamePattern;
use crate::pretty::FmtWithCtx;
use crate::ullbc_ast;
use std::fmt;
pub struct TransformOptions {
    pub no_code_duplication: bool,
    pub hide_marker_traits: bool,
    pub no_merge_goto_chains: bool,
    pub item_opacities: Vec<(NamePattern, ItemOpacity)>,
}
pub struct TransformCtx<'ctx> {
    pub options: TransformOptions,
    pub translated: TranslatedCrate,
    pub errors: ErrorCtx<'ctx>,
}
pub trait UllbcPass: Sync {
    fn transform_body(&self, _ctx: &mut TransformCtx<'_>, _body: &mut ullbc_ast::ExprBody) {}
    fn transform_function(
        &self,
        ctx: &mut TransformCtx<'_>,
        _decl: &mut FunDecl,
        body: Result<&mut ullbc_ast::ExprBody, Opaque>,
    ) {
        if let Ok(body) = body {
            self.transform_body(ctx, body)
        }
    }
    fn transform_global(
        &self,
        ctx: &mut TransformCtx<'_>,
        _decl: &mut GlobalDecl,
        body: Result<&mut ullbc_ast::ExprBody, Opaque>,
    ) {
        if let Ok(body) = body {
            self.transform_body(ctx, body)
        }
    }
    fn transform_ctx(&self, ctx: &mut TransformCtx<'_>) {
        ctx.for_each_fun_decl(|ctx, decl, body| {
            let body = body.map(|body| body.as_unstructured_mut().unwrap());
            self.log_before_body(ctx, &decl.item_meta.name, body.as_deref());
            self.transform_function(ctx, decl, body);
        });
        ctx.for_each_global_decl(|ctx, decl, body| {
            let body = body.map(|body| body.as_unstructured_mut().unwrap());
            self.log_before_body(ctx, &decl.item_meta.name, body.as_deref());
            self.transform_global(ctx, decl, body);
        });
    }
    fn name(&self) -> &str {
        std::any::type_name::<Self>()
    }
    fn log_before_body(
        &self,
        ctx: &TransformCtx<'_>,
        name: &Name,
        body: Result<&ullbc_ast::ExprBody, &Opaque>,
    ) {
        let fmt_ctx = &ctx.into_fmt();
        let body_str = if let Ok(body) = body {
            body.fmt_with_ctx(fmt_ctx)
        } else {
            "<opaque>".to_owned()
        };
        trace!(
            "# About to run pass [{}] on `{}`:\n{}",
            self.name(),
            name.with_ctx(fmt_ctx),
            body_str,
        );
    }
}
pub trait LlbcPass: Sync {
    fn transform_body(&self, _ctx: &mut TransformCtx<'_>, _body: &mut llbc_ast::ExprBody) {}
    fn transform_function(
        &self,
        ctx: &mut TransformCtx<'_>,
        _decl: &mut FunDecl,
        body: Result<&mut llbc_ast::ExprBody, Opaque>,
    ) {
        if let Ok(body) = body {
            self.transform_body(ctx, body)
        }
    }
    fn transform_global(
        &self,
        ctx: &mut TransformCtx<'_>,
        _decl: &mut GlobalDecl,
        body: Result<&mut llbc_ast::ExprBody, Opaque>,
    ) {
        if let Ok(body) = body {
            self.transform_body(ctx, body)
        }
    }
    fn transform_ctx(&self, ctx: &mut TransformCtx<'_>) {
        ctx.for_each_fun_decl(|ctx, decl, body| {
            let body = body.map(|body| body.as_structured_mut().unwrap());
            self.log_before_body(ctx, &decl.item_meta.name, body.as_deref());
            self.transform_function(ctx, decl, body);
        });
        ctx.for_each_global_decl(|ctx, decl, body| {
            let body = body.map(|body| body.as_structured_mut().unwrap());
            self.log_before_body(ctx, &decl.item_meta.name, body.as_deref());
            self.transform_global(ctx, decl, body);
        });
    }
    fn name(&self) -> &str {
        std::any::type_name::<Self>()
    }
    fn log_before_body(
        &self,
        ctx: &TransformCtx<'_>,
        name: &Name,
        body: Result<&llbc_ast::ExprBody, &Opaque>,
    ) {
        let fmt_ctx = &ctx.into_fmt();
        let body_str = if let Ok(body) = body {
            body.fmt_with_ctx(fmt_ctx)
        } else {
            "<opaque>".to_owned()
        };
        trace!(
            "# About to run pass [{}] on `{}`:\n{}",
            self.name(),
            name.with_ctx(fmt_ctx),
            body_str,
        );
    }
}
pub trait TransformPass: Sync {
    fn transform_ctx(&self, ctx: &mut TransformCtx<'_>);
    fn name(&self) -> &str {
        std::any::type_name::<Self>()
    }
}
impl<'ctx> TransformCtx<'ctx> {
    pub(crate) fn continue_on_failure(&self) -> bool {
        self.errors.continue_on_failure()
    }
    pub(crate) fn has_errors(&self) -> bool {
        self.errors.has_errors()
    }
    pub(crate) fn span_err(&mut self, span: Span, msg: &str) {
        self.errors.span_err(span, msg)
    }
    pub(crate) fn with_def_id<F, T>(
        &mut self,
        def_id: impl Into<AnyTransId>,
        def_id_is_local: bool,
        f: F,
    ) -> T
    where
        F: FnOnce(&mut Self) -> T,
    {
        let current_def_id = self.errors.def_id;
        let current_def_id_is_local = self.errors.def_id_is_local;
        self.errors.def_id = Some(def_id.into());
        self.errors.def_id_is_local = def_id_is_local;
        let ret = f(self);
        self.errors.def_id = current_def_id;
        self.errors.def_id_is_local = current_def_id_is_local;
        ret
    }
    pub(crate) fn with_mut_bodies<R>(
        &mut self,
        f: impl FnOnce(&mut Self, &mut Vector<BodyId, Body>) -> R,
    ) -> R {
        let mut bodies = std::mem::take(&mut self.translated.bodies);
        let ret = f(self, &mut bodies);
        self.translated.bodies = bodies;
        ret
    }
    pub(crate) fn with_mut_fun_decls<R>(
        &mut self,
        f: impl FnOnce(&mut Self, &mut Vector<FunDeclId, FunDecl>) -> R,
    ) -> R {
        let mut fun_decls = std::mem::take(&mut self.translated.fun_decls);
        let ret = f(self, &mut fun_decls);
        self.translated.fun_decls = fun_decls;
        ret
    }
    pub(crate) fn with_mut_global_decls<R>(
        &mut self,
        f: impl FnOnce(&mut Self, &mut Vector<GlobalDeclId, GlobalDecl>) -> R,
    ) -> R {
        let mut global_decls = std::mem::take(&mut self.translated.global_decls);
        let ret = f(self, &mut global_decls);
        self.translated.global_decls = global_decls;
        ret
    }
    pub(crate) fn for_each_body(&mut self, mut f: impl FnMut(&mut Self, &mut Body)) {
        self.with_mut_bodies(|ctx, bodies| {
            for body in bodies {
                f(ctx, body)
            }
        })
    }
    pub(crate) fn for_each_structured_body(
        &mut self,
        mut f: impl FnMut(&mut Self, &mut llbc_ast::ExprBody),
    ) {
        self.for_each_body(|ctx, body| f(ctx, body.as_structured_mut().unwrap()))
    }
    pub(crate) fn for_each_fun_decl(
        &mut self,
        mut f: impl FnMut(&mut Self, &mut FunDecl, Result<&mut Body, Opaque>),
    ) {
        self.with_mut_bodies(|ctx, bodies| {
            ctx.with_mut_fun_decls(|ctx, decls| {
                for decl in decls.iter_mut() {
                    let body = match decl.body {
                        Ok(id) => {
                            match bodies.get_mut(id) {
                                Some(body) => Ok(body),
                                None => continue,
                            }
                        }
                        Err(Opaque) => Err(Opaque),
                    };
                    ctx.with_def_id(decl.def_id, decl.item_meta.is_local, |ctx| {
                        f(ctx, decl, body);
                    })
                }
            })
        })
    }
    pub(crate) fn for_each_global_decl(
        &mut self,
        mut f: impl FnMut(&mut Self, &mut GlobalDecl, Result<&mut Body, Opaque>),
    ) {
        self.with_mut_bodies(|ctx, bodies| {
            ctx.with_mut_global_decls(|ctx, decls| {
                for decl in decls.iter_mut() {
                    let body = match decl.body {
                        Ok(id) => {
                            match bodies.get_mut(id) {
                                Some(body) => Ok(body),
                                None => continue,
                            }
                        }
                        Err(Opaque) => Err(Opaque),
                    };
                    ctx.with_def_id(decl.def_id, decl.item_meta.is_local, |ctx| {
                        f(ctx, decl, body);
                    })
                }
            })
        })
    }
}
impl<'a> IntoFormatter for &'a TransformCtx<'_> {
    type C = FmtCtx<'a>;
    fn into_fmt(self) -> Self::C {
        self.translated.into_fmt()
    }
}
impl fmt::Display for TransformCtx<'_> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.translated.fmt(f)
    }
}