mod arithmetic {
    use std::rc::Rc;

    use crate::{environment, node::{
        add::Add, constant::{Constant, ConstantValue}, divide::Divide, multiply::Multiply, subtract::Subtract, Node, NodeEnum
    }};
    use environment::Environment;

    use test_case::test_case;

    #[test_case(69.0, 420.0, 489.0 ; "when both are positive")]
    #[test_case(-2.0,  -4.0, -6.0  ; "when both are negative")]
    #[test_case(0.0,  0.0, 0.0  ; "when both are zero")]
    #[test_case(ConstantValue::INFINITY,  0.0, ConstantValue::INFINITY  ; "infinity")]
    // #[test_case(ConstantValue::NAN,  0.0, ConstantValue::NAN  ; "NaN")] // cant test NaN because NaN != NaN
    fn addition(a: ConstantValue, b: ConstantValue, e: ConstantValue) {
        let mut env = Environment::new();

        let a = Rc::new(Constant::new(a).into());
        let b = Rc::new(Constant::new(b).into());
        let d: NodeEnum = Add::new(a, b).into();

        let value = Rc::<NodeEnum>::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap();

        assert!(
            value == Constant::new(e).into(),
            "Expected {} got {}",
            e,
            value
        )
    }

    #[test_case(69.0, 420.0, -351.0 ; "when both are positive")]
    #[test_case(-2.0,  -4.0, 2.0  ; "when both are negative")]
    #[test_case(0.0,  0.0, 0.0  ; "when both are zero")]
    #[test_case(ConstantValue::INFINITY,  0.0, ConstantValue::INFINITY  ; "infinity")]
    // #[test_case(ConstantValue::NAN,  0.0, ConstantValue::NAN  ; "NaN")] // cant test NaN because NaN != NaN
    fn subtraction(aa: ConstantValue, bb: ConstantValue, e: ConstantValue) {
        let mut env = Environment::new();

        let a = Rc::new(Constant::new(0.0).into());
        let b = Rc::new(Constant::new(aa).into());
        let c = Rc::new(Constant::new(bb).into());
        let d: NodeEnum = Subtract::new(Rc::new(Add::new(a, b).into()), c).into();

        let value = Rc::<NodeEnum>::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap();

        assert!(
            value == Constant::new(e).into(),
            "Expected {} got {}",
            e,
            value
        )
    }

    #[test_case(5.0, 10.0, 50.0 ; "when both are positive")]
    #[test_case(-5.0, 10.0, -50.0 ; "when left is negative")]
    #[test_case(5.0, -10.0, -50.0 ; "when right is negative")]
    #[test_case(-5.0, -10.0, 50.0 ; "when both are negative")]
    #[test_case(2734589235234.23, 0.0, 0.0 ; "when 0 is involved")]
    fn multiplication(a: ConstantValue, b: ConstantValue, e: ConstantValue) {
        let mut env = Environment::new();

        let a = Rc::new(Constant::new(a).into());
        let b = Rc::new(Constant::new(b).into());
        let d: NodeEnum = Multiply::new(a, b).into();

        let value = Rc::<NodeEnum>::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap();

        assert!(
            value == Constant::new(e).into(),
            "Expected {} got {}",
            e,
            value
        )
    }

    #[test_case(5.0, 10.0, 0.5 ; "when both are positive")]
    #[test_case(-5.0, 10.0, -0.5 ; "when left is negative")]
    #[test_case(5.0, -10.0, -0.5 ; "when right is negative")]
    #[test_case(-5.0, -10.0, 0.5 ; "when both are negative")]
    fn division(a: ConstantValue, b: ConstantValue, e: ConstantValue) {
        let mut env = Environment::new();

        let a = Rc::new(Constant::new(a).into());
        let b = Rc::new(Constant::new(b).into());
        let d: NodeEnum = Divide::new(a, b).into();

        let value = Rc::<NodeEnum>::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap();

        assert!(
            value == Constant::new(e).into(),
            "Expected {} got {}",
            e,
            value
        )
    }
}

mod functions {
    #[test]
    fn stringification() {
        // let f = Function
    }
}

mod expected_errors {
    use test_case::test_case;

    use crate::node::constant::ConstantValue;

    #[test_case(5.0, 0.0 ; "divide by zero")]
    fn division(a: ConstantValue, b: ConstantValue) {
        let _ = a+b;
    }
}

mod misc {
    use test_case::test_case;

    use crate::node::constant::ConstantValue;

    #[test_case(30, '+', 60, '-', 20, "(30+60)-20" ; "add and subtract")]
    fn convert_to_string(
        a: impl Into<ConstantValue>,
        op1: char,
        b: impl Into<ConstantValue>,
        op2: char,
        c: impl Into<ConstantValue>,
        e: &'static str,
    ) {
    }
}
