//! Recursive algorithm for finding column minima.
//!
//! The functions here are mostly meant to be used for testing
//! correctness of the SMAWK implementation.
//!
//! **Note: this module is only available if you enable the `ndarray`
//! Cargo feature.**

use ndarray::{s, Array2, ArrayView2, Axis};

/// Compute row minima in O(*m* + *n* log *m*) time.
///
/// # Panics
///
/// It is an error to call this on a matrix with zero columns.
pub fn row_minima<T: Ord>(matrix: &Array2<T>) -> Vec<usize> {
    let mut minima = vec![0; matrix.nrows()];
    recursive_inner(matrix.view(), &|| Direction::Row, 0, &mut minima);
    minima
}

/// Compute column minima in O(*n* + *m* log *n*) time.
///
/// # Panics
///
/// It is an error to call this on a matrix with zero rows.
pub fn column_minima<T: Ord>(matrix: &Array2<T>) -> Vec<usize> {
    let mut minima = vec![0; matrix.ncols()];
    recursive_inner(matrix.view(), &|| Direction::Column, 0, &mut minima);
    minima
}

/// The type of minima (row or column) we compute.
enum Direction {
    Row,
    Column,
}

/// Compute the minima along the given direction (`Direction::Row` for
/// row minima and `Direction::Column` for column minima).
///
/// The direction is given as a generic function argument to allow
/// monomorphization to kick in. The function calls will be inlined
/// and optimized away and the result is that the compiler generates
/// differnet code for finding row and column minima.
fn recursive_inner<T: Ord, F: Fn() -> Direction>(
    matrix: ArrayView2<'_, T>,
    dir: &F,
    offset: usize,
    minima: &mut [usize],
) {
    if matrix.is_empty() {
        return;
    }

    let axis = match dir() {
        Direction::Row => Axis(0),
        Direction::Column => Axis(1),
    };
    let mid = matrix.len_of(axis) / 2;
    let min_idx = crate::brute_force::lane_minimum(matrix.index_axis(axis, mid));
    minima[mid] = offset + min_idx;

    if mid == 0 {
        return; // Matrix has a single row or column, so we're done.
    }

    let top_left = match dir() {
        Direction::Row => matrix.slice(s![..mid, ..(min_idx + 1)]),
        Direction::Column => matrix.slice(s![..(min_idx + 1), ..mid]),
    };
    let bot_right = match dir() {
        Direction::Row => matrix.slice(s![(mid + 1).., min_idx..]),
        Direction::Column => matrix.slice(s![min_idx.., (mid + 1)..]),
    };
    recursive_inner(top_left, dir, offset, &mut minima[..mid]);
    recursive_inner(bot_right, dir, offset + min_idx, &mut minima[mid + 1..]);
}

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

    #[test]
    fn recursive_1x1() {
        let matrix = arr2(&[[2]]);
        let minima = vec![0];
        assert_eq!(row_minima(&matrix), minima);
        assert_eq!(column_minima(&matrix.reversed_axes()), minima);
    }

    #[test]
    fn recursive_2x1() {
        let matrix = arr2(&[
            [3], //
            [2],
        ]);
        let minima = vec![0, 0];
        assert_eq!(row_minima(&matrix), minima);
        assert_eq!(column_minima(&matrix.reversed_axes()), minima);
    }

    #[test]
    fn recursive_1x2() {
        let matrix = arr2(&[[2, 1]]);
        let minima = vec![1];
        assert_eq!(row_minima(&matrix), minima);
        assert_eq!(column_minima(&matrix.reversed_axes()), minima);
    }

    #[test]
    fn recursive_2x2() {
        let matrix = arr2(&[
            [3, 2], //
            [2, 1],
        ]);
        let minima = vec![1, 1];
        assert_eq!(row_minima(&matrix), minima);
        assert_eq!(column_minima(&matrix.reversed_axes()), minima);
    }

    #[test]
    fn recursive_3x3() {
        let matrix = arr2(&[
            [3, 4, 4], //
            [3, 4, 4],
            [2, 3, 3],
        ]);
        let minima = vec![0, 0, 0];
        assert_eq!(row_minima(&matrix), minima);
        assert_eq!(column_minima(&matrix.reversed_axes()), minima);
    }

    #[test]
    fn recursive_4x4() {
        let matrix = arr2(&[
            [4, 5, 5, 5], //
            [2, 3, 3, 3],
            [2, 3, 3, 3],
            [2, 2, 2, 2],
        ]);
        let minima = vec![0, 0, 0, 0];
        assert_eq!(row_minima(&matrix), minima);
        assert_eq!(column_minima(&matrix.reversed_axes()), minima);
    }

    #[test]
    fn recursive_5x5() {
        let matrix = arr2(&[
            [3, 2, 4, 5, 6],
            [2, 1, 3, 3, 4],
            [2, 1, 3, 3, 4],
            [3, 2, 4, 3, 4],
            [4, 3, 2, 1, 1],
        ]);
        let minima = vec![1, 1, 1, 1, 3];
        assert_eq!(row_minima(&matrix), minima);
        assert_eq!(column_minima(&matrix.reversed_axes()), minima);
    }
}
