use crate::{
    prelude::DynDenseStoMut,
    utils::{are_in_upper_bound, does_not_have_duplicates},
    vec::css::CssSlice,
    Dim,
};

/// Constructs a new valid row in a easy and interactive manner, abstracting away the complexity
/// of the CSR format.
///
/// 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 CsrMatrixRowConstructor<'a, DS: 'a, US: 'a> {
    row_nnz: usize,
    data: &'a mut DS,
    dim: &'a mut Dim<[usize; 2]>,
    indcs: &'a mut US,
    ptrs: &'a mut US,
}

impl<'a, DS, US> CsrMatrixRowConstructor<'a, DS, US>
where
    DS: DynDenseStoMut,
    US: DynDenseStoMut<Item = usize>,
{
    pub(crate) fn new(
        dim: &'a mut Dim<[usize; 2]>,
        data: &'a mut DS,
        indcs: &'a mut US,
        ptrs: &'a mut US,
    ) -> Self {
        CsrMatrixRowConstructor {
            row_nnz: 0,
            data,
            dim,
            indcs,
            ptrs,
        }
    }

    /// Commits the row construction, modifying the internal structure.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use mop_structs::{
    ///     doc_tests::csr_matrix_empty_vec_array,
    ///     matrix::csr_matrix::CsrMatrixRef
    /// };
    /// let mut a = csr_matrix_empty_vec_array();
    /// a.row_constructor().commit();
    /// assert_eq!(a.as_ref(), CsrMatrixRef::new([1, 5], &[], &[], &[0, 0]));
    /// ```
    ///
    /// # Assertions
    ///
    /// * Inserted indices must be less than the number of columns of `Self`.
    ///
    /// ```should_panic
    /// use mop_structs::doc_tests::csr_matrix_empty_vec_array;
    /// let mut a = csr_matrix_empty_vec_array();
    /// a.row_constructor().set_value(10, 1).commit();
    /// ```
    ///
    /// * Inserted indices must be unique.
    ///
    /// ```should_panic
    /// use mop_structs::doc_tests::csr_matrix_empty_vec_array;
    /// let mut a = csr_matrix_empty_vec_array();
    /// a.row_constructor().set_value(1, 1).set_value(1, 10).commit();
    /// ```
    pub fn commit(self) {
        let last_ptr = *self.ptrs.as_slice().last().unwrap();
        let inserted_indcs = &self.indcs.as_slice()[last_ptr..];
        assert!(
            are_in_upper_bound(inserted_indcs, &self.dim.cols()),
            "Inserted indices must be less than the number of columns of `Self`"
        );
        assert!(
            does_not_have_duplicates(inserted_indcs),
            "Inserted indices must be unique."
        );
        unsafe { self.commit_unchecked() };
    }

    /// A faster and unsafe version of [`commit`](#method.commit).
    pub unsafe fn commit_unchecked(mut self) {
        let last_ptr = *self.ptrs.as_slice().last().unwrap();
        self.ptrs.push(last_ptr + self.row_nnz);
        *self.dim.rows_mut() += 1;
        self.row_nnz = 0;
    }

    /// Copies all values of `row` into the current row.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use mop_structs::{
    ///     doc_tests::csr_matrix_empty_vec_array,
    ///     matrix::csr_matrix::CsrMatrixRef,
    ///     vec::css::CssSlice
    /// };
    /// let mut a = csr_matrix_empty_vec_array();
    /// let b = CssSlice::new(5, &[1, 2], &[0, 1]);
    /// a.row_constructor().copy_values_from_row(&b).commit();
    /// assert_eq!(a.as_ref(), CsrMatrixRef::new(
    ///     [1, 5],
    ///     &[1, 2],
    ///     &[0, 1],
    ///     &[0, 2]
    /// ));
    /// ```
    pub fn copy_values_from_row(mut self, row: &CssSlice<'_, DS::Item>) -> Self
    where
        DS::Item: Copy,
    {
        self.row_nnz += row.nnz();
        self.data.extend(row.data());
        self.indcs.extend(row.indcs());
        self
    }

    /// Sets a new value in the current row and column index `column_idx`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use mop_structs::{
    ///     doc_tests::csr_matrix_empty_vec_array,
    ///     matrix::csr_matrix::CsrMatrixRef
    /// };
    /// let mut a = csr_matrix_empty_vec_array();
    /// a.row_constructor().set_value(1, 1).commit();
    /// assert_eq!(a.as_ref(), CsrMatrixRef::new([1, 5], &[1], &[1], &[0, 1]));
    /// ```
    pub fn set_value(mut self, column_idx: usize, value: DS::Item) -> Self {
        self.row_nnz += 1;
        self.data.push(value);
        self.indcs.push(column_idx);
        self
    }
}

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