use core::slice::{from_raw_parts, from_raw_parts_mut};

use crate::{
    vec::css::{CssSlice, CssSliceMut},
    Dim,
};

macro_rules! impl_iter (
    (
        $csr_matrix_row_iter:ident,
        $data_ptr:ty,
        $data_type:ty,
        $from_raw_parts:ident,
        $css_slice:ident
    ) => (

#[derive(Debug)]
pub struct $csr_matrix_row_iter<'a, T: 'a> {
    curr_row: usize,
    data: $data_ptr,
    dim: Dim<[usize; 2]>,
    indcs: &'a [usize],
    ptrs: &'a [usize],
}

impl<'a, T> $csr_matrix_row_iter<'a, T> {
    pub(crate) fn new(
        dim: [usize; 2],
        data: $data_ptr,
        indcs: &'a [usize],
        ptrs: &'a [usize]
    ) -> Self {
        $csr_matrix_row_iter {
            curr_row: 0,
            data,
            dim: dim.into(),
            indcs,
            ptrs,
        }
    }

    pub fn split_at(self, idx: usize) -> (Self, Self) {
        let current_len = self.dim.rows() - self.curr_row;
        assert!(idx <= current_len);
        let slice_point = self.curr_row + idx;
        (
            $csr_matrix_row_iter {
                curr_row: self.curr_row,
                data: self.data,
                dim: [slice_point, self.dim.cols()].into(),
                indcs: self.indcs,
                ptrs: self.ptrs,
            },
            $csr_matrix_row_iter {
                curr_row: slice_point,
                data: self.data,
                dim: self.dim,
                indcs: self.indcs,
                ptrs: self.ptrs,
            },
        )
    }
}

impl<'a, T> DoubleEndedIterator for $csr_matrix_row_iter<'a, T> {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.curr_row >= self.dim.rows() {
            return None;
        }
        let starting_row_ptr = self.ptrs[self.dim.rows() - 1];
        let finishing_row_ptr = self.ptrs[self.dim.rows()];
        let data: $data_type = unsafe {
            let ptr = self.data.add(starting_row_ptr);
            let len = finishing_row_ptr - starting_row_ptr;
            $from_raw_parts(ptr, len)
        };
        *self.dim.rows_mut() -= 1;
        Some($css_slice::new(
            self.dim.cols(),
            data,
            &self.indcs[starting_row_ptr..finishing_row_ptr],
        ))
    }
}

impl<'a, T> ExactSizeIterator for $csr_matrix_row_iter<'a, T> {}

impl<'a, T> Iterator for $csr_matrix_row_iter<'a, T> {
    type Item = $css_slice<'a, T>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.curr_row >= self.dim.rows() {
            return None;
        }

        let starting_row_ptr = self.ptrs[self.curr_row];
        let finishing_row_ptr = self.ptrs[self.curr_row + 1];

        let data: $data_type = unsafe {
            let ptr = self.data.add(starting_row_ptr);
            $from_raw_parts(ptr, finishing_row_ptr - starting_row_ptr)
        };

        self.curr_row += 1;

        Some($css_slice::new(
            self.dim.cols(),
            data,
            &self.indcs[starting_row_ptr..finishing_row_ptr],
        ))
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.dim.rows(), Some(self.dim.rows()))
    }
}

unsafe impl<'a, T> Send for $csr_matrix_row_iter<'a, T> {}
unsafe impl<'a, T> Sync for $csr_matrix_row_iter<'a, T> {}

    );
);

impl_iter!(
    CsrMatrixRowIter,
    *const T,
    &'a [T],
    from_raw_parts,
    CssSlice
);
impl_iter!(
    CsrMatrixRowIterMut,
    *mut T,
    &'a mut [T],
    from_raw_parts_mut,
    CssSliceMut
);
