use crate::{prelude::DynDenseStoMut, Dim};

/// Constructs a new valid row in a easy and interactive manner.
///
/// This struct may panic when out of scope. Please see the `Drop` documentation in
/// the [`Trait Implementations`](#implementations) section for more information.
#[derive(Debug, PartialEq)]
pub struct DrMatrixRowConstructor<'a, DS: 'a> {
    data: &'a mut DS,
    dim: &'a mut Dim<[usize; 2]>,
    inserted_elems: usize,
}

impl<'a, DS> DrMatrixRowConstructor<'a, DS>
where
    DS: DynDenseStoMut,
{
    pub(crate) fn new(data: &'a mut DS, dim: &'a mut Dim<[usize; 2]>) -> Self {
        DrMatrixRowConstructor {
            data,
            dim,
            inserted_elems: 0,
        }
    }

    /// Clones all values of `row` into the current row.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use mop_structs::{
    ///     doc_tests::dr_matrix_empty_vec_array,
    ///     matrix::dr_matrix::DrMatrixRef
    /// };
    /// let mut a = dr_matrix_empty_vec_array();
    /// a.row_constructor().copy_values_from_row(&[1, 2, 3, 4, 5]).commit();
    /// assert_eq!(a.as_ref(), DrMatrixRef::new(
    ///     [1, 5],
    ///     &[1, 2, 3, 4, 5],
    /// ));
    /// ```
    pub fn clone_values_from_row(mut self, row: &[DS::Item]) -> Self
    where
        DS::Item: Clone,
    {
        self.inserted_elems += row.len();
        self.data.extend_from_clone(row);
        self
    }

    /// Commits the row construction, modifying the internal structure.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use mop_structs::{
    ///     doc_tests::dr_matrix_empty_vec_array,
    ///     matrix::dr_matrix::DrMatrixRef
    /// };
    /// let mut a = dr_matrix_empty_vec_array();
    /// a.row_constructor()
    ///     .push_value(1)
    ///     .push_value(2)
    ///     .push_value(3)
    ///     .push_value(4)
    ///     .push_value(5)
    ///     .commit();
    /// assert_eq!(a.as_ref(), DrMatrixRef::new([1, 5], &[1, 2, 3, 4, 5]));
    /// ```
    ///
    /// # Assertions
    ///
    /// * The number of inserted elements must be equal the number of columns of `Self`.
    ///
    /// ```should_panic
    /// use mop_structs::doc_tests::dr_matrix_empty_vec_array;
    /// let mut a = dr_matrix_empty_vec_array();
    /// a.row_constructor().push_value(1).commit();
    /// ```
    pub fn commit(mut self) {
        assert!(
            self.inserted_elems == self.dim.cols(),
            "The number of inserted elements must be equal the number of columns of `Self`."
        );
        self.inserted_elems = 0;
        *self.dim.rows_mut() += 1;
    }

    /// Copies all values of `row` into the current row.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use mop_structs::{
    ///     doc_tests::dr_matrix_empty_vec_array,
    ///     matrix::dr_matrix::DrMatrixRef
    /// };
    /// let mut a = dr_matrix_empty_vec_array();
    /// a.row_constructor().copy_values_from_row(&[1, 2, 3, 4, 5]).commit();
    /// assert_eq!(a.as_ref(), DrMatrixRef::new(
    ///     [1, 5],
    ///     &[1, 2, 3, 4, 5],
    /// ));
    /// ```
    pub fn copy_values_from_row(mut self, row: &[DS::Item]) -> Self
    where
        DS::Item: Copy,
    {
        self.inserted_elems += row.len();
        self.data.extend(row);
        self
    }

    /// Pushes a new value.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use mop_structs::{
    ///     doc_tests::{dr_matrix_empty_vec_array, dr_matrix_vec_array},
    /// };
    /// let mut a = dr_matrix_empty_vec_array();
    /// a.row_constructor()
    ///     .push_value(1)
    ///     .push_value(2)
    ///     .push_value(3)
    ///     .push_value(4)
    ///     .push_value(5)
    ///     .commit();
    /// a.row_constructor()
    ///     .push_value(6)
    ///     .push_value(7)
    ///     .push_value(8)
    ///     .push_value(9)
    ///     .push_value(10)
    ///     .commit();
    /// a.row_constructor()
    ///     .push_value(11)
    ///     .push_value(12)
    ///     .push_value(13)
    ///     .push_value(14)
    ///     .push_value(15)
    ///     .commit();
    /// a.row_constructor()
    ///     .push_value(16)
    ///     .push_value(17)
    ///     .push_value(18)
    ///     .push_value(19)
    ///     .push_value(20)
    ///     .commit();
    /// assert_eq!(a.as_ref(), dr_matrix_vec_array().as_ref());
    /// ```
    pub fn push_value(mut self, value: DS::Item) -> Self {
        self.data.push(value);
        self.inserted_elems += 1;
        self
    }
}

impl<'a, DS> Drop for DrMatrixRowConstructor<'a, DS> {
    /// Some measures are taken to ensure a valid format and avoid unexpected runtime behavior.
    ///
    /// # Assertions
    ///
    /// * Every single nonempty instance of `DrMatrixRowConstructor` must end with a call to
    /// the `commit` method.
    ///
    /// ```should_panic
    /// use mop_structs::doc_tests::dr_matrix_empty_vec_array;
    /// let mut a = dr_matrix_empty_vec_array();
    /// a.row_constructor().push_value(1).push_value(2).push_value(3).push_value(4);
    /// ```
    fn drop(&mut self) {
        if self.inserted_elems > 0 {
            panic!(
                "Every single nonempty instance of `DrMatrixRowConstructor` must
                end with a call to the `commit` method."
            );
        }
    }
}
