use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Index, IndexMut, Range};
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{
    NSCopying, NSEnumerator, NSFastEnumeration, NSFastEnumerator, NSMutableArray, NSMutableCopying,
    NSObject, NSRange,
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::runtime::{Class, Object};
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
__inner_extern_class!(
    #[derive(PartialEq, Eq, Hash)]
    pub struct NSArray<T: Message, O: Ownership = Shared> {
        item: PhantomData<Id<T, O>>,
        notunwindsafe: PhantomData<&'static mut ()>,
    }
    unsafe impl<T: Message, O: Ownership> ClassType for NSArray<T, O> {
        type Super = NSObject;
    }
);
unsafe impl<T: Message + Sync + Send> Sync for NSArray<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSArray<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSArray<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSArray<T, Owned> {}
impl<T: Message + RefUnwindSafe, O: Ownership> RefUnwindSafe for NSArray<T, O> {}
impl<T: Message + RefUnwindSafe> UnwindSafe for NSArray<T, Shared> {}
impl<T: Message + UnwindSafe> UnwindSafe for NSArray<T, Owned> {}
#[track_caller]
pub(crate) unsafe fn with_objects<T: Message + ?Sized, R: Message, O: Ownership>(
    cls: &Class,
    objects: &[&T],
) -> Id<R, O> {
    unsafe {
        msg_send_id![
            msg_send_id![cls, alloc],
            initWithObjects: objects.as_ptr(),
            count: objects.len(),
        ]
    }
}
extern_methods!(
    unsafe impl<T: Message, O: Ownership> NSArray<T, O> {
        pub fn new() -> Id<Self, Shared> {
            unsafe { msg_send_id![Self::class(), new] }
        }
        pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, O> {
            unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
        }
    }
    unsafe impl<T: Message> NSArray<T, Shared> {
        pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Shared> {
            unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
        }
    }
    unsafe impl<T: Message, O: Ownership> NSArray<T, O> {
        #[doc(alias = "count")]
        #[sel(count)]
        pub fn len(&self) -> usize;
        pub fn is_empty(&self) -> bool {
            self.len() == 0
        }
        #[sel(objectAtIndex:)]
        unsafe fn get_unchecked(&self, index: usize) -> &T;
        #[doc(alias = "objectAtIndex:")]
        pub fn get(&self, index: usize) -> Option<&T> {
            if index < self.len() {
                Some(unsafe { self.get_unchecked(index) })
            } else {
                None
            }
        }
        #[doc(alias = "firstObject")]
        #[sel(firstObject)]
        pub fn first(&self) -> Option<&T>;
        #[doc(alias = "lastObject")]
        #[sel(lastObject)]
        pub fn last(&self) -> Option<&T>;
        #[doc(alias = "objectEnumerator")]
        pub fn iter(&self) -> NSEnumerator<'_, T> {
            unsafe {
                let result: *mut Object = msg_send![self, objectEnumerator];
                NSEnumerator::from_ptr(result)
            }
        }
        #[sel(getObjects:range:)]
        unsafe fn get_objects(&self, ptr: *mut &T, range: NSRange);
        pub fn objects_in_range(&self, range: Range<usize>) -> Vec<&T> {
            let range = NSRange::from(range);
            let mut vec = Vec::with_capacity(range.length);
            unsafe {
                self.get_objects(vec.as_mut_ptr(), range);
                vec.set_len(range.length);
            }
            vec
        }
        pub fn to_vec(&self) -> Vec<&T> {
            self.objects_in_range(0..self.len())
        }
        pub fn into_vec(array: Id<Self, Owned>) -> Vec<Id<T, O>> {
            array
                .to_vec()
                .into_iter()
                .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
                .collect()
        }
    }
    unsafe impl<T: Message> NSArray<T, Shared> {
        #[doc(alias = "objectAtIndex:")]
        pub fn get_retained(&self, index: usize) -> Id<T, Shared> {
            let obj = self.get(index).unwrap();
            unsafe { Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked() }
        }
        pub fn to_shared_vec(&self) -> Vec<Id<T, Shared>> {
            self.to_vec()
                .into_iter()
                .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
                .collect()
        }
    }
    unsafe impl<T: Message> NSArray<T, Owned> {
        #[doc(alias = "objectAtIndex:")]
        pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
            if index < self.len() {
                Some(unsafe { msg_send![self, objectAtIndex: index] })
            } else {
                None
            }
        }
        #[doc(alias = "firstObject")]
        #[sel(firstObject)]
        pub fn first_mut(&mut self) -> Option<&mut T>;
        #[doc(alias = "lastObject")]
        #[sel(lastObject)]
        pub fn last_mut(&mut self) -> Option<&mut T>;
    }
);
unsafe impl<T: Message> NSCopying for NSArray<T, Shared> {
    type Ownership = Shared;
    type Output = NSArray<T, Shared>;
}
unsafe impl<T: Message> NSMutableCopying for NSArray<T, Shared> {
    type Output = NSMutableArray<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSArray<T, Shared> {
    type Owned = Id<NSArray<T, Shared>, Shared>;
    fn to_owned(&self) -> Self::Owned {
        self.copy()
    }
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSArray<T, O> {
    type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSArray<T, O> {
    type Item = &'a T;
    type IntoIter = NSFastEnumerator<'a, NSArray<T, O>>;
    fn into_iter(self) -> Self::IntoIter {
        self.iter_fast()
    }
}
impl<T: Message, O: Ownership> Index<usize> for NSArray<T, O> {
    type Output = T;
    fn index(&self, index: usize) -> &T {
        self.get(index).unwrap()
    }
}
impl<T: Message> IndexMut<usize> for NSArray<T, Owned> {
    fn index_mut(&mut self, index: usize) -> &mut T {
        self.get_mut(index).unwrap()
    }
}
impl<T: Message, O: Ownership> DefaultId for NSArray<T, O> {
    type Ownership = Shared;
    #[inline]
    fn default_id() -> Id<Self, Self::Ownership> {
        Self::new()
    }
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSArray<T, O> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_list().entries(self.iter_fast()).finish()
    }
}
#[cfg(test)]
mod tests {
    use alloc::format;
    use alloc::vec::Vec;
    use super::*;
    use crate::foundation::{NSNumber, NSString};
    use crate::rc::{RcTestObject, ThreadTestData};
    fn sample_array(len: usize) -> Id<NSArray<NSObject, Owned>, Owned> {
        let mut vec = Vec::with_capacity(len);
        for _ in 0..len {
            vec.push(NSObject::new());
        }
        NSArray::from_vec(vec)
    }
    fn sample_number_array(len: u8) -> Id<NSArray<NSNumber, Shared>, Shared> {
        let mut vec = Vec::with_capacity(len as usize);
        for i in 0..len {
            vec.push(NSNumber::new_u8(i));
        }
        NSArray::from_vec(vec)
    }
    #[test]
    fn test_two_empty() {
        let _empty_array1 = NSArray::<NSObject>::new();
        let _empty_array2 = NSArray::<NSObject>::new();
    }
    #[test]
    fn test_len() {
        let empty_array = NSArray::<NSObject>::new();
        assert_eq!(empty_array.len(), 0);
        let array = sample_array(4);
        assert_eq!(array.len(), 4);
    }
    #[test]
    fn test_equality() {
        let array1 = sample_array(3);
        let array2 = sample_array(3);
        assert_ne!(array1, array2);
        let array1 = sample_number_array(3);
        let array2 = sample_number_array(3);
        assert_eq!(array1, array2);
        let array1 = sample_number_array(3);
        let array2 = sample_number_array(4);
        assert_ne!(array1, array2);
    }
    #[test]
    fn test_debug() {
        let obj = sample_number_array(0);
        assert_eq!(format!("{:?}", obj), "[]");
        let obj = sample_number_array(3);
        assert_eq!(format!("{:?}", obj), "[0, 1, 2]");
    }
    #[test]
    fn test_get() {
        let array = sample_array(4);
        assert_ne!(array.get(0), array.get(3));
        assert_eq!(array.first(), array.get(0));
        assert_eq!(array.last(), array.get(3));
        let empty_array = <NSArray<NSObject>>::new();
        assert!(empty_array.first().is_none());
        assert!(empty_array.last().is_none());
    }
    #[test]
    fn test_retains_stored() {
        let obj = Id::into_shared(RcTestObject::new());
        let mut expected = ThreadTestData::current();
        let input = [obj.clone(), obj.clone()];
        expected.retain += 2;
        expected.assert_current();
        let array = NSArray::from_slice(&input);
        expected.retain += 2;
        expected.assert_current();
        let _obj = array.first().unwrap();
        expected.assert_current();
        drop(array);
        expected.release += 2;
        expected.assert_current();
        let array = NSArray::from_vec(Vec::from(input));
        expected.retain += 2;
        expected.release += 2;
        expected.assert_current();
        let _obj = array.get(0).unwrap();
        let _obj = array.get(1).unwrap();
        assert!(array.get(2).is_none());
        expected.assert_current();
        drop(array);
        expected.release += 2;
        expected.assert_current();
        drop(obj);
        expected.release += 1;
        expected.dealloc += 1;
        expected.assert_current();
    }
    #[test]
    fn test_nscopying_uses_retain() {
        let obj = Id::into_shared(RcTestObject::new());
        let array = NSArray::from_slice(&[obj]);
        let mut expected = ThreadTestData::current();
        let _copy = array.copy();
        expected.assert_current();
        let _copy = array.mutable_copy();
        expected.retain += 1;
        expected.assert_current();
    }
    #[test]
    #[cfg_attr(
        feature = "apple",
        ignore = "this works differently on different framework versions"
    )]
    fn test_iter_no_retain() {
        let obj = Id::into_shared(RcTestObject::new());
        let array = NSArray::from_slice(&[obj]);
        let mut expected = ThreadTestData::current();
        let iter = array.iter();
        expected.retain += 0;
        expected.assert_current();
        assert_eq!(iter.count(), 1);
        expected.autorelease += 0;
        expected.assert_current();
        let iter = array.iter_fast();
        assert_eq!(iter.count(), 1);
        expected.assert_current();
    }
    #[test]
    fn test_iter() {
        let array = sample_array(4);
        assert_eq!(array.iter().count(), 4);
        assert!(array
            .iter()
            .enumerate()
            .all(|(i, obj)| Some(obj) == array.get(i)));
    }
    #[test]
    fn test_objects_in_range() {
        let array = sample_array(4);
        let middle_objs = array.objects_in_range(1..3);
        assert_eq!(middle_objs.len(), 2);
        assert_eq!(middle_objs[0], array.get(1).unwrap());
        assert_eq!(middle_objs[1], array.get(2).unwrap());
        let empty_objs = array.objects_in_range(1..1);
        assert!(empty_objs.is_empty());
        let all_objs = array.objects_in_range(0..4);
        assert_eq!(all_objs.len(), 4);
    }
    #[test]
    fn test_into_vec() {
        let array = sample_array(4);
        let vec = NSArray::into_vec(array);
        assert_eq!(vec.len(), 4);
    }
    #[test]
    fn test_generic_ownership_traits() {
        fn assert_partialeq<T: PartialEq>() {}
        assert_partialeq::<NSArray<NSString, Shared>>();
        assert_partialeq::<NSArray<NSString, Owned>>();
        fn test_ownership_implies_partialeq<O: Ownership>() {
            assert_partialeq::<NSArray<NSString, O>>();
        }
        test_ownership_implies_partialeq::<Shared>();
        test_ownership_implies_partialeq::<Owned>();
    }
}