//! 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::time::system_time_internal::{from_uefi, to_uefi};
use crate::io::{IoSlice, IoSliceMut};
use crate::ops::{Deref, DerefMut};
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,
})
.unwrap();

// 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).unwrap(), Duration::new(0, 0));
    assert!(uefi_time_cmp(t, to_uefi(&from_uefi(&t).unwrap(), -1440, 0).unwrap()));
    assert!(to_uefi(&from_uefi(&t).unwrap(), 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).unwrap(), Duration::new(1440 * SECS_IN_MINUTE, 0));
    assert!(uefi_time_cmp(t, to_uefi(&from_uefi(&t).unwrap(), 0, 0).unwrap()));
    assert!(to_uefi(&from_uefi(&t).unwrap(), -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).unwrap(), 1440, 0).is_ok());
    assert!(to_uefi(&from_uefi(&t).unwrap(), 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;
    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 - 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 - 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.deref());
    assert_eq!(data, io_slice_mut.deref_mut());

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