//! Shape / stride helpers. Strides are in **elements** (not bytes), row-major.

use smallvec::SmallVec;

/// Inline storage for the common ≤4D case; spills to the heap beyond that.
pub type Dims = SmallVec<[usize; 4]>;

/// Row-major (C order) contiguous strides for a shape.
/// Example: `[2, 3, 4]` => `[12, 4, 1]`.
pub fn contiguous_strides(shape: &[usize]) -> Dims {
    let mut strides: Dims = SmallVec::with_capacity(shape.len());
    strides.resize(shape.len(), 0);
    if shape.is_empty() {
        return strides;
    }
    strides[shape.len() - 1] = 1;
    for i in (0..shape.len() - 1).rev() {
        strides[i] = strides[i + 1] * shape[i + 1];
    }
    strides
}

/// True if `strides` describe a row-major contiguous layout for `shape`.
/// A mismatched stride on a size-1 dimension is fine (it is never stepped).
pub fn is_contiguous(shape: &[usize], strides: &[usize]) -> bool {
    let ndim = shape.len();
    let mut expected = 1usize;
    for d in (0..ndim).rev() {
        if shape[d] != 1 && strides[d] != expected {
            return false;
        }
        expected *= shape[d];
    }
    true
}

/// Total element count.
pub fn num_elements(shape: &[usize]) -> usize {
    shape.iter().product()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn contiguous_strides_basic() {
        assert_eq!(contiguous_strides(&[2, 3, 4]).as_slice(), &[12, 4, 1]);
        assert_eq!(contiguous_strides(&[5]).as_slice(), &[1]);
    }

    #[test]
    fn is_contiguous_detects_transpose() {
        assert!(is_contiguous(&[2, 3], &[3, 1]));
        assert!(!is_contiguous(&[3, 2], &[1, 3]));
    }
}
