// SPDX-License-Identifier: Apache-2.0

use super::*;
use crate::guest::alloc::{Alloc, Allocator, Collect, Commit, Committer};
use crate::guest::call::kind;
use crate::guest::syscall::types::SockaddrOutput;
use crate::guest::Call;
use crate::item::{self, syscall};
use crate::NULL;

use core::mem::size_of;
use libc::{socklen_t, AF_INET};

fn assert_call<'a, K: kind::Kind, T: Call<'a, K>, const N: usize>(
    call: T,
    committed: [usize; N],
    collect: [usize; N],
    collected: T::Collected,
) where
    T::Collected: core::fmt::Debug + core::cmp::PartialEq,
{
    let mut buf = [0usize; N];
    let buf_ptr = &mut buf as *mut [usize; N];

    let mut alloc = Alloc::new(&mut buf).stage();
    let call = call.stage(&mut alloc).unwrap();

    let alloc = alloc.commit();
    let call = call.commit(&alloc);
    assert_eq!(unsafe { buf_ptr.read() }, committed);
    unsafe { buf_ptr.write(collect) };

    let alloc = alloc.collect();
    assert_eq!(call.collect(&alloc), collected);
}

#[test]
fn exit() {
    assert_call(
        Exit { status: 2 },
        [
            syscall::USIZE_COUNT * size_of::<usize>(),
            item::Kind::Syscall as _,
            libc::SYS_exit as _,
            2,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            -libc::ENOSYS as _,
            0,
        ],
        [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0],
        Ok(()),
    )
}

#[test]
fn recv() {
    let sockfd = 42;
    let flags = 0;
    let mut buf = [42, 42];
    let buf_len = buf.len();
    assert_call(
        Recv {
            sockfd,
            buf: &mut buf,
            flags,
        },
        [
            syscall::USIZE_COUNT * size_of::<usize>() + size_of::<usize>(),
            item::Kind::Syscall as _,
            libc::SYS_recvfrom as _,
            sockfd as _,
            0,
            buf_len,
            flags as _,
            NULL,
            NULL,
            -libc::ENOSYS as _,
            0,
            0,
        ],
        [
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 2, 0, 0xedfe,
        ],
        Some(Ok(2)),
    );
    assert_eq!(buf, [0xfe, 0xed]);
}

#[test]
fn recvfrom() {
    let sockfd = 42;
    let flags = 0;
    let mut buf = [42, 42];
    let buf_len = buf.len();
    let mut src_addr = [42, 42, 42, 42, 42, 42, 42];
    let mut addrlen = src_addr.len() as _;
    assert_call(
        Recvfrom {
            sockfd,
            buf: &mut buf,
            flags,
            src_addr: SockaddrOutput::new(&mut src_addr, &mut addrlen),
        },
        [
            syscall::USIZE_COUNT * size_of::<usize>() + 2 * size_of::<usize>(),
            item::Kind::Syscall as _,
            libc::SYS_recvfrom as _,
            sockfd as _,
            size_of::<usize>() + size_of::<socklen_t>(),
            buf_len,
            flags as _,
            0,
            size_of::<usize>(),
            -libc::ENOSYS as _,
            0,
            0,
            7,
        ],
        [
            0xff,
            0xff,
            0xff,
            0xff,
            0xff,
            0xff,
            0xff,
            0xff,
            0xff,
            2,
            0,
            0x5040302010000 | AF_INET as usize,
            0xedfe00000042,
        ],
        Some(Ok(2)),
    );
    assert_eq!(src_addr, [AF_INET as _, 0, 0x01, 0x02, 0x03, 0x04, 0x05]);
    assert_eq!(addrlen, 0x42);
    assert_eq!(buf, [0xfe, 0xed]);
}
