1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};

use super::{NSCopying, NSObject, NSString};
use crate::rc::{DefaultId, Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType, Encode, Encoding, RefEncode};

extern_class!(
    /// A universally unique value.
    ///
    /// Can be used to identify types, interfaces, and other items.
    ///
    /// Conversion methods to/from UUIDs from the `uuid` crate can be
    /// enabled with the `uuid` crate feature.
    ///
    /// macOS: This is only available on 10.8 and above.
    ///
    /// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsuuid?language=objc).
    #[derive(PartialEq, Eq, Hash)]
    pub struct NSUUID;

    unsafe impl ClassType for NSUUID {
        type Super = NSObject;
    }
);

/// The headers describe `initWithUUIDBytes:` and `getUUIDBytes:` as
/// taking `uuid_t`, but something fishy is going on, in reality they
/// expect a reference to these!
///
/// Hence we create this newtype to change the encoding.
#[repr(transparent)]
struct UuidBytes([u8; 16]);

unsafe impl RefEncode for UuidBytes {
    const ENCODING_REF: Encoding = Encoding::Array(16, &u8::ENCODING);
}

// SAFETY: `NSUUID` is immutable.
unsafe impl Sync for NSUUID {}
unsafe impl Send for NSUUID {}

impl UnwindSafe for NSUUID {}
impl RefUnwindSafe for NSUUID {}

extern_methods!(
    unsafe impl NSUUID {
        pub fn new_v4() -> Id<Self, Shared> {
            unsafe { msg_send_id![Self::class(), new] }
        }

        /// The 'nil UUID'.
        pub fn nil() -> Id<Self, Shared> {
            Self::from_bytes([0; 16])
        }

        pub fn from_bytes(bytes: [u8; 16]) -> Id<Self, Shared> {
            let bytes = UuidBytes(bytes);
            unsafe {
                let obj = msg_send_id![Self::class(), alloc];
                msg_send_id![obj, initWithUUIDBytes: &bytes]
            }
        }

        pub fn from_string(string: &NSString) -> Option<Id<Self, Shared>> {
            unsafe {
                let obj = msg_send_id![Self::class(), alloc];
                msg_send_id![obj, initWithUUIDString: string]
            }
        }

        #[sel(getUUIDBytes:)]
        fn get_bytes_raw(&self, bytes: &mut UuidBytes);

        pub fn as_bytes(&self) -> [u8; 16] {
            let mut bytes = UuidBytes([0; 16]);
            self.get_bytes_raw(&mut bytes);
            bytes.0
        }

        pub fn string(&self) -> Id<NSString, Shared> {
            unsafe { msg_send_id![self, UUIDString] }
        }
    }
);

impl fmt::Display for NSUUID {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.string(), f)
    }
}

impl fmt::Debug for NSUUID {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // The `uuid` crate does `Debug` and `Display` equally, and so do we
        fmt::Display::fmt(&self.string(), f)
    }
}

// UUID `compare:` is broken for some reason?

// impl PartialOrd for NSUUID {
//     #[inline]
//     fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
//         Some(self.cmp(other))
//     }
// }

// impl Ord for NSUUID {
//     fn cmp(&self, other: &Self) -> cmp::Ordering {
//         let res: NSComparisonResult = unsafe { msg_send![self, compare: other] };
//         res.into()
//     }
// }

/// Conversion methods to/from `uuid` crate.
#[cfg(feature = "uuid")]
impl NSUUID {
    pub fn from_uuid(uuid: uuid::Uuid) -> Id<Self, Shared> {
        Self::from_bytes(uuid.into_bytes())
    }

    pub fn as_uuid(&self) -> uuid::Uuid {
        uuid::Uuid::from_bytes(self.as_bytes())
    }
}

impl DefaultId for NSUUID {
    type Ownership = Shared;
    fn default_id() -> Id<Self, Self::Ownership> {
        Self::nil()
    }
}

unsafe impl NSCopying for NSUUID {
    type Ownership = Shared;
    type Output = NSUUID;
}

impl alloc::borrow::ToOwned for NSUUID {
    type Owned = Id<NSUUID, Shared>;
    fn to_owned(&self) -> Self::Owned {
        self.copy()
    }
}

#[cfg(test)]
mod tests {
    use alloc::format;

    use super::*;

    #[test]
    fn test_new() {
        let uuid1 = NSUUID::new_v4();
        let uuid2 = NSUUID::new_v4();
        assert_ne!(uuid1, uuid2, "Statistically very unlikely");
    }

    #[test]
    fn test_bytes() {
        let uuid = NSUUID::from_bytes([10; 16]);
        assert_eq!(uuid.as_bytes(), [10; 16]);
    }

    #[test]
    fn display_debug() {
        let uuid = NSUUID::from_bytes([10; 16]);
        let expected = "0A0A0A0A-0A0A-0A0A-0A0A-0A0A0A0A0A0A";
        assert_eq!(format!("{}", uuid), expected);
        assert_eq!(format!("{:?}", uuid), expected);
    }

    // #[test]
    // fn test_compare() {
    //     let uuid1 = NSUUID::from_bytes([10; 16]);
    //     let uuid2 = NSUUID::from_bytes([9; 16]);
    //     assert!(uuid1 > uuid2);
    // }

    #[cfg(feature = "uuid")]
    #[test]
    fn test_convert_roundtrip() {
        let nsuuid1 = NSUUID::new_v4();
        let uuid = nsuuid1.as_uuid();
        let nsuuid2 = NSUUID::from_uuid(uuid);
        assert_eq!(nsuuid1, nsuuid2);
    }
}