//! Holds the definition of [`DynamicArray`].

use core::{
    borrow::{Borrow, BorrowMut},
    hash::Hash,
};

use osom_lib_alloc::traits::Allocator;
use osom_lib_primitives::{align::Align, length::Length};
use osom_lib_reprc::macros::reprc;
use osom_lib_try_clone::TryClone;

use crate::{
    errors::{ArrayError, ArrayIsEmptyError, ArrayTryCloneError},
    traits::{ImmutableArray, MutableArray},
};

use super::AlignedDynamicArray;

/// A `#[repr(C)]` variant of the standard `vec` struct.
///
/// Functionally similar, and implements [`ReprC`][osom_lib_reprc::traits::ReprC] for `T: ReprC`.
/// However, unlike `vec` this struct multiplies capacity by `3/2` when resizing is needed.
///
/// It is equivalent to [`AlignedDynamicArray<Align<1>, TItem, TAllocator>`].
#[derive(Debug)]
#[reprc]
#[repr(transparent)]
#[must_use]
pub struct DynamicArray<TItem, TAllocator>
where
    TAllocator: Allocator,
{
    inner: AlignedDynamicArray<Align<1>, TItem, TAllocator>,
}

impl<TItem, TAllocator> DynamicArray<TItem, TAllocator>
where
    TAllocator: Allocator,
{
    /// Creates a new, empty [`DynamicArray`].
    #[inline(always)]
    pub fn new() -> Self
    where
        TAllocator: Default,
    {
        Self::with_allocator(TAllocator::default())
    }

    /// Creates a new, empty [`DynamicArray`] with an allocator.
    #[inline(always)]
    pub const fn with_allocator(allocator: TAllocator) -> Self {
        Self {
            inner: AlignedDynamicArray::with_allocator(allocator),
        }
    }

    /// Creates a new [`DynamicArray`] with capacity and allocator.
    /// This allocates memory only when `capacity > 0`.
    ///
    /// # Errors
    ///
    /// For details see [`ArrayError`].
    #[inline(always)]
    pub fn with_capacity_and_allocator(capacity: Length, allocator: TAllocator) -> Result<Self, ArrayError> {
        let inner = AlignedDynamicArray::with_capacity_and_allocator(capacity, allocator)?;
        Ok(Self { inner })
    }

    /// Creates a new [`DynamicArray`] with capacity and the default allocator.
    /// This allocates memory only when `capacity > 0`.
    ///
    /// # Errors
    ///
    /// For details see [`ArrayError`].
    #[inline(always)]
    pub fn with_capacity(capacity: Length) -> Result<Self, ArrayError>
    where
        TAllocator: Default,
    {
        Self::with_capacity_and_allocator(capacity, TAllocator::default())
    }

    /// Creates a new [`DynamicArray`] with a given size, generated through a given factory.
    /// This allocates memory only when `size > 0`.
    ///
    /// # Notes
    ///
    /// This method is functionally equivalent to initializing an empty vector and running
    /// factory one by one in a loop, and passing it to
    /// [`push`][`crate::traits::MutableArray::push`]. This way, however, is more efficient,
    /// even if you preallocate the vector with capacity. Because this method gives the
    /// compiler an opportunity to vectorize the construction, unlike sequential
    /// [`push`][`crate::traits::MutableArray::push`] calls.
    ///
    /// # Errors
    ///
    /// For details see [`ArrayError`].
    #[inline(always)]
    pub fn with_factory<Factory: FnMut(usize) -> TItem>(size: Length, factory: Factory) -> Result<Self, ArrayError>
    where
        TAllocator: Default,
    {
        Self::with_factory_and_allocator(size, factory, TAllocator::default())
    }

    /// Creates a new [`DynamicArray`] with a given size, generated through a given factory,
    /// with a custom allocator. This allocates memory only when `size > 0`.
    ///
    /// # Notes
    ///
    /// This method is functionally equivalent to initializing an empty vector and running
    /// factory one by one in a loop, and passing it to
    /// [`push`][`crate::traits::MutableArray::push`]. This way, however, is more efficient,
    /// even if you preallocate the vector with capacity. Because this method gives the
    /// compiler an opportunity to vectorize the construction, unlike sequential
    /// [`push`][`crate::traits::MutableArray::push`] calls.
    ///
    /// # Errors
    ///
    /// For details see [`ArrayError`]
    #[inline(always)]
    pub fn with_factory_and_allocator<Factory: FnMut(usize) -> TItem>(
        size: Length,
        factory: Factory,
        allocator: TAllocator,
    ) -> Result<Self, ArrayError> {
        let inner = AlignedDynamicArray::with_factory_and_allocator(size, factory, allocator)?;
        Ok(Self { inner })
    }

    /// Creates a new [`DynamicArray`] with a given size, but uninitialized.
    ///
    /// # Safety
    ///
    /// The underlying array is uninitialized and reading the data is UB, unless
    /// initialized first.
    ///
    /// # Errors
    ///
    /// For details see [`ArrayError`].
    #[inline(always)]
    pub unsafe fn with_size_uninitialized(size: Length) -> Result<Self, ArrayError>
    where
        TAllocator: Default,
    {
        let inner = unsafe { AlignedDynamicArray::with_size_uninitialized(size) }?;
        Ok(Self { inner })
    }

    /// Creates a new [`DynamicArray`] with a given size and allocator, but uninitialized.
    ///
    /// # Safety
    ///
    /// The underlying array is uninitialized and reading the data is UB, unless
    /// initialized first.
    ///
    /// # Errors
    ///
    /// For details see [`ArrayError`].
    #[inline(always)]
    pub unsafe fn with_size_and_allocator_uninitialized(
        size: Length,
        allocator: TAllocator,
    ) -> Result<Self, ArrayError> {
        let inner = unsafe { AlignedDynamicArray::with_size_and_allocator_uninitialized(size, allocator) }?;
        Ok(Self { inner })
    }
}

impl<T, TAllocator> ImmutableArray<T> for DynamicArray<T, TAllocator>
where
    TAllocator: Allocator,
{
    #[inline(always)]
    fn length(&self) -> Length {
        self.inner.length()
    }

    #[inline(always)]
    fn capacity(&self) -> Length {
        self.inner.capacity()
    }

    #[inline(always)]
    fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }
}

impl<TItem, TAllocator> MutableArray<TItem> for DynamicArray<TItem, TAllocator>
where
    TAllocator: Allocator,
{
    #[inline(always)]
    fn try_push_array<const TSIZE: usize>(&mut self, arr: [TItem; TSIZE]) -> Result<(), ArrayError> {
        self.inner.try_push_array(arr)
    }

    #[inline(always)]
    fn try_push_slice(&mut self, slice: &[TItem]) -> Result<(), ArrayTryCloneError>
    where
        TItem: TryClone,
    {
        self.inner.try_push_slice(slice)
    }

    #[inline(always)]
    fn try_pop(&mut self) -> Result<TItem, ArrayIsEmptyError> {
        self.inner.try_pop()
    }
}

impl<TItem, TAllocator> Default for DynamicArray<TItem, TAllocator>
where
    TAllocator: Allocator + Default,
{
    fn default() -> Self {
        Self::new()
    }
}

impl<TItem, TAllocator> Clone for DynamicArray<TItem, TAllocator>
where
    TItem: TryClone + Clone,
    TAllocator: Allocator + TryClone + Clone,
{
    fn clone(&self) -> Self {
        self.try_clone().expect("Failed to clone dynamic array")
    }
}

impl<TItem, TAllocator> TryClone for DynamicArray<TItem, TAllocator>
where
    TItem: TryClone,
    TAllocator: Allocator + TryClone,
{
    type Error = ArrayTryCloneError;

    fn try_clone(&self) -> Result<Self, Self::Error> {
        let inner = self.inner.try_clone()?;
        Ok(Self { inner })
    }
}

impl<TItem, TAllocator, Rhs> PartialEq<Rhs> for DynamicArray<TItem, TAllocator>
where
    TItem: PartialEq,
    TAllocator: Allocator,
    Rhs: AsRef<[TItem]>,
{
    fn eq(&self, other: &Rhs) -> bool {
        self.as_ref() == other.as_ref()
    }
}

impl<TItem, TAllocator> Eq for DynamicArray<TItem, TAllocator>
where
    TItem: Eq,
    TAllocator: Allocator,
{
}

impl<TItem, TAllocator> Hash for DynamicArray<TItem, TAllocator>
where
    TItem: Hash,
    TAllocator: Allocator,
{
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.as_ref().hash(state);
    }
}

impl<TItem, TAllocator> AsRef<[TItem]> for DynamicArray<TItem, TAllocator>
where
    TAllocator: Allocator,
{
    fn as_ref(&self) -> &[TItem] {
        self.inner.as_ref()
    }
}

impl<TItem, TAllocator> AsMut<[TItem]> for DynamicArray<TItem, TAllocator>
where
    TAllocator: Allocator,
{
    fn as_mut(&mut self) -> &mut [TItem] {
        self.inner.as_mut()
    }
}

impl<TItem, TAllocator> Borrow<[TItem]> for DynamicArray<TItem, TAllocator>
where
    TAllocator: Allocator,
{
    #[inline(always)]
    fn borrow(&self) -> &[TItem] {
        self.inner.borrow()
    }
}

impl<TItem, TAllocator> BorrowMut<[TItem]> for DynamicArray<TItem, TAllocator>
where
    TAllocator: Allocator,
{
    #[inline(always)]
    fn borrow_mut(&mut self) -> &mut [TItem] {
        self.inner.borrow_mut()
    }
}
