use crate::core::*;
use hashbrown::HashMap;
use indexmap::IndexSet;
use lazy_static::lazy_static;
use log::info;
use regex::Regex;
use std::fmt::Display;
use std::fmt::Write;
use tokio::time::Instant;
use ustr::Ustr;
#[derive(Clone, Debug)]
pub enum AssertName {
    Config,
    Constraint,
    Attribute,
    Group,
    GroupMember,
    GroupMin,
    GroupMax,
    RootFeature,
}
impl Display for Type {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}
impl Display for AssertName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Attribute => write!(f, "attribute value"),
            Self::Constraint => write!(f, "constraint"),
            Self::Config => write!(f, "configuration value"),
            Self::Group => write!(f, "group"),
            Self::GroupMin => write!(f, "lower bound"),
            Self::GroupMax => write!(f, "upper bound"),
            Self::GroupMember => write!(f, "group member"),
            Self::RootFeature => write!(f, "root feature are always required"),
        }
    }
}
#[derive(Clone, Debug)]
pub struct AssertInfo(pub ModuleSymbol, pub AssertName);
#[derive(Clone, Debug)]
pub struct Assert(pub Option<AssertInfo>, pub Expr);
#[derive(Clone, Debug)]
pub enum Expr {
    Bool(bool),
    Real(f64),
    String(String),
    Var(usize),
    And(Vec<Expr>),
    Or(Vec<Expr>),
    Not(Box<Expr>),
    Implies(Vec<Expr>),
    Greater(Vec<Expr>),
    Less(Vec<Expr>),
    Equal(Vec<Expr>),
    AtLeast(usize, Vec<Expr>),
    AtMost(usize, Vec<Expr>),
    Add(Vec<Expr>),
    Sub(Vec<Expr>),
    Mul(Vec<Expr>),
    Div(Vec<Expr>),
    Ceil(Box<Expr>),
    Floor(Box<Expr>),
    Strlen(Box<Expr>),
    StrLess(Vec<Expr>),
    StrLessEq(Vec<Expr>),
    StrConcat(Box<Expr>, Box<Expr>),
    Ite(Box<Expr>, Box<Expr>, Box<Expr>),
}
pub struct SMTModule {
    pub variables: IndexSet<ModuleSymbol>,
    pub asserts: Vec<Assert>,
}
impl SMTModule {
    #[allow(dead_code)]
    pub fn parse_model<'a>(
        &'a self,
        model: &'a str,
    ) -> impl Iterator<Item = (ModuleSymbol, ConfigValue)> + 'a {
        lazy_static! {
            static ref RE: Regex =
                Regex::new(r#"\(\s*define-fun\s+v(\d+)\s+\(\)\s+(Bool|String|Real)\s+(true|false|"[^"]*"|-?[0-9]*\.[0-9]*)\s*\)"#)
                    .unwrap();
        };
        RE.captures_iter(model).map(|i| {
            let idx: usize = i[1].parse().unwrap();
            let var = *self.variables.get_index(idx).unwrap();
            (
                var,
                match &i[2] {
                    "Bool" => ConfigValue::Bool(match &i[3] {
                        "true" => true,
                        _ => false,
                    }),
                    "Real" => ConfigValue::Number(i[3].parse().unwrap()),
                    _ => {
                        let s = &i[3];
                        ConfigValue::String(s[1..s.len() - 1].into())
                    }
                },
            )
        })
    }
    pub fn parse_values<'a>(
        &'a self,
        values: &'a str,
        module: &'a Module,
    ) -> impl Iterator<Item = (ModuleSymbol, ConfigValue)> + 'a {
        super::parse::iter_values(self, module, values)
        }
    pub fn parse_unsat_core<'a>(&'a self, core: &'a str) -> impl Iterator<Item = AssertInfo> + 'a {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"a(\d+)").unwrap();
        };
        RE.captures_iter(core).filter_map(|i| {
            let idx: usize = i[1].parse().unwrap();
            self.asserts[idx].0.clone()
        })
    }
    pub fn config_to_source(&self) -> String {
        let out = "(set-option :produce-unsat-cores true)
        (define-fun smooth_div ((x Real) (y Real)) Real(if (not (= y 0.0))(/ x y)0.0))
        (define-fun floor ((x Real)) Int (to_int x))
        (define-fun ceil ((x Real)) Int (ite (= (to_int x) x) (to_int x) (to_int (+ x 1)) ))
        (set-option :smt.core.minimize true)\n"
            .to_string();
        out
    }
    pub fn variable_to_source(&self, module: &Module) -> String {
        let mut out = "".to_string();
        for (i, v) in self.variables.iter().enumerate() {
            let ty = module.type_of(*v);
            let _ = writeln!(out, "(declare-const v{i} {ty})");
        }
        out
    }
    pub fn assert_to_source(
        &self,
        i: usize,
        info: &Option<AssertInfo>,
        expr: &Expr,
        not: bool,
    ) -> String {
        let mut out = "".to_string();
        let _ = write!(out, "(assert");
        if info.is_some() {
            let _ = write!(out, "(! ");
        }
        if not {
            let _ = write!(out, "( not ");
        }
        #[derive(Debug)]
        enum CExpr<'a> {
            Expr(&'a Expr),
            End,
        }
        let mut stack = vec![CExpr::Expr(expr)];
        while let Some(e) = stack.pop() {
            match e {
                CExpr::End => {
                    let _ = write!(out, ")");
                }
                CExpr::Expr(e) => {
                    match e {
                        Expr::Bool(b) => {
                            let _ = write!(out, " {b}");
                        }
                        Expr::Real(r) => {
                            let _ = write!(out, " {r:?}");
                        }
                        Expr::String(val) => {
                            let _ = write!(out, " \"{val}\"");
                        }
                        Expr::Var(off) => {
                            let _ = write!(out, " v{off}");
                        }
                        Expr::Add(..) => {
                            let _ = write!(out, "(+");
                        }
                        Expr::Sub(..) => {
                            let _ = write!(out, "(-");
                        }
                        Expr::Mul(..) => {
                            let _ = write!(out, "(*");
                        }
                        Expr::Div(..) => {
                            let _ = write!(out, "(smooth_div");
                        }
                        Expr::Ceil(..) => {
                            let _ = write!(out, "(ceil");
                        }
                        Expr::Floor(..) => {
                            let _ = write!(out, "(floor");
                        }
                        Expr::And(..) => {
                            let _ = write!(out, "(and");
                        }
                        Expr::Or(..) => {
                            let _ = write!(out, "(or");
                        }
                        Expr::Implies(..) => {
                            let _ = write!(out, "(=>");
                        }
                        Expr::Not(..) => {
                            let _ = write!(out, "(not");
                        }
                        Expr::Ite(..) => {
                            let _ = write!(out, "(ite");
                        }
                        Expr::Equal(..) => {
                            let _ = write!(out, "(=");
                        }
                        Expr::Greater(..) => {
                            let _ = write!(out, "(>");
                        }
                        Expr::Less(..) => {
                            let _ = write!(out, "(<");
                        }
                        Expr::Strlen(..) => {
                            let _ = write!(out, "(str.len");
                        }
                        Expr::AtLeast(min, ..) => {
                            let _ = write!(out, "((_ at-least {min})");
                        }
                        Expr::AtMost(max, ..) => {
                            let _ = write!(out, "((_ at-most {max})");
                        }
                        Expr::StrConcat(..) => {
                            let _ = write!(out, "(str.++");
                        }
                        Expr::StrLess(..) => {
                            let _ = write!(out, "(str.<");
                        }
                        Expr::StrLessEq(..) => {
                            let _ = write!(out, "(str.<=");
                        }
                    }
                    match e {
                        Expr::Add(v)
                        | Expr::Sub(v)
                        | Expr::Mul(v)
                        | Expr::Div(v)
                        | Expr::Or(v)
                        | Expr::And(v)
                        | Expr::Implies(v)
                        | Expr::AtLeast(_, v)
                        | Expr::AtMost(_, v)
                        | Expr::Greater(v)
                        | Expr::Less(v)
                        | Expr::StrLess(v)
                        | Expr::StrLessEq(v)
                        | Expr::Equal(v) => {
                            stack.push(CExpr::End);
                            for i in v.iter().rev() {
                                stack.push(CExpr::Expr(i));
                            }
                        }
                        Expr::Strlen(e) | Expr::Ceil(e) | Expr::Floor(e) | Expr::Not(e) => {
                            stack.push(CExpr::End);
                            stack.push(CExpr::Expr(e));
                        }
                        Expr::StrConcat(rhs, lhs) => {
                            stack.push(CExpr::End);
                            stack.push(CExpr::Expr(rhs));
                            stack.push(CExpr::Expr(lhs));
                        }
                        Expr::Ite(cond, lhs, rhs) => {
                            stack.push(CExpr::End);
                            stack.push(CExpr::Expr(rhs));
                            stack.push(CExpr::Expr(lhs));
                            stack.push(CExpr::Expr(cond));
                        }
                        Expr::Bool(..) | Expr::String(..) | Expr::Real(..) | Expr::Var(..) => {}
                    }
                }
            }
        }
        if not {
            let _ = write!(out, " )");
        }
        if info.is_some() {
            let _ = write!(out, " :named a{i})");
        }
        let _ = write!(out, ")\n");
        out
    }
    pub fn to_source(&self, module: &Module) -> String {
        let time = Instant::now();
        let mut out = self.config_to_source();
        let _ = writeln!(out, "{}", self.variable_to_source(module));
        for (i, Assert(info, expr)) in self.asserts.iter().enumerate() {
            let _ = writeln!(out, "{}", self.assert_to_source(i, info, expr, false));
        }
        info!("model to string  in {:?}", time.elapsed());
        out
    }
    pub fn var(&self, ms: ModuleSymbol) -> usize {
        self.variables.get_index_of(&ms).unwrap()
    }
    pub fn pseudo_bool(&self, ms: ModuleSymbol, module: &Module) -> String {
        let ms = module.resolve_value(ms);
        match module.type_of(ms) {
            Type::Bool => format!("v{}", self.var(ms)),
            Type::Real => format!("(not(= v{} 0.0))", self.var(ms)),
            Type::String => format!(r#"(not(= v{} ""))"#, self.var(ms)),
            _ => unimplemented!(),
        }
    }
}
struct SMTBuilder<'a> {
    sym2var: IndexSet<ModuleSymbol>,
    assert: Vec<Assert>,
    module: &'a Module,
}
impl<'a> SMTBuilder<'a> {
    fn var(&self, ms: ModuleSymbol) -> Expr {
        Expr::Var(
            self.sym2var
                .get_index_of(&self.module.resolve_value(ms))
                .unwrap_or(0),
        )
    }
    fn pseudo_bool(&self, ms: ModuleSymbol) -> Expr {
        let ms = self.module.resolve_value(ms);
        match self.module.type_of(ms) {
            Type::Bool => self.var(ms),
            Type::Real => Expr::Not(Expr::Equal(vec![self.var(ms), Expr::Real(0.0)]).into()),
            Type::String => {
                Expr::Not(Expr::Equal(vec![self.var(ms), Expr::String("".into())]).into())
            }
            _ => unimplemented!(),
        }
    }
    fn clause(&self, g: ModuleSymbol) -> Vec<Expr> {
        let file = self
            .module
            .instances()
            .filter(|(id, _)| id == &g.instance)
            .map(|(_, f)| f)
            .collect::<Vec<&AstDocument>>()[0];
        let mut cardinalitys: HashMap<Ustr, (Cardinality, Vec<Symbol>)> = HashMap::from([]);
        let card_expressions = self
            .module
            .file(g.instance)
            .direct_children(g.sym)
            .filter(|i| {
                if let Symbol::Feature(index) = i {
                    let feature = file.get_feature(index.clone()).unwrap();
                    match feature.cardinality {
                        Some(Cardinality::Range(_, _)) => {
                            if !cardinalitys.contains_key(&feature.name.name) {
                                cardinalitys.insert(
                                    feature.name.name,
                                    (
                                        feature.clone().cardinality.unwrap(),
                                        vec![Symbol::Feature(index.clone())],
                                    ),
                                );
                            } else {
                                cardinalitys
                                    .get_mut(&feature.name.name)
                                    .unwrap()
                                    .1
                                    .push(Symbol::Feature(index.clone()));
                            }
                            return true;
                        }
                        _ => return false,
                    }
                }
                return false;
            })
            .collect::<Vec<Symbol>>();
        let mut result = self
            .module
            .file(g.instance)
            .direct_children(g.sym)
            .filter(|s| !card_expressions.contains(s))
            .map(|i| self.pseudo_bool(g.instance.sym(i)))
            .collect::<Vec<Expr>>();
        result.append(
            cardinalitys
                .values()
                .into_iter()
                .map(|(card, syms)| {
                    let list = syms
                        .into_iter()
                        .map(|i| self.pseudo_bool(g.instance.sym(i.clone())))
                        .collect::<Vec<Expr>>();
                    match card {
                        Cardinality::Range(min, max) => Expr::And(vec![
                            Expr::AtLeast(min.clone(), list.clone()),
                            Expr::AtMost(max.clone(), list),
                        ]),
                        _ => panic!(),
                    }
                })
                .collect::<Vec<Expr>>()
                .as_mut(),
        );
        return result;
    }
    fn min_assert(&mut self, min: usize, p_bind: &Expr, g: ModuleSymbol) {
        let clause = self.clause(g);
        self.assert.push(Assert(
            Some(AssertInfo(g, AssertName::GroupMin)),
            Expr::Implies(vec![p_bind.clone(), Expr::AtLeast(min, clause)]),
        ));
    }
    fn max_assert(&mut self, max: usize, p_bind: &Expr, g: ModuleSymbol) {
        let clause = self.clause(g);
        self.assert.push(Assert(
            Some(AssertInfo(g, AssertName::GroupMax)),
            Expr::Implies(vec![p_bind.clone(), Expr::AtMost(max, clause)]),
        ));
    }
    fn push_var(&mut self, ms: ModuleSymbol) -> Expr {
        self.sym2var.insert(ms);
        Expr::Var(self.sym2var.len() - 1)
    }
}
impl Into<Expr> for ConfigValue {
    fn into(self) -> Expr {
        match self {
            Self::Bool(b) => Expr::Bool(b),
            Self::Number(n) => Expr::Real(n),
            Self::String(s) => Expr::String(s),
            Self::Cardinality(_) => Expr::Bool(true),
        }
    }
}
pub fn uvl2smt(module: &Module, config: &HashMap<ModuleSymbol, ConfigValue>) -> SMTModule {
    assert!(module.ok);
    let mut builder = SMTBuilder {
        module,
        sym2var: IndexSet::new(),
        assert: Vec::new(),
    };
    for (m, file) in module.instances() {
        for f in file.all_features() {
            builder.push_var(m.sym(f));
        }
        for sym_feature in file.all_features() {
            if let Symbol::Feature(id) = sym_feature {
                let feature = file.get_feature(id).unwrap().clone();
                if let Cardinality::Range(min, _) =
                    feature.cardinality.unwrap_or_else(|| Cardinality::Fixed)
                {
                    if feature.first_cardinality_child {
                        let mut list = vec![];
                        let parent = file.parent(sym_feature, false).unwrap();
                        for child in file.direct_children(parent) {
                            if let Symbol::Feature(sibling_id) = child {
                                let sibling = file.get_feature(sibling_id);
                                if sibling.unwrap().name.name.as_str() == feature.name.name.as_str()
                                {
                                    list.push(builder.var(m.sym(Symbol::Feature(sibling_id))));
                                }
                            }
                        }
                        match file.parent(Symbol::Feature(id), false) {
                            Some(Symbol::Group(_)) => {
                                builder.assert.push(Assert(
                                    Some(AssertInfo(m.sym(sym_feature), AssertName::GroupMin)),
                                    Expr::Or(vec![
                                        Expr::AtLeast(min, list.clone()),
                                        Expr::AtMost(0, list),
                                    ]),
                                ));
                            }
                            _ => {
                                builder.assert.push(Assert(
                                    Some(AssertInfo(m.sym(sym_feature), AssertName::GroupMin)),
                                    Expr::AtLeast(min, list),
                                ));
                            }
                        }
                    }
                }
            }
        }
    }
    for (&ms, val) in config
        .iter()
        .filter(|i| matches!(i.0.sym, Symbol::Feature(..)))
    {
        let var = builder.var(ms);
        builder.assert.push(Assert(
            Some(AssertInfo(ms, AssertName::Config)),
            Expr::Equal(vec![var, val.clone().into()]),
        ));
    }
    for (m, file) in module.instances() {
        for f in file.all_features() {
            file.visit_named_children(f, true, |a, _| {
                if !matches!(a, Symbol::Attribute(..)) {
                    return true;
                }
                let ms = m.sym(a);
                let Some((val, n)) = config
                    .get(&ms)
                    .map(|v| (v.clone().into(), AssertName::Config))
                    .or_else(|| {
                        file.value(a)
                            .and_then(|v| match v {
                                ast::Value::Bool(x) => Some(Expr::Bool(*x)),
                                ast::Value::Number(x) => Some(Expr::Real(*x)),
                                ast::Value::String(x) => Some(Expr::String(x.clone())),
                                _ => None,
                            })
                            .map(|v| (v, AssertName::Attribute))
                    })
                else {
                    return true;
                };
                let zero = match val {
                    Expr::Bool(..) => Expr::Bool(false),
                    Expr::Real(..) => Expr::Real(0.0),
                    Expr::String(..) => Expr::String("".into()),
                    _ => unreachable!(),
                };
                let attrib_var = builder.push_var(ms);
                let feat_var = builder.pseudo_bool(m.sym(f));
                builder.assert.push(Assert(
                    Some(AssertInfo(ms, n)),
                    Expr::Equal(vec![
                        Expr::Ite(feat_var.into(), val.into(), zero.into()),
                        attrib_var,
                    ]),
                ));
                true
            });
        }
    }
    for (m, file) in module.instances() {
        for p in file.all_features() {
            for g in file
                .direct_children(p)
                .filter(|sym| matches!(sym, Symbol::Group(..)))
            {
                if file.direct_children(g).next().is_none() {
                    continue;
                }
                let p_bind = builder.pseudo_bool(m.sym(p));
                for c in file.direct_children(g) {
                    let c_bind = builder.pseudo_bool(m.sym(c));
                    builder.assert.push(Assert(
                        None,
                        Expr::Implies(vec![c_bind.into(), p_bind.clone().into()]),
                    ));
                }
                match file.group_mode(g).unwrap() {
                    GroupMode::Or => {
                        let clause = builder.clause(m.sym(g));
                        builder.assert.push(Assert(
                            Some(AssertInfo(m.sym(g), AssertName::Group)),
                            Expr::Implies(vec![p_bind.into(), Expr::Or(clause).into()]),
                        ));
                    }
                    GroupMode::Alternative => {
                        builder.min_assert(1, &p_bind, m.sym(g));
                        builder.max_assert(1, &p_bind, m.sym(g));
                    }
                    GroupMode::Mandatory => {
                        builder.clause(m.sym(g)).into_iter().for_each(|expr| {
                            info!("expr {:?}", expr);
                            builder.assert.push(Assert(
                                Some(AssertInfo(m.sym(p), AssertName::GroupMember)),
                                Expr::Equal(vec![expr.into(), p_bind.clone().into()]),
                            ))
                        });
                    }
                    GroupMode::Optional | GroupMode::Cardinality(Cardinality::Fixed) => {}
                    GroupMode::Cardinality(Cardinality::Range(min, max)) => {
                        builder.min_assert(min, &p_bind, m.sym(g));
                        builder.max_assert(max, &p_bind, m.sym(g));
                    }
                }
            }
        }
    }
    for f in module
        .file(InstanceID(0))
        .direct_children(Symbol::Root)
        .filter(|f| matches!(f, Symbol::Feature(..)))
    {
        builder.assert.push(Assert(
            Some(AssertInfo(InstanceID(0).sym(f), AssertName::RootFeature)),
            builder.pseudo_bool(InstanceID(0).sym(f)),
        ));
    }
    for (m, file) in module.instances() {
        for c in file.all_constraints() {
            let expr = translate_constraint(file.constraint(c).unwrap(), m, &mut builder, file);
            builder.assert.push(Assert(
                Some(AssertInfo(m.sym(c), AssertName::Constraint)),
                expr,
            ));
        }
    }
    SMTModule {
        variables: builder.sym2var,
        asserts: builder.assert,
    }
}
pub fn uvl2smt_constraints(module: &Module) -> SMTModule {
    assert!(module.ok);
    let mut builder = SMTBuilder {
        module,
        sym2var: IndexSet::new(),
        assert: Vec::new(),
    };
    for (m, file) in module.instances() {
        for f in file.all_features() {
            builder.push_var(m.sym(f));
        }
    }
    for (m, file) in module.instances() {
        for f in file.all_features() {
            file.visit_named_children(f, true, |a, _| {
                if !matches!(a, Symbol::Attribute(..)) {
                    return true;
                }
                let ms = m.sym(a);
                builder.push_var(ms);
                true
            });
        }
    }
    for (m, file) in module.instances() {
        for c in file.all_constraints() {
            let expr = translate_constraint(file.constraint(c).unwrap(), m, &mut builder, file);
            builder.assert.push(Assert(
                Some(AssertInfo(m.sym(c), AssertName::Constraint)),
                expr,
            ));
        }
    }
    SMTModule {
        variables: builder.sym2var,
        asserts: builder.assert,
    }
}
fn translate_constraint(
    decl: &ast::ConstraintDecl,
    m: InstanceID,
    builder: &mut SMTBuilder,
    ast: &AstDocument,
) -> Expr {
    match &decl.content {
        ast::Constraint::Ref(sym) => {
            let module_symbol: ModuleSymbol = builder.module.resolve_value(m.sym(*sym));
            let resolved_ast = builder.module.file(module_symbol.instance);
            let all_of = resolved_ast
                .find_all_of(resolved_ast.name(module_symbol.sym).unwrap())
                .into_iter()
                .map(|feature| builder.var(module_symbol.instance.sym(feature)))
                .collect::<Vec<Expr>>();
            return Expr::Or(all_of);
        }
        ast::Constraint::Not(lhs) => stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
            Expr::Not(translate_constraint(lhs, m, builder, ast).into())
        }),
        ast::Constraint::Constant(b) => Expr::Bool(*b),
        ast::Constraint::Logic { op, lhs, rhs } => {
            let lhs = stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
                translate_constraint(lhs, m, builder, ast)
            });
            let rhs = translate_constraint(rhs, m, builder, ast);
            match op {
                ast::LogicOP::Or => Expr::Or(vec![lhs, rhs]),
                ast::LogicOP::And => Expr::And(vec![lhs, rhs]),
                ast::LogicOP::Equiv => Expr::Equal(vec![lhs, rhs]),
                ast::LogicOP::Implies => Expr::Implies(vec![lhs, rhs]),
            }
        }
        ast::Constraint::Equation { op, lhs, rhs } => {
            let (lhs, lty) =
                stacker::maybe_grow(32 * 1024, 1024 * 1024, || translate_expr(lhs, m, builder));
            let (rhs, rty) = translate_expr(rhs, m, builder);
            debug_assert!(rty == lty);
            if lty == Type::String {
                match op {
                    ast::EquationOP::Equal => Expr::Equal(vec![lhs, rhs]),
                    ast::EquationOP::Greater => Expr::StrLess(vec![lhs, rhs]),
                    ast::EquationOP::Smaller => Expr::Not(Expr::StrLessEq(vec![lhs, rhs]).into()),
                }
            } else {
                match op {
                    ast::EquationOP::Equal => Expr::Equal(vec![lhs, rhs]),
                    ast::EquationOP::Greater => Expr::Greater(vec![lhs, rhs]),
                    ast::EquationOP::Smaller => Expr::Less(vec![lhs, rhs]),
                }
            }
        }
    }
}
fn translate_expr(decl: &ast::ExprDecl, m: InstanceID, builder: &mut SMTBuilder) -> (Expr, Type) {
    match &decl.content {
        ast::Expr::Number(n) => (Expr::Real(*n), Type::Real),
        ast::Expr::String(s) => (Expr::String(s.clone()), Type::String),
        ast::Expr::Ref(sym) => (
            builder.var(m.sym(*sym)),
            builder.module.type_of(m.sym(*sym)),
        ),
        ast::Expr::Len(lhs) => (
            Expr::Strlen(translate_expr(lhs, m, builder).0.into()),
            Type::Real,
        ),
        ast::Expr::Binary { lhs, rhs, op } => {
            let (lhs, lty) =
                stacker::maybe_grow(32 * 1024, 1024 * 1024, || translate_expr(lhs, m, builder));
            let (rhs, rty) = translate_expr(rhs, m, builder);
            debug_assert!(rty == lty);
            if rty == Type::String {
                debug_assert!(*op == NumericOP::Add);
                (Expr::StrConcat(rhs.into(), lhs.into()), Type::String)
            } else {
                let expr = match op {
                    ast::NumericOP::Add => Expr::Add(vec![lhs, rhs]),
                    ast::NumericOP::Sub => Expr::Sub(vec![lhs, rhs]),
                    ast::NumericOP::Mul => Expr::Mul(vec![lhs, rhs]),
                    ast::NumericOP::Div => Expr::Div(vec![lhs, rhs]),
                };
                (expr, Type::Real)
            }
        }
        ast::Expr::Aggregate { op, context, query } => {
            let mut all_attributes = Vec::new();
            let mut count_features = Vec::new();
            let tgt = context
                .map(|sym| builder.module.resolve_value(m.sym(sym)))
                .unwrap_or(m.sym(Symbol::Root));
            let tgt_file = builder.module.file(tgt.instance);
            tgt_file.visit_attributes(tgt.sym, |feature, attrib, prefix| {
                if prefix == query.names.as_slice()
                    && tgt_file.type_of(attrib).unwrap() == Type::Real
                {
                    count_features.push(Expr::Ite(
                        builder.pseudo_bool(tgt.instance.sym(feature)).into(),
                        Expr::Real(1.0).into(),
                        Expr::Real(0.0).into(),
                    ));
                    all_attributes.push(builder.var(tgt.instance.sym(attrib)));
                }
            });
            if all_attributes.is_empty() {
                (Expr::Real(0.0), Type::Real)
            } else {
                (
                    match op {
                        ast::AggregateOP::Sum => Expr::Add(all_attributes),
                        ast::AggregateOP::Avg => {
                            Expr::Div(vec![Expr::Add(all_attributes), Expr::Add(count_features)])
                        }
                    },
                    Type::Real,
                )
            }
        }
        ast::Expr::Integer { op, n } => (
            match op {
                ast::IntegerOP::Ceil => Expr::Ceil(translate_expr(n, m, builder).0.into()),
                ast::IntegerOP::Floor => Expr::Floor(translate_expr(n, m, builder).0.into()),
            },
            Type::Real,
        ),
    }
}