//! Tests for the `Func` type in `wasmi`.

use core::slice;

use assert_matches::assert_matches;
use wasmi::{errors::FuncError, Engine, Error, Func, FuncType, Store, Value};
use wasmi_core::{ValueType, F32, F64};

fn test_setup() -> Store<()> {
    let engine = Engine::default();
    Store::new(&engine, ())
}

/// Asserts that `lhs` and `rhs` tuples are equal.
///
/// We need to define the following macro for the comparison of tuples with
/// 16 elements since `PartialEq` and `Debug` do not seem to be implemented
/// for tuples of that size.
macro_rules! assert_eq_tuple {
    ( $lhs:ident, $rhs:ident; $($n:tt),* $(,)? ) => {
        $(
            assert_eq!($lhs.$n, $rhs.$n);
        )*
    }
}

// Returns a Wasm store and two binary addition [`Func`] instances.
fn setup_add2() -> (Store<()>, Func, Func) {
    let mut store = test_setup();
    let add2 = Func::wrap(&mut store, |lhs: i32, rhs: i32| lhs + rhs);
    let add2_dyn = Func::new(
        &mut store,
        FuncType::new([ValueType::I32, ValueType::I32], [ValueType::I32]),
        |_caller, inputs: &[Value], results: &mut [Value]| {
            assert_eq!(inputs.len(), 2);
            assert_eq!(results.len(), 1);
            let lhs = &inputs[0].i32().unwrap();
            let rhs = &inputs[1].i32().unwrap();
            results[0] = (lhs + rhs).into();
            Ok(())
        },
    );
    (store, add2, add2_dyn)
}

#[test]
fn dynamic_add2_works() {
    let (mut store, add2, add2_dyn) = setup_add2();
    for a in 0..10 {
        for b in 0..10 {
            let params = [Value::I32(a), Value::I32(b)];
            let expected = a + b;
            let mut result = Value::I32(0);
            // Call to Func with statically typed closure.
            add2.call(&mut store, &params, slice::from_mut(&mut result))
                .unwrap();
            // Reset result before execution.
            result = Value::I32(0);
            // Call to Func with dynamically typed closure.
            add2_dyn
                .call(&mut store, &params, slice::from_mut(&mut result))
                .unwrap();
            assert_eq!(result.i32(), Some(expected));
        }
    }
}

#[test]
fn static_add2_works() {
    let (mut store, add2, add2_dyn) = setup_add2();
    let add2 = add2.typed::<(i32, i32), i32>(&mut store).unwrap();
    let add2_dyn = add2_dyn.typed::<(i32, i32), i32>(&mut store).unwrap();
    for a in 0..10 {
        for b in 0..10 {
            let expected = a + b;
            assert_eq!(add2.call(&mut store, (a, b)).unwrap(), expected);
            assert_eq!(add2_dyn.call(&mut store, (a, b)).unwrap(), expected);
        }
    }
}

// Returns a Wasm store and two three-way addition [`Func`] instances.
fn setup_add3() -> (Store<()>, Func, Func) {
    let mut store = test_setup();
    let add3 = Func::wrap(&mut store, |v0: i32, v1: i32, v2: i32| v0 + v1 + v2);
    let add3_dyn = Func::new(
        &mut store,
        FuncType::new(
            [ValueType::I32, ValueType::I32, ValueType::I32],
            [ValueType::I32],
        ),
        |_caller, inputs: &[Value], results: &mut [Value]| {
            assert_eq!(inputs.len(), 3);
            assert_eq!(results.len(), 1);
            let a = &inputs[0].i32().unwrap();
            let b = &inputs[1].i32().unwrap();
            let c = &inputs[2].i32().unwrap();
            results[0] = (a + b + c).into();
            Ok(())
        },
    );
    (store, add3, add3_dyn)
}

#[test]
fn dynamic_add3_works() {
    let (mut store, add3, add3_dyn) = setup_add3();
    for a in 0..5 {
        for b in 0..5 {
            for c in 0..5 {
                let params = [Value::I32(a), Value::I32(b), Value::I32(c)];
                let expected = a + b + c;
                let mut result = Value::I32(0);
                // Call to Func with statically typed closure.
                add3.call(&mut store, &params, slice::from_mut(&mut result))
                    .unwrap();
                assert_eq!(result.i32(), Some(expected));
                // Reset result before execution.
                result = Value::I32(0);
                // Call to Func with dynamically typed closure.
                add3_dyn
                    .call(&mut store, &params, slice::from_mut(&mut result))
                    .unwrap();
                assert_eq!(result.i32(), Some(expected));
            }
        }
    }
}

#[test]
fn static_add3_works() {
    let (mut store, add3, add3_dyn) = setup_add3();
    let add3 = add3.typed::<(i32, i32, i32), i32>(&mut store).unwrap();
    let add3_dyn = add3_dyn.typed::<(i32, i32, i32), i32>(&mut store).unwrap();
    for a in 0..5 {
        for b in 0..5 {
            for c in 0..5 {
                let expected = a + b + c;
                assert_eq!(add3.call(&mut store, (a, b, c)).unwrap(), expected);
                assert_eq!(add3_dyn.call(&mut store, (a, b, c)).unwrap(), expected);
            }
        }
    }
}

// Returns a `Store` and two Wasm host functions that duplicate their inputs.
fn setup_duplicate() -> (Store<()>, Func, Func) {
    let mut store = test_setup();
    let duplicate = Func::wrap(&mut store, |value: i32| (value, value));
    let duplicate_dyn = Func::new(
        &mut store,
        FuncType::new([ValueType::I32], [ValueType::I32, ValueType::I32]),
        |_caller, inputs: &[Value], results: &mut [Value]| {
            assert_eq!(inputs.len(), 1);
            assert_eq!(results.len(), 2);
            let input = inputs[0].i32().unwrap();
            results[0] = input.into();
            results[1] = input.into();
            Ok(())
        },
    );
    (store, duplicate, duplicate_dyn)
}

#[test]
fn dynamic_duplicate_works() {
    let (mut store, duplicate, duplicate_dyn) = setup_duplicate();
    for input in 0..10 {
        let params = [Value::I32(input)];
        let expected = [Value::I32(input), Value::I32(input)];
        let mut results = [Value::I32(0), Value::I32(0)];
        // Call to Func with statically typed closure.
        duplicate.call(&mut store, &params, &mut results).unwrap();
        assert_eq!(results[0].i32(), expected[0].i32());
        assert_eq!(results[1].i32(), expected[1].i32());
        // Reset result before execution.
        results = [Value::I32(0), Value::I32(0)];
        // Call to Func with dynamically typed closure.
        duplicate_dyn
            .call(&mut store, &params, &mut results)
            .unwrap();
        assert_eq!(results[0].i32(), expected[0].i32());
        assert_eq!(results[1].i32(), expected[1].i32());
    }
}

#[test]
fn static_duplicate_works() {
    let (mut store, duplicate, duplicate_dyn) = setup_duplicate();
    let duplicate = duplicate.typed::<i32, (i32, i32)>(&mut store).unwrap();
    let duplicate_dyn = duplicate_dyn.typed::<i32, (i32, i32)>(&mut store).unwrap();
    for input in 0..10 {
        assert_eq!(duplicate.call(&mut store, input).unwrap(), (input, input));
        assert_eq!(
            duplicate_dyn.call(&mut store, input).unwrap(),
            (input, input)
        );
    }
}

fn setup_many_params() -> (Store<()>, Func) {
    let mut store = test_setup();
    // Function taking 16 arguments (maximum) and doing nothing.
    let func = Func::wrap(
        &mut store,
        |_0: i32,
         _1: i32,
         _2: i32,
         _3: i32,
         _4: i32,
         _5: i32,
         _6: i32,
         _7: i32,
         _8: i32,
         _9: i32,
         _10: i32,
         _11: i32,
         _12: i32,
         _13: i32,
         _14: i32,
         _15: i32| (),
    );
    (store, func)
}

type I32x16 = (
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
    i32,
);

/// Returns a `(i32, ...)` tuple with 16 elements that have ascending values.
///
/// This is required as input or output of many of the following tests.
fn ascending_tuple() -> I32x16 {
    (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
}

#[test]
fn dynamic_many_params_works() {
    let (mut store, func) = setup_many_params();
    func.call(
        &mut store,
        &[
            Value::I32(0),
            Value::I32(1),
            Value::I32(2),
            Value::I32(3),
            Value::I32(4),
            Value::I32(5),
            Value::I32(6),
            Value::I32(7),
            Value::I32(8),
            Value::I32(9),
            Value::I32(10),
            Value::I32(11),
            Value::I32(12),
            Value::I32(13),
            Value::I32(14),
            Value::I32(15),
        ],
        &mut [],
    )
    .unwrap();
}

#[test]
fn static_many_params_works() {
    let (mut store, func) = setup_many_params();
    let typed_func = func.typed::<I32x16, ()>(&mut store).unwrap();
    let inputs = ascending_tuple();
    let result = typed_func.call(&mut store, inputs);
    assert_matches!(result, Ok(()));
}

fn setup_many_results() -> (Store<()>, Func) {
    let mut store = test_setup();
    // Function taking 16 arguments (maximum) and doing nothing.
    let func = Func::wrap(&mut store, ascending_tuple);
    (store, func)
}

#[test]
fn dynamic_many_results_works() {
    let (mut store, func) = setup_many_results();
    let mut results = [0; 16].map(Value::I32);
    func.call(&mut store, &[], &mut results).unwrap();
    let mut i = 0;
    let expected = [0; 16].map(|_| {
        let value = Value::I32(i as _);
        i += 1;
        value
    });
    assert_eq!(
        results.map(|result| result.i32().unwrap()),
        expected.map(|expected| expected.i32().unwrap())
    )
}

#[test]
fn static_many_results_works() {
    let (mut store, func) = setup_many_results();
    let typed_func = func.typed::<(), I32x16>(&mut store).unwrap();
    let result = typed_func.call(&mut store, ()).unwrap();
    let expected = ascending_tuple();
    assert_eq_tuple!(result, expected; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
}

fn setup_many_params_many_results() -> (Store<()>, Func) {
    let mut store = test_setup();
    // Function taking 16 arguments (maximum) and doing nothing.
    let func = Func::wrap(
        &mut store,
        |v0: i32,
         v1: i32,
         v2: i32,
         v3: i32,
         v4: i32,
         v5: i32,
         v6: i32,
         v7: i32,
         v8: i32,
         v9: i32,
         v10: i32,
         v11: i32,
         v12: i32,
         v13: i32,
         v14: i32,
         v15: i32| {
            (
                v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15,
            )
        },
    );
    (store, func)
}

#[test]
fn dynamic_many_params_many_results_works() {
    let (mut store, func) = setup_many_params_many_results();
    let mut results = [0; 16].map(Value::I32);
    let inputs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map(Value::I32);
    func.call(&mut store, &inputs, &mut results).unwrap();
    assert_eq!(
        results.map(|result| result.i32().unwrap()),
        inputs.map(|input| input.i32().unwrap()),
    )
}

#[test]
fn static_many_params_many_results_works() {
    let (mut store, func) = setup_many_params_many_results();
    let typed_func = func.typed::<I32x16, I32x16>(&mut store).unwrap();
    let inputs = ascending_tuple();
    let result = typed_func.call(&mut store, inputs).unwrap();
    assert_eq_tuple!(result, inputs; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
}

#[test]
fn dynamic_many_types_works() {
    let mut store = test_setup();
    // Function taking no arguments and returning 16 results as tuple (maximum).
    let func = Func::wrap(
        &mut store,
        |v0: i32, v1: u32, v2: i64, v3: u64, v4: F32, v5: F64| (v0, v1, v2, v3, v4, v5),
    );
    let mut results = [0; 6].map(Value::I32);
    let inputs = [
        Value::I32(0),
        Value::I32(1),
        Value::I64(2),
        Value::I64(3),
        Value::F32(4.0.into()),
        Value::F64(5.0.into()),
    ];
    func.call(&mut store, &inputs, &mut results).unwrap();
    assert_eq!(results[0].i32(), Some(0));
    assert_eq!(results[1].i32(), Some(1));
    assert_eq!(results[2].i64(), Some(2));
    assert_eq!(results[3].i64(), Some(3));
    assert_eq!(results[4].f32(), Some(4.0.into()));
    assert_eq!(results[5].f64(), Some(5.0.into()));
}

#[test]
fn static_many_types_works() {
    let mut store = test_setup();
    // Function taking no arguments and returning 16 results as tuple (maximum).
    let func = Func::wrap(
        &mut store,
        |v0: i32, v1: u32, v2: i64, v3: u64, v4: F32, v5: F64| (v0, v1, v2, v3, v4, v5),
    );
    let typed_func = func
        .typed::<(i32, u32, i64, u64, F32, F64), (i32, u32, i64, u64, F32, F64)>(&mut store)
        .unwrap();
    let inputs = (0, 1, 2, 3, 4.0.into(), 5.0.into());
    let result = typed_func.call(&mut store, inputs).unwrap();
    assert_eq!(result, inputs);
}

#[test]
fn dynamic_type_check_works() {
    let mut store = test_setup();
    let identity = Func::wrap(&mut store, |value: i32| value);
    let mut result = Value::I32(0);
    // Case: Too few inputs given to function.
    assert_matches!(
        identity.call(&mut store, &[], core::slice::from_mut(&mut result)),
        Err(Error::Func(FuncError::MismatchingParameterLen))
    );
    // Case: Too many inputs given to function.
    assert_matches!(
        identity.call(
            &mut store,
            &[Value::I32(0), Value::I32(1)],
            core::slice::from_mut(&mut result)
        ),
        Err(Error::Func(FuncError::MismatchingParameterLen))
    );
    // Case: Too few outputs given to function.
    assert_matches!(
        identity.call(&mut store, &[Value::I32(0)], &mut [],),
        Err(Error::Func(FuncError::MismatchingResultLen))
    );
    // Case: Too many outputs given to function.
    assert_matches!(
        identity.call(
            &mut store,
            &[Value::I32(0)],
            &mut [Value::I32(0), Value::I32(1)],
        ),
        Err(Error::Func(FuncError::MismatchingResultLen))
    );
    // Case: Mismatching type given as input to function.
    for input in &[
        Value::I64(0),
        Value::F32(0.0.into()),
        Value::F64(0.0.into()),
    ] {
        assert_matches!(
            identity.call(
                &mut store,
                core::slice::from_ref(input),
                core::slice::from_mut(&mut result)
            ),
            Err(Error::Func(FuncError::MismatchingParameterType))
        );
    }
    // Case: Allow for incorrect result type.
    //
    // The result type will be overwritten anyways.
    assert_matches!(
        identity.call(&mut store, &[Value::I32(0)], &mut [Value::I64(0)]),
        Ok(_)
    );
}

#[test]
fn static_type_check_works() {
    let mut store = test_setup();
    let identity = Func::wrap(&mut store, |value: i32| value);
    // Case: Too few inputs given to function.
    assert_matches!(
        identity.typed::<(), i32>(&mut store),
        Err(Error::Func(FuncError::MismatchingParameterLen))
    );
    // Case: Too many inputs given to function.
    assert_matches!(
        identity.typed::<(i32, i32), i32>(&mut store),
        Err(Error::Func(FuncError::MismatchingParameterLen))
    );
    // Case: Too few results given to function.
    assert_matches!(
        identity.typed::<i32, ()>(&mut store),
        Err(Error::Func(FuncError::MismatchingResultLen))
    );
    // Case: Too many results given to function.
    assert_matches!(
        identity.typed::<i32, (i32, i32)>(&mut store),
        Err(Error::Func(FuncError::MismatchingResultLen))
    );
    // Case: Mismatching type given as input to function.
    assert_matches!(
        identity.typed::<i64, i32>(&mut store),
        Err(Error::Func(FuncError::MismatchingParameterType))
    );
    // Case: Mismatching type given as output of function.
    assert_matches!(
        identity.typed::<i32, i64>(&mut store),
        Err(Error::Func(FuncError::MismatchingResultType))
    );
}
