use arrow::compute::concatenate::concatenate;
use arrow::Either;
use crate::prelude::append::update_sorted_flag_before_append;
use crate::prelude::*;
use crate::series::IsSorted;
fn extend_immutable(immutable: &dyn Array, chunks: &mut Vec<ArrayRef>, other_chunks: &[ArrayRef]) {
    let out = if chunks.len() == 1 {
        concatenate(&[immutable, &*other_chunks[0]]).unwrap()
    } else {
        let mut arrays = Vec::with_capacity(other_chunks.len() + 1);
        arrays.push(immutable);
        arrays.extend(other_chunks.iter().map(|a| &**a));
        concatenate(&arrays).unwrap()
    };
    chunks.push(out);
}
impl<T> ChunkedArray<T>
where
    T: PolarsNumericType,
{
    pub fn extend(&mut self, other: &Self) {
        update_sorted_flag_before_append::<T>(self, other);
        if self.chunks.len() > 1 {
            self.append(other);
            *self = self.rechunk();
            return;
        }
        let arr = self.downcast_iter().next().unwrap();
        let arr = arr.clone();
        {
            self.chunks.clear();
        }
        use Either::*;
        if arr.values().is_sliced() {
            extend_immutable(&arr, &mut self.chunks, &other.chunks);
        } else {
            match arr.into_mut() {
                Left(immutable) => {
                    extend_immutable(&immutable, &mut self.chunks, &other.chunks);
                },
                Right(mut mutable) => {
                    for arr in other.downcast_iter() {
                        match arr.null_count() {
                            0 => mutable.extend_from_slice(arr.values()),
                            _ => mutable.extend_trusted_len(arr.into_iter()),
                        }
                    }
                    let arr: PrimitiveArray<T::Native> = mutable.into();
                    self.chunks.push(Box::new(arr) as ArrayRef)
                },
            }
        }
        self.compute_len();
    }
}
#[doc(hidden)]
impl StringChunked {
    pub fn extend(&mut self, other: &Self) {
        self.set_sorted_flag(IsSorted::Not);
        self.append(other)
    }
}
#[doc(hidden)]
impl BinaryChunked {
    pub fn extend(&mut self, other: &Self) {
        self.set_sorted_flag(IsSorted::Not);
        self.append(other)
    }
}
#[doc(hidden)]
impl BinaryOffsetChunked {
    pub fn extend(&mut self, other: &Self) {
        self.set_sorted_flag(IsSorted::Not);
        self.append(other)
    }
}
#[doc(hidden)]
impl BooleanChunked {
    pub fn extend(&mut self, other: &Self) {
        update_sorted_flag_before_append::<BooleanType>(self, other);
        if self.chunks.len() > 1 {
            self.append(other);
            *self = self.rechunk();
            return;
        }
        let arr = self.downcast_iter().next().unwrap();
        let arr = arr.clone();
        {
            self.chunks.clear();
        }
        use Either::*;
        match arr.into_mut() {
            Left(immutable) => {
                extend_immutable(&immutable, &mut self.chunks, &other.chunks);
            },
            Right(mut mutable) => {
                for arr in other.downcast_iter() {
                    mutable.extend_trusted_len(arr.into_iter())
                }
                let arr: BooleanArray = mutable.into();
                self.chunks.push(Box::new(arr) as ArrayRef)
            },
        }
        self.compute_len();
        self.set_sorted_flag(IsSorted::Not);
    }
}
#[doc(hidden)]
impl ListChunked {
    pub fn extend(&mut self, other: &Self) -> PolarsResult<()> {
        self.set_sorted_flag(IsSorted::Not);
        self.append(other)
    }
}
#[cfg(feature = "dtype-array")]
#[doc(hidden)]
impl ArrayChunked {
    pub fn extend(&mut self, other: &Self) -> PolarsResult<()> {
        self.set_sorted_flag(IsSorted::Not);
        self.append(other)
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    #[allow(clippy::redundant_clone)]
    fn test_extend_primitive() {
        let mut values = Vec::with_capacity(32);
        values.extend_from_slice(&[1, 2, 3]);
        let mut ca = Int32Chunked::from_vec("a", values);
        let location = ca.cont_slice().unwrap().as_ptr() as usize;
        let to_append = Int32Chunked::new("a", &[4, 5, 6]);
        ca.extend(&to_append);
        let location2 = ca.cont_slice().unwrap().as_ptr() as usize;
        assert_eq!(location, location2);
        assert_eq!(ca.cont_slice().unwrap(), [1, 2, 3, 4, 5, 6]);
        let _temp = ca.chunks.clone();
        ca.extend(&to_append);
        let location2 = ca.cont_slice().unwrap().as_ptr() as usize;
        assert_ne!(location, location2);
        assert_eq!(ca.cont_slice().unwrap(), [1, 2, 3, 4, 5, 6, 4, 5, 6]);
    }
    #[test]
    fn test_extend_string() {
        let mut ca = StringChunked::new("a", &["a", "b", "c"]);
        let to_append = StringChunked::new("a", &["a", "b", "e"]);
        ca.extend(&to_append);
        assert_eq!(ca.len(), 6);
        let vals = ca.into_no_null_iter().collect::<Vec<_>>();
        assert_eq!(vals, ["a", "b", "c", "a", "b", "e"])
    }
    #[test]
    fn test_extend_bool() {
        let mut ca = BooleanChunked::new("a", [true, false]);
        let to_append = BooleanChunked::new("a", &[false, false]);
        ca.extend(&to_append);
        assert_eq!(ca.len(), 4);
        let vals = ca.into_no_null_iter().collect::<Vec<_>>();
        assert_eq!(vals, [true, false, false, false]);
    }
}