use macro_wasmer_universal_test::universal_test;
#[cfg(feature = "js")]
use wasm_bindgen_test::*;

use wasmer::*;

#[universal_test]
fn global_new() -> Result<(), String> {
    let mut store = Store::default();
    let global = Global::new(&mut store, Value::I32(10));
    assert_eq!(
        global.ty(&store),
        GlobalType {
            ty: Type::I32,
            mutability: Mutability::Const
        }
    );

    let global_mut = Global::new_mut(&mut store, Value::I32(10));
    assert_eq!(
        global_mut.ty(&store),
        GlobalType {
            ty: Type::I32,
            mutability: Mutability::Var
        }
    );

    Ok(())
}

#[universal_test]
#[cfg_attr(
    feature = "wamr",
    ignore = "wamr does not support globals unattached to instances"
)]
fn global_get() -> Result<(), String> {
    let mut store = Store::default();

    let global_i32 = Global::new(&mut store, Value::I32(10));
    assert_eq!(global_i32.get(&mut store), Value::I32(10));

    // 64-bit values are not yet fully supported in some versions of Node
    #[cfg(feature = "sys")]
    {
        let global_i64 = Global::new(&mut store, Value::I64(20));
        assert_eq!(global_i64.get(&mut store), Value::I64(20));
    }

    let global_f32 = Global::new(&mut store, Value::F32(10.0));
    assert_eq!(global_f32.get(&mut store), Value::F32(10.0));

    // 64-bit values are not yet fully supported in some versions of Node
    #[cfg(feature = "sys")]
    {
        let global_f64 = Global::new(&mut store, Value::F64(20.0));
        assert_eq!(global_f64.get(&mut store), Value::F64(20.0));
    }

    Ok(())
}

#[universal_test]
#[cfg_attr(
    feature = "wamr",
    ignore = "wamr does not support globals unattached to instances"
)]
fn global_set() -> Result<(), String> {
    let mut store = Store::default();
    let global_i32 = Global::new(&mut store, Value::I32(10));
    // Set on a constant should error
    assert!(global_i32.set(&mut store, Value::I32(20)).is_err());

    let global_i32_mut = Global::new_mut(&mut store, Value::I32(10));
    // Set on different type should error
    assert!(global_i32_mut.set(&mut store, Value::I64(20)).is_err());

    // Set on same type should succeed
    global_i32_mut
        .set(&mut store, Value::I32(20))
        .map_err(|e| format!("{e:?}"))?;
    assert_eq!(global_i32_mut.get(&mut store), Value::I32(20));

    Ok(())
}

#[universal_test]
#[cfg_attr(feature = "wasmi", ignore = "wasmi does not support funcrefs")]
fn table_new() -> Result<(), String> {
    let mut store = Store::default();
    let table_type = TableType {
        ty: Type::FuncRef,
        minimum: 0,
        maximum: None,
    };
    let f = Function::new_typed(&mut store, || {});
    let table = Table::new(&mut store, table_type, Value::FuncRef(Some(f)))
        .map_err(|e| format!("{e:?}"))?;
    assert_eq!(table.ty(&store), table_type);

    // Anyrefs not yet supported
    // let table_type = TableType {
    //     ty: Type::ExternRef,
    //     minimum: 0,
    //     maximum: None,
    // };
    // let table = Table::new(&store, table_type, Value::ExternRef(ExternRef::Null)).map_err(|e| format!("{e:?}"))?;
    // assert_eq!(*table.ty(), table_type);

    Ok(())
}

#[universal_test]
fn table_get() -> Result<(), String> {
    // Tables are not yet fully supported in Wasm
    // This test was marked as #[ignore] on -sys, which is why it is commented out.

    //    let mut store = Store::default();
    //    let table_type = TableType {
    //        ty: Type::FuncRef,
    //        minimum: 0,
    //        maximum: Some(1),
    //    };
    //    let f = Function::new_typed(&mut store, |num: i32| num + 1);
    //    let table = Table::new(&mut store, table_type, Value::FuncRef(Some(f)))
    //        .map_err(|e| format!("{e:?}"))?;
    //    assert_eq!(table.ty(&mut store), table_type);
    //    let _elem = table.get(&mut store, 0).unwrap();
    //    assert_eq!(elem.funcref().unwrap(), f);

    Ok(())
}

#[universal_test]
fn table_set() -> Result<(), String> {
    // Table set not yet tested
    #[cfg(feature = "sys")]
    {
        let mut store = Store::default();

        let table_type = TableType {
            ty: Type::ExternRef,
            minimum: 1,
            maximum: None,
        };
        let extern_ref = ExternRef::new(&mut store, 0u32);
        let table = Table::new(
            &mut store,
            table_type,
            Value::ExternRef(Some(extern_ref.clone())),
        )
        .map_err(|e| format!("{e:?}"))?;
        assert_eq!(table.ty(&store), table_type);

        let v = table.get(&mut store, 0);
        assert!(v.is_some());

        let v = v.unwrap();

        let v = if let Value::ExternRef(Some(ext)) = v {
            ext.downcast::<u32>(&store)
        } else {
            return Err("table.get does not match `ExternRef(Some(..))`!".into());
        };

        let v = v.unwrap();
        assert_eq!(*v, 0u32);

        let extern_ref = ExternRef::new(&mut store, 1u32);
        table
            .set(&mut store, 0, Value::ExternRef(Some(extern_ref)))
            .map_err(|e| e.to_string())?;

        let v = table.get(&mut store, 0);
        assert!(v.is_some());
        let v = v.unwrap();

        let v = if let Value::ExternRef(Some(ext)) = v {
            ext.downcast::<u32>(&store)
        } else {
            return Err("table.get does not match `ExternRef(Some(..))`!".into());
        };

        assert!(v.is_some());
        let v = v.unwrap();
        assert_eq!(*v, 1u32);

        let extern_ref = ExternRef::new(&mut store, 2u32);
        table
            .set(&mut store, 0, Value::ExternRef(Some(extern_ref)))
            .map_err(|e| e.to_string())?;

        let v = table.get(&mut store, 0);
        assert!(v.is_some());
        let v = v.unwrap();

        let v = if let Value::ExternRef(Some(ext)) = v {
            ext.downcast::<u32>(&store)
        } else {
            return Err("table.get does not match `ExternRef(Some(..))`!".into());
        };

        assert!(v.is_some());
        let v = v.unwrap();
        assert_eq!(*v, 2u32);
    }

    Ok(())
}

#[universal_test]
fn table_grow() -> Result<(), String> {
    // Tables are not yet fully supported in Wasm
    #[cfg(feature = "sys")]
    {
        let mut store = Store::default();
        let table_type = TableType {
            ty: Type::FuncRef,
            minimum: 0,
            maximum: Some(10),
        };
        let f = Function::new_typed(&mut store, |num: i32| num + 1);
        let table = Table::new(&mut store, table_type, Value::FuncRef(Some(f.clone())))
            .map_err(|e| format!("{e:?}"))?;

        let old_len = table.grow(&mut store, 1, Value::FuncRef(Some(f.clone())));
        assert_eq!(0, old_len.unwrap());
        let old_len = table.grow(&mut store, 1, Value::FuncRef(Some(f.clone())));
        assert_eq!(1, old_len.unwrap());

        // Growing to a bigger maximum should return None
        let old_len = table.grow(&mut store, 12, Value::FuncRef(Some(f.clone())));
        assert!(old_len.is_err());

        let old_len = table
            .grow(&mut store, 5, Value::FuncRef(Some(f)))
            .map_err(|e| format!("{e:?}"))?;
        assert_eq!(old_len, 2);
    }

    Ok(())
}

#[universal_test]
fn table_copy() -> Result<(), String> {
    // TODO: table copy test not yet implemented
    Ok(())
}

#[universal_test]
fn memory_new() -> Result<(), String> {
    let mut store = Store::default();
    let memory_type = MemoryType {
        shared: cfg!(feature = "wamr"),
        minimum: Pages(0),
        maximum: Some(Pages(10)),
    };
    let memory = Memory::new(&mut store, memory_type).map_err(|e| format!("{e:?}"))?;
    assert_eq!(memory.view(&store).size(), Pages(0));
    assert_eq!(memory.ty(&store), memory_type);
    Ok(())
}

#[universal_test]
#[cfg_attr(
    feature = "wamr",
    ignore = "wamr does not support direct calls to grow memory"
)]
fn memory_grow() -> Result<(), String> {
    let mut store = Store::default();
    let desc = MemoryType::new(Pages(10), Some(Pages(16)), false);
    let memory = Memory::new(&mut store, desc).map_err(|e| format!("{e:?}"))?;
    assert_eq!(memory.view(&store).size(), Pages(10));

    let result = memory.grow(&mut store, Pages(2)).unwrap();
    assert_eq!(result, Pages(10));
    assert_eq!(memory.view(&store).size(), Pages(12));

    let result = memory.grow(&mut store, Pages(10));
    assert_eq!(
        result,
        Err(MemoryError::CouldNotGrow {
            current: 12.into(),
            attempted_delta: 10.into()
        })
    );

    // JS will never give BadMemory unless V8 is broken somehow
    #[cfg(feature = "sys")]
    {
        let bad_desc = MemoryType::new(Pages(15), Some(Pages(10)), false);
        let bad_result = Memory::new(&mut store, bad_desc);
        assert!(matches!(bad_result, Err(MemoryError::InvalidMemory { .. })));
    }

    Ok(())
}

#[universal_test]
fn function_new() -> Result<(), String> {
    let mut store = Store::default();
    let function = Function::new_typed(&mut store, || {});
    assert_eq!(function.ty(&store), FunctionType::new(vec![], vec![]));
    let function = Function::new_typed(&mut store, |_a: i32| {});
    assert_eq!(
        function.ty(&store),
        FunctionType::new(vec![Type::I32], vec![])
    );
    let function = Function::new_typed(&mut store, |_a: i32, _b: i64, _c: f32, _d: f64| {});
    assert_eq!(
        function.ty(&store),
        FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![])
    );
    let function = Function::new_typed(&mut store, || -> i32 { 1 });
    assert_eq!(
        function.ty(&store),
        FunctionType::new(vec![], vec![Type::I32])
    );
    let function = Function::new_typed(&mut store, || -> (i32, i64, f32, f64) { (1, 2, 3.0, 4.0) });
    assert_eq!(
        function.ty(&store),
        FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64])
    );
    Ok(())
}

#[universal_test]
fn function_new_env() -> Result<(), String> {
    let mut store = Store::default();
    #[derive(Clone)]
    struct MyEnv {}

    let my_env = MyEnv {};
    let env = FunctionEnv::new(&mut store, my_env);
    let function = Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>| {});
    assert_eq!(function.ty(&store), FunctionType::new(vec![], vec![]));
    let function =
        Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>, _a: i32| {});
    assert_eq!(
        function.ty(&store),
        FunctionType::new(vec![Type::I32], vec![])
    );
    let function = Function::new_typed_with_env(
        &mut store,
        &env,
        |_env: FunctionEnvMut<MyEnv>, _a: i32, _b: i64, _c: f32, _d: f64| {},
    );
    assert_eq!(
        function.ty(&store),
        FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![])
    );
    let function =
        Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>| -> i32 { 1 });
    assert_eq!(
        function.ty(&store),
        FunctionType::new(vec![], vec![Type::I32])
    );
    let function = Function::new_typed_with_env(
        &mut store,
        &env,
        |_env: FunctionEnvMut<MyEnv>| -> (i32, i64, f32, f64) { (1, 2, 3.0, 4.0) },
    );
    assert_eq!(
        function.ty(&store),
        FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64])
    );
    Ok(())
}

#[universal_test]
fn function_new_dynamic() -> Result<(), String> {
    let mut store = Store::default();

    // Using &FunctionType signature
    let function_type = FunctionType::new(vec![], vec![]);
    let function = Function::new(
        &mut store,
        &function_type,
        |_values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);
    let function_type = FunctionType::new(vec![Type::I32], vec![]);
    let function = Function::new(
        &mut store,
        &function_type,
        |_values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);
    let function_type = FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![]);
    let function = Function::new(
        &mut store,
        &function_type,
        |_values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);
    let function_type = FunctionType::new(vec![], vec![Type::I32]);
    let function = Function::new(
        &mut store,
        &function_type,
        |_values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);
    let function_type = FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64]);
    let function = Function::new(
        &mut store,
        &function_type,
        |_values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);

    // wasmi does not support V128 through its wasm_c_api bindings.
    #[cfg(not(any(feature = "wasmi", feature = "v8")))]
    {
        // Using array signature
        let function_type = ([Type::V128], [Type::I32, Type::F32, Type::F64]);
        let function = Function::new(
            &mut store,
            function_type,
            |_values: &[Value]| unimplemented!(),
        );
        assert_eq!(function.ty(&store).params(), [Type::V128]);
        assert_eq!(
            function.ty(&store).results(),
            [Type::I32, Type::F32, Type::F64]
        );
    }

    Ok(())
}

#[universal_test]
fn function_new_dynamic_env() -> Result<(), String> {
    let mut store = Store::default();
    #[derive(Clone)]
    struct MyEnv {}
    let my_env = MyEnv {};
    let env = FunctionEnv::new(&mut store, my_env);

    // Using &FunctionType signature
    let function_type = FunctionType::new(vec![], vec![]);
    let function = Function::new_with_env(
        &mut store,
        &env,
        &function_type,
        |_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);
    let function_type = FunctionType::new(vec![Type::I32], vec![]);
    let function = Function::new_with_env(
        &mut store,
        &env,
        &function_type,
        |_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);
    let function_type = FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![]);
    let function = Function::new_with_env(
        &mut store,
        &env,
        &function_type,
        |_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);
    let function_type = FunctionType::new(vec![], vec![Type::I32]);
    let function = Function::new_with_env(
        &mut store,
        &env,
        &function_type,
        |_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);
    let function_type = FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64]);
    let function = Function::new_with_env(
        &mut store,
        &env,
        &function_type,
        |_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
    );
    assert_eq!(function.ty(&store), function_type);

    // wasmi does not support V128 through its wasm_c_api bindings.
    #[cfg(not(any(feature = "wasmi", feature = "v8")))]
    {
        // Using array signature
        let function_type = ([Type::V128], [Type::I32, Type::F32, Type::F64]);
        let function = Function::new_with_env(
            &mut store,
            &env,
            function_type,
            |_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
        );
        assert_eq!(function.ty(&store).params(), [Type::V128]);
        assert_eq!(
            function.ty(&store).results(),
            [Type::I32, Type::F32, Type::F64]
        );
    }
    Ok(())
}
