//! Miri tests that exercise the unsafe pointer cast at runtime.
//! Run with: cargo +nightly miri test --test miri

// -- #[cast] tests -----------------------------------------------------------
//
// Structs are placed in an inner module so that field access from the test
// goes through Deref, exercising the unsafe pointer cast under Miri.

mod cast_all {
    use read_only::cast;

    #[cast]
    pub struct FullReadOnly {
        pub a: i32,
        pub b: i32,
    }

    impl FullReadOnly {
        pub fn new(a: i32, b: i32) -> Self {
            Self { a, b }
        }
    }
}

#[test]
fn cast_all_fields_read_only_accessible() {
    let v = cast_all::FullReadOnly::new(10, 20);
    assert_eq!(v.a, 10);
    assert_eq!(v.b, 20);
}

mod cast_selective {
    use read_only::cast;

    #[cast]
    pub struct SelectiveReadOnly {
        #[readonly]
        pub id: u64,
        pub mutable: i32,
    }

    impl SelectiveReadOnly {
        pub fn new(id: u64, mutable: i32) -> Self {
            Self { id, mutable }
        }

        #[allow(dead_code)]
        pub fn mutate(&mut self) {
            self.id += 1;
            self.mutable += 1;
        }
    }
}

#[test]
fn cast_selective_readonly_fields() {
    let mut v = cast_selective::SelectiveReadOnly::new(42, 0);
    assert_eq!(v.id, 42);
    assert_eq!(v.mutable, 0);
    v.mutable = 1;
    assert_eq!(v.mutable, 1);
}

mod cast_string {
    use read_only::cast;

    #[cast]
    pub struct WithString {
        #[readonly]
        pub name: String,
        pub count: usize,
    }

    impl WithString {
        pub fn new(name: &str, count: usize) -> Self {
            Self {
                name: name.to_string(),
                count,
            }
        }
    }
}

#[test]
fn cast_with_heap_allocated_types() {
    let v = cast_string::WithString::new("hello", 3);
    assert_eq!(v.name, "hello");
    assert_eq!(v.count, 3);
}

mod cast_vec {
    use read_only::cast;

    #[cast]
    pub struct WithVec {
        pub items: Vec<i32>,
    }

    impl WithVec {
        pub fn new(items: Vec<i32>) -> Self {
            Self { items }
        }
    }
}

#[test]
fn cast_with_vec() {
    let v = cast_vec::WithVec::new(vec![1, 2, 3]);
    assert_eq!(v.items.len(), 3);
    assert_eq!(v.items[1], 2);
}

mod cast_multi_readonly {
    use read_only::cast;

    #[cast]
    pub struct WithMultipleReadonly {
        #[readonly]
        pub x: i32,
        #[readonly]
        pub y: String,
        pub z: f64,
    }

    impl WithMultipleReadonly {
        pub fn new(x: i32, y: &str, z: f64) -> Self {
            Self {
                x,
                y: y.to_string(),
                z,
            }
        }
    }
}

#[test]
fn cast_multiple_readonly_fields() {
    let v = cast_multi_readonly::WithMultipleReadonly::new(5, "test", std::f64::consts::PI);
    assert_eq!(v.x, 5);
    assert_eq!(v.y, "test");
    assert!((v.z - std::f64::consts::PI).abs() < 1e-10);
}

mod cast_generics {
    use read_only::cast;

    #[cast]
    pub struct WithGenerics<T> {
        pub value: T,
    }

    impl<T> WithGenerics<T> {
        pub fn new(value: T) -> Self {
            Self { value }
        }
    }
}

#[test]
fn cast_with_generics() {
    let v = cast_generics::WithGenerics::new("hello");
    assert_eq!(v.value, "hello");
}

// -- #[embed] tests -----------------------------------------------------------
//
// For #[embed] the ReadOnly* struct IS publicly accessible.

mod embed_device {
    use read_only::embed;

    #[embed]
    pub struct Device {
        #[readonly]
        pub handle: u64,
        pub mutable: i32,
    }

    impl Device {
        pub fn new(handle: u64, mutable: i32) -> Self {
            Self {
                read_only: ReadOnlyDevice { handle },
                mutable,
            }
        }
    }
}

#[test]
fn embed_readonly_accessible_via_deref() {
    let v = embed_device::Device::new(99, 7);
    assert_eq!(v.handle, 99);
    assert_eq!(v.mutable, 7);
}

#[test]
fn embed_mutable_field_writable() {
    let mut v = embed_device::Device::new(99, 7);
    v.mutable = 42;
    assert_eq!(v.mutable, 42);
}

#[test]
fn embed_deref_target_matches() {
    let v = embed_device::Device::new(1, 2);
    let ro: &embed_device::ReadOnlyDevice = &v;
    assert_eq!(ro.handle, 1);
}

mod embed_generics {
    use read_only::embed;

    #[embed]
    pub struct WithGenerics<T: Copy> {
        #[readonly]
        pub value: T,
        #[allow(dead_code)]
        pub extra: i32,
    }

    impl<T: Copy> WithGenerics<T> {
        pub fn new(value: T, extra: i32) -> Self {
            Self {
                read_only: ReadOnlyWithGenerics { value },
                extra,
            }
        }
    }
}

#[test]
fn embed_with_generics() {
    let v = embed_generics::WithGenerics::new(42u64, 0);
    assert_eq!(v.value, 42u64);
}
