// Smoldot
// Copyright (C) 2023  Pierre Krieger
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

use super::super::{
    vm::ExecHint, Config, HeapPages, HostVm, HostVmPrototype, StorageProofSizeBehavior,
};
use super::with_core_version_custom_sections;

/*

The Wasm fixtures in the tests of this module have been generated by compiling variations of the
Rust code below.

Compilation options:
> rustc --crate-type cdylib -C opt-level=z --target wasm32-unknown-unknown ./foo.rs


extern "C" {
    fn ext_hashing_keccak_256_version_1(ptrsz: i64) -> i32;
}

#[no_mangle]
extern "C" fn test(_param_ptr: i32, _param_sz: i32) -> i64 {
    let slice = b"hello world";
    let ptrsz = u64::from(slice.len() as u32) << 32 | u64::from(slice.as_ptr() as usize as u32);

    let input: &[u8] = unsafe {
        core::slice::from_raw_parts(
            u32::from_ne_bytes(_param_ptr.to_ne_bytes()) as usize as *const u8,
            u32::from_ne_bytes(_param_sz.to_ne_bytes()) as usize,
        )
    };

    let outptr =
        unsafe { ext_hashing_keccak_256_version_1(i64::from_ne_bytes(ptrsz.to_ne_bytes())) };
    let outsz = 32;
    let out: &[u8] = unsafe {
        core::slice::from_raw_parts(
            u32::from_ne_bytes(outptr.to_ne_bytes()) as usize as *const u8,
            outsz as usize,
        )
    };

    if out != input {
        core::arch::wasm32::unreachable()
    }

    0
}

*/

macro_rules! gen_test {
    ($host_fn:ident, $host_fn_out_sz:expr, $expected_out:expr) => {
        // TODO: also generate tests for bad host function input

        #[test]
        fn $host_fn() {
            /*
            Source code: (with the host function substituted)

                extern "C" {
                    fn ext_hashing_keccak_256_version_1(ptrsz: i64) -> i32;
                }

                #[no_mangle]
                extern "C" fn test(_param_ptr: i32, _param_sz: i32) -> i64 {
                    let slice = b"hello world";
                    let ptrsz = u64::from(slice.len() as u32) << 32 | u64::from(slice.as_ptr() as usize as u32);

                    let input: &[u8] = unsafe {
                        core::slice::from_raw_parts(
                            u32::from_ne_bytes(_param_ptr.to_ne_bytes()) as usize as *const u8,
                            u32::from_ne_bytes(_param_sz.to_ne_bytes()) as usize,
                        )
                    };

                    let outptr =
                        unsafe { ext_hashing_keccak_256_version_1(i64::from_ne_bytes(ptrsz.to_ne_bytes())) };
                    let outsz = 32;
                    let out: &[u8] = unsafe {
                        core::slice::from_raw_parts(
                            u32::from_ne_bytes(outptr.to_ne_bytes()) as usize as *const u8,
                            outsz as usize,
                        )
                    };

                    if out != input {
                        core::arch::wasm32::unreachable()
                    }

                    0
            } */
            let module_bytes = with_core_version_custom_sections(
                wat::parse_str(concat!(
                    r#"
                    (module
                        (type (;0;) (func (param i64) (result i32)))
                        (type (;1;) (func (param i32 i32) (result i64)))
                        (type (;2;) (func (param i32 i32 i32 i32) (result i32)))
                        (type (;3;) (func (param i32 i32 i32) (result i32)))
                        (import "env" ""#,
                    stringify!($host_fn),
                    r#""(func (;0;) (type 0)))
                        (func (;1;) (type 1) (param i32 i32) (result i64)
                          block  ;; label = @1
                            i32.const 1048576
                            i64.extend_i32_u
                            i64.const 47244640256
                            i64.or
                            call 0
                            i32.const "#,
                    $host_fn_out_sz,
                    r#"
                            local.get 0
                            local.get 1
                            call 2
                            br_if 0 (;@1;)
                            i64.const 0
                            return
                          end
                          unreachable
                          unreachable)
                        (func (;2;) (type 2) (param i32 i32 i32 i32) (result i32)
                          (local i32)
                          i32.const 1
                          local.set 4
                          block  ;; label = @1
                            local.get 1
                            local.get 3
                            i32.ne
                            br_if 0 (;@1;)
                            local.get 0
                            local.get 2
                            local.get 1
                            call 4
                            i32.const 0
                            i32.ne
                            local.set 4
                          end
                          local.get 4)
                        (func (;3;) (type 3) (param i32 i32 i32) (result i32)
                          (local i32 i32 i32)
                          i32.const 0
                          local.set 3
                          block  ;; label = @1
                            local.get 2
                            i32.eqz
                            br_if 0 (;@1;)
                            block  ;; label = @2
                              loop  ;; label = @3
                                local.get 0
                                i32.load8_u
                                local.tee 4
                                local.get 1
                                i32.load8_u
                                local.tee 5
                                i32.ne
                                br_if 1 (;@2;)
                                local.get 0
                                i32.const 1
                                i32.add
                                local.set 0
                                local.get 1
                                i32.const 1
                                i32.add
                                local.set 1
                                local.get 2
                                i32.const -1
                                i32.add
                                local.tee 2
                                i32.eqz
                                br_if 2 (;@1;)
                                br 0 (;@3;)
                              end
                            end
                            local.get 4
                            local.get 5
                            i32.sub
                            local.set 3
                          end
                          local.get 3)
                        (func (;4;) (type 3) (param i32 i32 i32) (result i32)
                          local.get 0
                          local.get 1
                          local.get 2
                          call 3)
                        (table (;0;) 1 1 funcref)
                        (memory (;0;) 17)
                        (global (;0;) (mut i32) (i32.const 1048576))
                        (global (;1;) i32 (i32.const 1048587))
                        (global (;2;) i32 (i32.const 1048592))
                        (export "memory" (memory 0))
                        (export "test" (func 1))
                        (export "__data_end" (global 1))
                        (export "__heap_base" (global 2))
                        (data (;0;) (i32.const 1048576) "hello world"))
            "#,
                ))
                .unwrap(),
            );

            for exec_hint in ExecHint::available_engines() {
                let proto = HostVmPrototype::new(Config {
                    allow_unresolved_imports: false,
                    exec_hint,
                    heap_pages: HeapPages::new(1024),
                    module: &module_bytes,
                })
                .unwrap();

                let mut vm = HostVm::from(
                    proto
                        .run(
                            "test",
                            StorageProofSizeBehavior::proof_recording_disabled(),
                            &$expected_out,
                        )
                        .unwrap(),
                );
                loop {
                    match vm {
                        HostVm::ReadyToRun(r) => vm = r.run(),
                        HostVm::Finished(v) => {
                            assert_eq!(v.value().as_ref(), b"");
                            break;
                        }
                        _ => unreachable!(),
                    }
                }
            }
        }
    };
}

gen_test!(
    ext_hashing_keccak_256_version_1,
    32,
    [
        0x47, 0x17, 0x32, 0x85, 0xa8, 0xd7, 0x34, 0x1e, 0x5e, 0x97, 0x2f, 0xc6, 0x77, 0x28, 0x63,
        0x84, 0xf8, 0x02, 0xf8, 0xef, 0x42, 0xa5, 0xec, 0x5f, 0x03, 0xbb, 0xfa, 0x25, 0x4c, 0xb0,
        0x1f, 0xad,
    ]
);

gen_test!(
    ext_hashing_keccak_512_version_1,
    64,
    [
        0x3e, 0xe2, 0xb4, 0x00, 0x47, 0xb8, 0x06, 0x0f, 0x68, 0xc6, 0x72, 0x42, 0x17, 0x56, 0x60,
        0xf4, 0x17, 0x4d, 0x0a, 0xf5, 0xc0, 0x1d, 0x47, 0x16, 0x8e, 0xc2, 0x0e, 0xd6, 0x19, 0xb0,
        0xb7, 0xc4, 0x21, 0x81, 0xf4, 0x0a, 0xa1, 0x04, 0x6f, 0x39, 0xe2, 0xef, 0x9e, 0xfc, 0x69,
        0x10, 0x78, 0x2a, 0x99, 0x8e, 0x00, 0x13, 0xd1, 0x72, 0x45, 0x89, 0x57, 0x95, 0x7f, 0xac,
        0x94, 0x05, 0xb6, 0x7d
    ]
);

gen_test!(
    ext_hashing_sha2_256_version_1,
    32,
    [
        0xb9, 0x4d, 0x27, 0xb9, 0x93, 0x4d, 0x3e, 0x08, 0xa5, 0x2e, 0x52, 0xd7, 0xda, 0x7d, 0xab,
        0xfa, 0xc4, 0x84, 0xef, 0xe3, 0x7a, 0x53, 0x80, 0xee, 0x90, 0x88, 0xf7, 0xac, 0xe2, 0xef,
        0xcd, 0xe9
    ]
);

gen_test!(
    ext_hashing_blake2_256_version_1,
    32,
    [
        0x25, 0x6c, 0x83, 0xb2, 0x97, 0x11, 0x4d, 0x20, 0x1b, 0x30, 0x17, 0x9f, 0x3f, 0x0e, 0xf0,
        0xca, 0xce, 0x97, 0x83, 0x62, 0x2d, 0xa5, 0x97, 0x43, 0x26, 0xb4, 0x36, 0x17, 0x8a, 0xee,
        0xf6, 0x10
    ]
);

gen_test!(
    ext_hashing_twox_64_version_1,
    8,
    [104, 105, 30, 178, 52, 103, 171, 69]
);

gen_test!(
    ext_hashing_twox_128_version_1,
    16,
    [104, 105, 30, 178, 52, 103, 171, 69, 199, 183, 31, 36, 197, 3, 27, 176]
);

gen_test!(
    ext_hashing_twox_256_version_1,
    32,
    [
        104, 105, 30, 178, 52, 103, 171, 69, 199, 183, 31, 36, 197, 3, 27, 176, 98, 176, 136, 150,
        36, 146, 130, 146, 10, 183, 18, 206, 20, 156, 91, 136
    ]
);
