use ergotree_ir::mir::negation::Negation;
use ergotree_ir::mir::value::Value;

use crate::eval::env::Env;
use crate::eval::EvalContext;
use crate::eval::EvalError;
use crate::eval::Evaluable;
use num_traits::CheckedNeg;

impl Evaluable for Negation {
    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
        let input_v = self.input.eval(env, ctx)?;

        fn overflow_err<T: std::fmt::Display>(v: &T) -> EvalError {
            EvalError::ArithmeticException(format!("Overflow on Negation of value {}", *v))
        }
        fn neg<T: CheckedNeg + Into<Value> + std::fmt::Display>(v: &T) -> Result<Value, EvalError> {
            v.checked_neg()
                .map(|v| v.into())
                .ok_or_else(|| overflow_err(v))
        }
        match input_v {
            Value::Byte(v) => neg(&v),
            Value::Short(v) => neg(&v),
            Value::Int(v) => neg(&v),
            Value::Long(v) => neg(&v),
            Value::BigInt(v) => neg(&v),
            _ => Err(EvalError::UnexpectedValue(format!(
                "Expected Negation input to be numeric value, got {:?}",
                input_v
            ))),
        }
    }
}

#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod tests {

    use super::*;
    use crate::eval::tests::try_eval_out_wo_ctx;
    use ergotree_ir::bigint256::BigInt256;
    use ergotree_ir::mir::constant::Constant;
    use ergotree_ir::mir::constant::TryExtractFrom;
    use ergotree_ir::mir::expr::Expr;
    use ergotree_ir::mir::unary_op::OneArgOpTryBuild;
    use num_traits::{Bounded, Num};

    fn try_run_eval<T: Num + Into<Constant> + TryExtractFrom<Value>>(
        input: T,
    ) -> Result<T, EvalError> {
        let expr: Expr = Negation::try_build(Expr::Const(input.into()))
            .unwrap()
            .into();
        try_eval_out_wo_ctx::<T>(&expr)
    }
    fn run_eval<T: Num + Into<Constant> + TryExtractFrom<Value>>(input: T) -> T {
        try_run_eval(input).unwrap()
    }

    #[test]
    fn eval() {
        assert_eq!(run_eval(1i8), -1i8);
        assert!(try_run_eval(i8::MIN).is_err());
        assert_eq!(run_eval(1i16), -1i16);
        assert!(try_run_eval(i16::MIN).is_err());
        assert_eq!(run_eval(1i32), -1i32);
        assert!(try_run_eval(i32::MIN).is_err());
        assert_eq!(run_eval(1i64), -1i64);
        assert!(try_run_eval(i64::MIN).is_err());
        assert_eq!(run_eval(BigInt256::from(1i64)), BigInt256::from(-1i64));
        assert!(try_run_eval(BigInt256::min_value()).is_err());
    }
}
