use anyhow::bail;
use hamelin_lib::func::defs::{
NumericDivide, NumericMinus, NumericModulo, NumericMultiply, NumericPlus,
};
use crate::null_propagate;
use crate::registry::EvalRegistry;
use crate::reverse_eval::domain::Constraint;
use crate::value::DecimalValue;
use crate::value::Value;
pub fn register(registry: &mut EvalRegistry) {
registry.register_eval::<NumericPlus>(|mut params| {
let left = null_propagate!(params.take()?);
let right = null_propagate!(params.take()?);
match (left, right) {
(Value::Int(l), Value::Int(r)) => Ok(Value::Int(l + r)),
(Value::Double(l), Value::Double(r)) => Ok(Value::Double(l + r)),
(Value::Int(l), Value::Double(r)) => Ok(Value::Double(l as f64 + r)),
(Value::Double(l), Value::Int(r)) => Ok(Value::Double(l + r as f64)),
(Value::Decimal(l), Value::Decimal(r)) => Ok(Value::Decimal(l.add(&r))),
(Value::Decimal(l), Value::Int(r)) => Ok(Value::Decimal(l.add(&r.into()))),
(Value::Int(l), Value::Decimal(r)) => Ok(Value::Decimal(DecimalValue::from(l).add(&r))),
(Value::Decimal(l), Value::Double(r)) => Ok(Value::Double(f64::from(l) + r)),
(Value::Double(l), Value::Decimal(r)) => Ok(Value::Double(l + f64::from(r))),
_ => bail!("Invalid operands for + operator"),
}
});
registry.register_reverse::<NumericPlus>(|output_constraint, params| {
let constant_val = params.take_first_defined()?;
output_constraint.map(|output_val| match (output_val, &constant_val) {
(Value::Int(out), Value::Int(c)) => Ok(Value::Int(out - c)),
(Value::Double(out), Value::Double(c)) => Ok(Value::Double(out - c)),
(Value::Int(out), Value::Double(c)) => Ok(Value::Double(*out as f64 - c)),
(Value::Double(out), Value::Int(c)) => Ok(Value::Double(out - *c as f64)),
(Value::Decimal(out), Value::Decimal(c)) => Ok(Value::Decimal(out.sub(c))),
(Value::Decimal(out), Value::Int(c)) => Ok(Value::Decimal(out.sub(&(*c).into()))),
(Value::Int(out), Value::Decimal(c)) => {
Ok(Value::Decimal(DecimalValue::from(*out).sub(c)))
}
(Value::Decimal(out), Value::Double(c)) => Ok(Value::Double(f64::from(out) - c)),
(Value::Double(out), Value::Decimal(c)) => Ok(Value::Double(out - f64::from(c))),
_ => bail!("Invalid operands for reverse addition"),
})
});
registry.register_eval::<NumericMinus>(|mut params| {
let left = null_propagate!(params.take()?);
let right = null_propagate!(params.take()?);
match (left, right) {
(Value::Int(l), Value::Int(r)) => Ok(Value::Int(l - r)),
(Value::Double(l), Value::Double(r)) => Ok(Value::Double(l - r)),
(Value::Int(l), Value::Double(r)) => Ok(Value::Double(l as f64 - r)),
(Value::Double(l), Value::Int(r)) => Ok(Value::Double(l - r as f64)),
(Value::Decimal(l), Value::Decimal(r)) => Ok(Value::Decimal(l.sub(&r))),
(Value::Decimal(l), Value::Int(r)) => Ok(Value::Decimal(l.sub(&r.into()))),
(Value::Int(l), Value::Decimal(r)) => Ok(Value::Decimal(DecimalValue::from(l).sub(&r))),
(Value::Decimal(l), Value::Double(r)) => Ok(Value::Double(f64::from(l) - r)),
(Value::Double(l), Value::Decimal(r)) => Ok(Value::Double(l - f64::from(r))),
_ => bail!("Invalid operands for - operator"),
}
});
registry.register_reverse::<NumericMinus>(|output_constraint, params| {
let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
let right = params
.get_by_name("right")
.ok()
.and_then(|opt| opt.as_ref());
match (left, right) {
(None, Some(right_val)) => {
output_constraint.map(|output_val| match (output_val, right_val) {
(Value::Int(out), Value::Int(r)) => Ok(Value::Int(out + r)),
(Value::Double(out), Value::Double(r)) => Ok(Value::Double(out + r)),
(Value::Int(out), Value::Double(r)) => Ok(Value::Double(*out as f64 + r)),
(Value::Double(out), Value::Int(r)) => Ok(Value::Double(out + *r as f64)),
(Value::Decimal(out), Value::Decimal(r)) => Ok(Value::Decimal(out.add(&r))),
(Value::Decimal(out), Value::Int(r)) => {
Ok(Value::Decimal(out.add(&(*r).into())))
}
(Value::Int(out), Value::Decimal(r)) => {
Ok(Value::Decimal(DecimalValue::from(*out).add(r)))
}
(Value::Decimal(out), Value::Double(r)) => {
Ok(Value::Double(f64::from(out) + r))
}
(Value::Double(out), Value::Decimal(r)) => {
Ok(Value::Double(out + f64::from(r)))
}
_ => bail!("Invalid operands for reverse subtraction"),
})
}
(Some(left_val), None) => {
output_constraint.map(|output_val| match (left_val, output_val) {
(Value::Int(l), Value::Int(out)) => Ok(Value::Int(l - out)),
(Value::Double(l), Value::Double(out)) => Ok(Value::Double(l - out)),
(Value::Int(l), Value::Double(out)) => Ok(Value::Double(*l as f64 - out)),
(Value::Double(l), Value::Int(out)) => Ok(Value::Double(l - *out as f64)),
(Value::Decimal(l), Value::Decimal(out)) => Ok(Value::Decimal(l.sub(&out))),
(Value::Decimal(l), Value::Int(out)) => {
Ok(Value::Decimal(l.sub(&(*out).into())))
}
(Value::Int(l), Value::Decimal(out)) => {
Ok(Value::Decimal(DecimalValue::from(*l).sub(out)))
}
(Value::Decimal(l), Value::Double(out)) => {
Ok(Value::Double(f64::from(l) - out))
}
(Value::Double(l), Value::Decimal(out)) => {
Ok(Value::Double(l - f64::from(out)))
}
_ => bail!("Invalid operands for reverse subtraction"),
})
}
_ => bail!("Invalid reverse evaluation: expected exactly one variable"),
}
});
registry.register_eval::<NumericMultiply>(|mut params| {
let left = null_propagate!(params.take()?);
let right = null_propagate!(params.take()?);
match (left, right) {
(Value::Int(l), Value::Int(r)) => Ok(Value::Int(l * r)),
(Value::Double(l), Value::Double(r)) => Ok(Value::Double(l * r)),
(Value::Int(l), Value::Double(r)) => Ok(Value::Double(l as f64 * r)),
(Value::Double(l), Value::Int(r)) => Ok(Value::Double(l * r as f64)),
(Value::Decimal(l), Value::Decimal(r)) => Ok(Value::Decimal(l.mul(&r))),
(Value::Decimal(l), Value::Int(r)) => Ok(Value::Decimal(l.mul(&r.into()))),
(Value::Int(l), Value::Decimal(r)) => Ok(Value::Decimal(DecimalValue::from(l).mul(&r))),
(Value::Decimal(l), Value::Double(r)) => Ok(Value::Double(f64::from(l) * r)),
(Value::Double(l), Value::Decimal(r)) => Ok(Value::Double(l * f64::from(r))),
_ => bail!("Invalid operands for * operator"),
}
});
registry.register_reverse::<NumericMultiply>(|output_constraint, params| {
let constant_val = params.take_first_defined()?;
output_constraint.map(|output_val| match (output_val, &constant_val) {
(Value::Int(out), Value::Int(c)) => {
if *c == 0 {
bail!("Division by zero in reverse multiplication")
}
Ok(Value::Int(out / c))
}
(Value::Double(out), Value::Double(c)) => {
if *c == 0.0 {
bail!("Division by zero in reverse multiplication")
}
Ok(Value::Double(out / c))
}
(Value::Int(out), Value::Double(c)) => {
if *c == 0.0 {
bail!("Division by zero in reverse multiplication")
}
Ok(Value::Double(*out as f64 / c))
}
(Value::Double(out), Value::Int(c)) => {
if *c == 0 {
bail!("Division by zero in reverse multiplication")
}
Ok(Value::Double(out / *c as f64))
}
(Value::Decimal(out), Value::Decimal(c)) => Ok(Value::Decimal(out.div(&c)?)),
(Value::Decimal(out), Value::Int(c)) => {
if *c == 0 {
bail!("Division by zero in reverse multiplication")
}
Ok(Value::Decimal(out.div(&(*c).into())?))
}
(Value::Int(out), Value::Decimal(c)) => {
Ok(Value::Decimal(DecimalValue::from(*out).div(&c)?))
}
(Value::Decimal(out), Value::Double(c)) => {
if *c == 0.0 {
bail!("Division by zero in reverse multiplication")
}
Ok(Value::Double(f64::from(out) / c))
}
(Value::Double(out), Value::Decimal(c)) => {
let c_double: f64 = f64::from(c);
if c_double == 0.0 {
bail!("Division by zero in reverse multiplication")
}
Ok(Value::Double(out / c_double))
}
_ => bail!("Invalid operands for reverse multiplication"),
})
});
registry.register_eval::<NumericDivide>(|mut params| {
let left = null_propagate!(params.take()?);
let right = null_propagate!(params.take()?);
match (left, right) {
(Value::Int(l), Value::Int(r)) => {
if r == 0 {
bail!("Division by zero")
} else {
Ok(Value::Int(l / r))
}
}
(Value::Double(l), Value::Double(r)) => {
if r == 0.0 {
bail!("Division by zero")
} else {
Ok(Value::Double(l / r))
}
}
(Value::Int(l), Value::Double(r)) => {
if r == 0.0 {
bail!("Division by zero")
} else {
Ok(Value::Double(l as f64 / r))
}
}
(Value::Double(l), Value::Int(r)) => {
if r == 0 {
bail!("Division by zero")
} else {
Ok(Value::Double(l / r as f64))
}
}
(Value::Decimal(l), Value::Decimal(r)) => Ok(Value::Decimal(l.div(&r)?)),
(Value::Decimal(l), Value::Int(r)) => {
if r == 0 {
bail!("Division by zero")
}
Ok(Value::Decimal(l.div(&r.into())?))
}
(Value::Int(l), Value::Decimal(r)) => {
Ok(Value::Decimal(DecimalValue::from(l).div(&r)?))
}
(Value::Decimal(l), Value::Double(r)) => {
if r == 0.0 {
bail!("Division by zero")
}
Ok(Value::Double(f64::from(l) / r))
}
(Value::Double(l), Value::Decimal(r)) => {
let r_double: f64 = f64::from(r);
if r_double == 0.0 {
bail!("Division by zero")
}
Ok(Value::Double(l / r_double))
}
_ => bail!("Invalid operands for / operator"),
}
});
registry.register_reverse::<NumericDivide>(|output_constraint, params| {
let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
let right = params
.get_by_name("right")
.ok()
.and_then(|opt| opt.as_ref());
match (left, right) {
(None, Some(right_val)) => {
let is_zero = match right_val {
Value::Int(r) => *r == 0,
Value::Double(r) => *r == 0.0,
Value::Decimal(r) => r.unscaled == 0,
_ => false,
};
if is_zero {
return Ok(Constraint::Empty);
}
output_constraint.map(|output_val| match (output_val, right_val) {
(Value::Int(out), Value::Int(r)) => Ok(Value::Int(out * r)),
(Value::Double(out), Value::Double(r)) => Ok(Value::Double(out * r)),
(Value::Int(out), Value::Double(r)) => Ok(Value::Double(*out as f64 * r)),
(Value::Double(out), Value::Int(r)) => Ok(Value::Double(out * *r as f64)),
(Value::Decimal(out), Value::Decimal(r)) => Ok(Value::Decimal(out.mul(&r))),
(Value::Decimal(out), Value::Int(r)) => {
Ok(Value::Decimal(out.mul(&(*r).into())))
}
(Value::Int(out), Value::Decimal(r)) => {
Ok(Value::Decimal(DecimalValue::from(*out).mul(r)))
}
(Value::Decimal(out), Value::Double(r)) => {
Ok(Value::Double(f64::from(out) * r))
}
(Value::Double(out), Value::Decimal(r)) => {
Ok(Value::Double(out * f64::from(r)))
}
_ => bail!("Invalid operands for reverse division"),
})
}
(Some(left_val), None) => {
output_constraint.map(|output_val| match (left_val, output_val) {
(Value::Int(l), Value::Int(out)) => {
if *out == 0 {
bail!("Division by zero in reverse division")
}
Ok(Value::Int(l / out))
}
(Value::Double(l), Value::Double(out)) => {
if *out == 0.0 {
bail!("Division by zero in reverse division")
}
Ok(Value::Double(l / out))
}
(Value::Int(l), Value::Double(out)) => {
if *out == 0.0 {
bail!("Division by zero in reverse division")
}
Ok(Value::Double(*l as f64 / out))
}
(Value::Double(l), Value::Int(out)) => {
if *out == 0 {
bail!("Division by zero in reverse division")
}
Ok(Value::Double(l / *out as f64))
}
(Value::Decimal(l), Value::Decimal(out)) => Ok(Value::Decimal(l.div(&out)?)),
(Value::Decimal(l), Value::Int(out)) => {
if *out == 0 {
bail!("Division by zero in reverse division")
}
Ok(Value::Decimal(l.div(&(*out).into())?))
}
(Value::Int(l), Value::Decimal(out)) => {
Ok(Value::Decimal(DecimalValue::from(*l).div(&out)?))
}
(Value::Decimal(l), Value::Double(out)) => {
if *out == 0.0 {
bail!("Division by zero in reverse division")
}
Ok(Value::Double(f64::from(l) / out))
}
(Value::Double(l), Value::Decimal(out)) => {
let out_double: f64 = f64::from(out);
if out_double == 0.0 {
bail!("Division by zero in reverse division")
}
Ok(Value::Double(l / out_double))
}
_ => bail!("Invalid operands for reverse division"),
})
}
_ => bail!("Invalid reverse evaluation: expected exactly one variable"),
}
});
registry.register_eval::<NumericModulo>(|mut params| {
let left = null_propagate!(params.take()?);
let right = null_propagate!(params.take()?);
match (left, right) {
(Value::Int(l), Value::Int(r)) => {
if r == 0 {
bail!("Division by zero")
} else {
Ok(Value::Int(l % r))
}
}
(Value::Double(l), Value::Double(r)) => {
if r == 0.0 {
bail!("Division by zero")
} else {
Ok(Value::Double(l % r))
}
}
(Value::Int(l), Value::Double(r)) => {
if r == 0.0 {
bail!("Division by zero")
} else {
Ok(Value::Double((l as f64) % r))
}
}
(Value::Double(l), Value::Int(r)) => {
if r == 0 {
bail!("Division by zero")
} else {
Ok(Value::Double(l % (r as f64)))
}
}
_ => bail!("Invalid operands for % operator"),
}
});
}