//! These tests are not run automatically right now. Please run these tests manually by copying them
//! to a separate project when modifying any related code.

use super::alloc::*;
use super::time::system_time_internal::{from_uefi, to_uefi};
use crate::io::{IoSlice, IoSliceMut};
use crate::time::Duration;

const SECS_IN_MINUTE: u64 = 60;

const MAX_UEFI_TIME: Duration = from_uefi(r_efi::efi::Time {
    year: 9999,
    month: 12,
    day: 31,
    hour: 23,
    minute: 59,
    second: 59,
    nanosecond: 999_999_999,
    timezone: 1440,
    daylight: 0,
    pad1: 0,
    pad2: 0,
});

#[test]
fn align() {
    // UEFI ABI specifies that allocation alignment minimum is always 8. So this can be
    // statically verified.
    assert_eq!(POOL_ALIGNMENT, 8);

    // Loop over allocation-request sizes from 0-256 and alignments from 1-128, and verify
    // that in case of overalignment there is at least space for one additional pointer to
    // store in the allocation.
    for i in 0..256 {
        for j in &[1, 2, 4, 8, 16, 32, 64, 128] {
            if *j <= 8 {
                assert_eq!(align_size(i, *j), i);
            } else {
                assert!(align_size(i, *j) > i + size_of::<*mut ()>());
            }
        }
    }
}

// UEFI Time cannot implement Eq due to uninitilaized pad1 and pad2
fn uefi_time_cmp(t1: r_efi::efi::Time, t2: r_efi::efi::Time) -> bool {
    t1.year == t2.year
        && t1.month == t2.month
        && t1.day == t2.day
        && t1.hour == t2.hour
        && t1.minute == t2.minute
        && t1.second == t2.second
        && t1.nanosecond == t2.nanosecond
        && t1.timezone == t2.timezone
        && t1.daylight == t2.daylight
}

#[test]
fn systemtime_start() {
    let t = r_efi::efi::Time {
        year: 1900,
        month: 1,
        day: 1,
        hour: 0,
        minute: 0,
        second: 0,
        pad1: 0,
        nanosecond: 0,
        timezone: -1440,
        daylight: 0,
        pad2: 0,
    };
    assert_eq!(from_uefi(&t), Duration::new(0, 0));
    assert!(uefi_time_cmp(t, to_uefi(&from_uefi(&t), -1440, 0).unwrap()));
    assert!(to_uefi(&from_uefi(&t), 0, 0).is_err());
}

#[test]
fn systemtime_utc_start() {
    let t = r_efi::efi::Time {
        year: 1900,
        month: 1,
        day: 1,
        hour: 0,
        minute: 0,
        second: 0,
        pad1: 0,
        nanosecond: 0,
        timezone: 0,
        daylight: 0,
        pad2: 0,
    };
    assert_eq!(from_uefi(&t), Duration::new(1440 * SECS_IN_MINUTE, 0));
    assert!(uefi_time_cmp(t, to_uefi(&from_uefi(&t), 0, 0).unwrap()));
    assert!(to_uefi(&from_uefi(&t), -1440, 0).is_ok());
}

#[test]
fn systemtime_end() {
    let t = r_efi::efi::Time {
        year: 9999,
        month: 12,
        day: 31,
        hour: 23,
        minute: 59,
        second: 59,
        pad1: 0,
        nanosecond: 0,
        timezone: 1440,
        daylight: 0,
        pad2: 0,
    };
    assert!(to_uefi(&from_uefi(&t), 1440, 0).is_ok());
    assert!(to_uefi(&from_uefi(&t), 1439, 0).is_err());
}

#[test]
fn min_time() {
    let inp = Duration::from_secs(1440 * SECS_IN_MINUTE);
    let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
    assert_eq!(new_tz, 0);
    assert!(to_uefi(&inp, new_tz, 0).is_ok());

    let inp = Duration::from_secs(1450 * SECS_IN_MINUTE);
    let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
    assert_eq!(new_tz, 10);
    assert!(to_uefi(&inp, new_tz, 0).is_ok());

    let inp = Duration::from_secs(1450 * SECS_IN_MINUTE + 10);
    let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
    assert_eq!(new_tz, 10);
    assert!(to_uefi(&inp, new_tz, 0).is_ok());

    let inp = Duration::from_secs(1430 * SECS_IN_MINUTE);
    let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
    assert_eq!(new_tz, -10);
    assert!(to_uefi(&inp, new_tz, 0).is_ok());
}

#[test]
fn max_time() {
    let inp = MAX_UEFI_TIME.0;
    let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
    assert_eq!(new_tz, 1440);
    assert!(to_uefi(&inp, new_tz, 0).is_ok());

    let inp = MAX_UEFI_TIME.0 - Duration::from_secs(1440 * SECS_IN_MINUTE);
    let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
    assert_eq!(new_tz, 0);
    assert!(to_uefi(&inp, new_tz, 0).is_ok());

    let inp = MAX_UEFI_TIME.0 - Duration::from_secs(1440 * SECS_IN_MINUTE + 10);
    let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
    assert_eq!(new_tz, 0);
    assert!(to_uefi(&inp, new_tz, 0).is_ok());
}

// UEFI IoSlice and IoSliceMut Tests
//
// Strictly speaking, vectored read/write types for UDP4, UDP6, TCP4, TCP6 are defined
// separately in the UEFI Spec. However, they have the same signature. These tests just ensure
// that `IoSlice` and `IoSliceMut` are compatible with the vectored types for all the
// networking protocols.

unsafe fn to_slice<T>(val: &T) -> &[u8] {
    let len = size_of_val(val);
    unsafe { crate::slice::from_raw_parts(crate::ptr::from_ref(val).cast(), len) }
}

#[test]
fn io_slice_single() {
    let mut data = [0, 1, 2, 3, 4];

    let tcp4_frag = r_efi::protocols::tcp4::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let tcp6_frag = r_efi::protocols::tcp6::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let udp4_frag = r_efi::protocols::udp4::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let udp6_frag = r_efi::protocols::udp6::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let io_slice = IoSlice::new(&data);

    unsafe {
        assert_eq!(to_slice(&io_slice), to_slice(&tcp4_frag));
        assert_eq!(to_slice(&io_slice), to_slice(&tcp6_frag));
        assert_eq!(to_slice(&io_slice), to_slice(&udp4_frag));
        assert_eq!(to_slice(&io_slice), to_slice(&udp6_frag));
    }
}

#[test]
fn io_slice_mut_single() {
    let mut data = [0, 1, 2, 3, 4];

    let tcp4_frag = r_efi::protocols::tcp4::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let tcp6_frag = r_efi::protocols::tcp6::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let udp4_frag = r_efi::protocols::udp4::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let udp6_frag = r_efi::protocols::udp6::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let io_slice_mut = IoSliceMut::new(&mut data);

    unsafe {
        assert_eq!(to_slice(&io_slice_mut), to_slice(&tcp4_frag));
        assert_eq!(to_slice(&io_slice_mut), to_slice(&tcp6_frag));
        assert_eq!(to_slice(&io_slice_mut), to_slice(&udp4_frag));
        assert_eq!(to_slice(&io_slice_mut), to_slice(&udp6_frag));
    }
}

#[test]
fn io_slice_multi() {
    let mut data = [0, 1, 2, 3, 4];

    let tcp4_frag = r_efi::protocols::tcp4::FragmentData {
        fragment_length: data.len().try_into().unwrap(),
        fragment_buffer: data.as_mut_ptr().cast(),
    };
    let rhs =
        [tcp4_frag.clone(), tcp4_frag.clone(), tcp4_frag.clone(), tcp4_frag.clone(), tcp4_frag];
    let lhs = [
        IoSlice::new(&data),
        IoSlice::new(&data),
        IoSlice::new(&data),
        IoSlice::new(&data),
        IoSlice::new(&data),
    ];

    unsafe {
        assert_eq!(to_slice(&lhs), to_slice(&rhs));
    }
}

#[test]
fn io_slice_basic() {
    let data = [0, 1, 2, 3, 4];
    let mut io_slice = IoSlice::new(&data);

    assert_eq!(data, io_slice.as_slice());
    io_slice.advance(2);
    assert_eq!(&data[2..], io_slice.as_slice());
}

#[test]
fn io_slice_mut_basic() {
    let data = [0, 1, 2, 3, 4];
    let mut data_clone = [0, 1, 2, 3, 4];
    let mut io_slice_mut = IoSliceMut::new(&mut data_clone);

    assert_eq!(data, io_slice_mut.as_slice());
    assert_eq!(data, io_slice_mut.as_mut_slice());

    io_slice_mut.advance(2);
    assert_eq!(&data[2..], io_slice_mut.into_slice());
}
