#![allow(
    clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names
)]

use ndarray::prelude::*;
use ndarray::{arr3, Zip};

// Edge Cases for Windows iterator:
//
// - window size is 0
//     - what is the behaviour of the standard for this situation?
//       "Panics if size is 0."
// - window size of 1
//     - should a warning be printed?
//     - what is the behaviour of the standard for this situation?
//       => No overlapping for size-1-windows but normal behaviour besides that.
// - window size bigger than actual array size
//     - ragged windows or panic?
//     - what is the behaviour of the standard for this situation?
//       "If the slice is shorter than size, the iterator returns no values."

/// Test that verifies the `Windows` iterator panics on window sizes equal to zero.
#[test]
#[should_panic]
fn windows_iterator_zero_size()
{
    let a = Array::from_iter(10..37)
        .into_shape_with_order((3, 3, 3))
        .unwrap();
    a.windows(Dim((0, 0, 0)));
}

/// Test that verifies that no windows are yielded on oversized window sizes.
#[test]
fn windows_iterator_oversized()
{
    let a = Array::from_iter(10..37)
        .into_shape_with_order((3, 3, 3))
        .unwrap();
    let mut iter = a.windows((4, 3, 2)).into_iter(); // (4,3,2) doesn't fit into (3,3,3) => oversized!
    assert_eq!(iter.next(), None);
}

/// Simple test for iterating 1d-arrays via `Windows`.
#[test]
fn windows_iterator_1d()
{
    let a = Array::from_iter(10..20).into_shape_with_order(10).unwrap();
    itertools::assert_equal(a.windows(Dim(4)), vec![
            arr1(&[10, 11, 12, 13]),
            arr1(&[11, 12, 13, 14]),
            arr1(&[12, 13, 14, 15]),
            arr1(&[13, 14, 15, 16]),
            arr1(&[14, 15, 16, 17]),
            arr1(&[15, 16, 17, 18]),
            arr1(&[16, 17, 18, 19]),
        ]);
}

/// Simple test for iterating 2d-arrays via `Windows`.
#[test]
fn windows_iterator_2d()
{
    let a = Array::from_iter(10..30)
        .into_shape_with_order((5, 4))
        .unwrap();
    itertools::assert_equal(a.windows(Dim((3, 2))), vec![
            arr2(&[[10, 11], [14, 15], [18, 19]]),
            arr2(&[[11, 12], [15, 16], [19, 20]]),
            arr2(&[[12, 13], [16, 17], [20, 21]]),
            arr2(&[[14, 15], [18, 19], [22, 23]]),
            arr2(&[[15, 16], [19, 20], [23, 24]]),
            arr2(&[[16, 17], [20, 21], [24, 25]]),
            arr2(&[[18, 19], [22, 23], [26, 27]]),
            arr2(&[[19, 20], [23, 24], [27, 28]]),
            arr2(&[[20, 21], [24, 25], [28, 29]]),
        ]);
}

/// Simple test for iterating 3d-arrays via `Windows`.
#[test]
fn windows_iterator_3d()
{
    let a = Array::from_iter(10..37)
        .into_shape_with_order((3, 3, 3))
        .unwrap();
    itertools::assert_equal(a.windows(Dim((2, 2, 2))), vec![
            arr3(&[[[10, 11], [13, 14]], [[19, 20], [22, 23]]]),
            arr3(&[[[11, 12], [14, 15]], [[20, 21], [23, 24]]]),
            arr3(&[[[13, 14], [16, 17]], [[22, 23], [25, 26]]]),
            arr3(&[[[14, 15], [17, 18]], [[23, 24], [26, 27]]]),
            arr3(&[[[19, 20], [22, 23]], [[28, 29], [31, 32]]]),
            arr3(&[[[20, 21], [23, 24]], [[29, 30], [32, 33]]]),
            arr3(&[[[22, 23], [25, 26]], [[31, 32], [34, 35]]]),
            arr3(&[[[23, 24], [26, 27]], [[32, 33], [35, 36]]]),
        ]);
}

/// Test that verifies the `Windows` iterator panics when stride has an axis equal to zero.
#[test]
#[should_panic]
fn windows_iterator_stride_axis_zero()
{
    let a = Array::from_iter(10..37)
        .into_shape_with_order((3, 3, 3))
        .unwrap();
    a.windows_with_stride((2, 2, 2), (0, 2, 2));
}

/// Test that verifies that only first window is yielded when stride is oversized on every axis.
#[test]
fn windows_iterator_only_one_valid_window_for_oversized_stride()
{
    let a = Array::from_iter(10..135)
        .into_shape_with_order((5, 5, 5))
        .unwrap();
    let mut iter = a.windows_with_stride((2, 2, 2), (8, 8, 8)).into_iter(); // (4,3,2) doesn't fit into (3,3,3) => oversized!
    itertools::assert_equal(iter.next(), Some(arr3(&[[[10, 11], [15, 16]], [[35, 36], [40, 41]]])));
}

/// Simple test for iterating 1d-arrays via `Windows` with stride.
#[test]
fn windows_iterator_1d_with_stride()
{
    let a = Array::from_iter(10..20).into_shape_with_order(10).unwrap();
    itertools::assert_equal(a.windows_with_stride(4, 2), vec![
            arr1(&[10, 11, 12, 13]),
            arr1(&[12, 13, 14, 15]),
            arr1(&[14, 15, 16, 17]),
            arr1(&[16, 17, 18, 19]),
        ]);
}

/// Simple test for iterating 2d-arrays via `Windows` with stride.
#[test]
fn windows_iterator_2d_with_stride()
{
    let a = Array::from_iter(10..30)
        .into_shape_with_order((5, 4))
        .unwrap();
    itertools::assert_equal(a.windows_with_stride((3, 2), (2, 1)), vec![
            arr2(&[[10, 11], [14, 15], [18, 19]]),
            arr2(&[[11, 12], [15, 16], [19, 20]]),
            arr2(&[[12, 13], [16, 17], [20, 21]]),
            arr2(&[[18, 19], [22, 23], [26, 27]]),
            arr2(&[[19, 20], [23, 24], [27, 28]]),
            arr2(&[[20, 21], [24, 25], [28, 29]]),
        ]);
}

/// Simple test for iterating 3d-arrays via `Windows` with stride.
#[test]
fn windows_iterator_3d_with_stride()
{
    let a = Array::from_iter(10..74)
        .into_shape_with_order((4, 4, 4))
        .unwrap();
    itertools::assert_equal(a.windows_with_stride((2, 2, 2), (2, 2, 2)), vec![
            arr3(&[[[10, 11], [14, 15]], [[26, 27], [30, 31]]]),
            arr3(&[[[12, 13], [16, 17]], [[28, 29], [32, 33]]]),
            arr3(&[[[18, 19], [22, 23]], [[34, 35], [38, 39]]]),
            arr3(&[[[20, 21], [24, 25]], [[36, 37], [40, 41]]]),
            arr3(&[[[42, 43], [46, 47]], [[58, 59], [62, 63]]]),
            arr3(&[[[44, 45], [48, 49]], [[60, 61], [64, 65]]]),
            arr3(&[[[50, 51], [54, 55]], [[66, 67], [70, 71]]]),
            arr3(&[[[52, 53], [56, 57]], [[68, 69], [72, 73]]]),
        ]);
}

#[test]
fn test_window_zip()
{
    let a = Array::from_iter(0..64)
        .into_shape_with_order((4, 4, 4))
        .unwrap();

    for x in 1..4 {
        for y in 1..4 {
            for z in 1..4 {
                Zip::indexed(a.windows((x, y, z))).for_each(|(i, j, k), window| {
                    let x = x as isize;
                    let y = y as isize;
                    let z = z as isize;
                    let i = i as isize;
                    let j = j as isize;
                    let k = k as isize;
                    assert_eq!(window, a.slice(s![i..i + x, j..j + y, k..k + z]));
                })
            }
        }
    }
}

/// Test verifies that non existent Axis results in panic
#[test]
#[should_panic]
fn axis_windows_outofbound()
{
    let a = Array::from_iter(10..37)
        .into_shape_with_order((3, 3, 3))
        .unwrap();
    a.axis_windows(Axis(4), 2);
}

/// Test verifies that zero sizes results in panic
#[test]
#[should_panic]
fn axis_windows_zero_size()
{
    let a = Array::from_iter(10..37)
        .into_shape_with_order((3, 3, 3))
        .unwrap();
    a.axis_windows(Axis(0), 0);
}

/// Test verifies that over sized windows yield nothing
#[test]
fn axis_windows_oversized()
{
    let a = Array::from_iter(10..37)
        .into_shape_with_order((3, 3, 3))
        .unwrap();
    let mut iter = a.axis_windows(Axis(2), 4).into_iter();
    assert_eq!(iter.next(), None);
}

/// Simple test for iterating 1d-arrays via `Axis Windows`.
#[test]
fn test_axis_windows_1d()
{
    let a = Array::from_iter(10..20).into_shape_with_order(10).unwrap();

    itertools::assert_equal(a.axis_windows(Axis(0), 5), vec![
            arr1(&[10, 11, 12, 13, 14]),
            arr1(&[11, 12, 13, 14, 15]),
            arr1(&[12, 13, 14, 15, 16]),
            arr1(&[13, 14, 15, 16, 17]),
            arr1(&[14, 15, 16, 17, 18]),
            arr1(&[15, 16, 17, 18, 19]),
        ]);
}

/// Simple test for iterating 2d-arrays via `Axis Windows`.
#[test]
fn test_axis_windows_2d()
{
    let a = Array::from_iter(10..30)
        .into_shape_with_order((5, 4))
        .unwrap();

    itertools::assert_equal(a.axis_windows(Axis(0), 2), vec![
            arr2(&[[10, 11, 12, 13], [14, 15, 16, 17]]),
            arr2(&[[14, 15, 16, 17], [18, 19, 20, 21]]),
            arr2(&[[18, 19, 20, 21], [22, 23, 24, 25]]),
            arr2(&[[22, 23, 24, 25], [26, 27, 28, 29]]),
        ]);
}

/// Simple test for iterating 3d-arrays via `Axis Windows`.
#[test]
fn test_axis_windows_3d()
{
    let a = Array::from_iter(0..27)
        .into_shape_with_order((3, 3, 3))
        .unwrap();

    itertools::assert_equal(a.axis_windows(Axis(1), 2), vec![
            arr3(&[
                [[0, 1, 2], [3, 4, 5]],
                [[9, 10, 11], [12, 13, 14]],
                [[18, 19, 20], [21, 22, 23]],
            ]),
            arr3(&[
                [[3, 4, 5], [6, 7, 8]],
                [[12, 13, 14], [15, 16, 17]],
                [[21, 22, 23], [24, 25, 26]],
            ]),
        ]);
}

#[test]
fn tests_axis_windows_3d_zips_with_1d()
{
    let a = Array::from_iter(0..27)
        .into_shape_with_order((3, 3, 3))
        .unwrap();
    let mut b = Array::zeros(2);

    Zip::from(b.view_mut())
        .and(a.axis_windows(Axis(1), 2))
        .for_each(|b, a| {
            *b = a.sum();
        });
    assert_eq!(b,arr1(&[207, 261]));
}

#[test]
fn test_window_neg_stride()
{
    let array = Array::from_iter(1..10)
        .into_shape_with_order((3, 3))
        .unwrap();

    // window neg/pos stride combinations

    // Make a 2 x 2 array of the windows of the 3 x 3 array
    // and compute test answers from here
    let mut answer = Array::from_iter(array.windows((2, 2)).into_iter().map(|a| a.to_owned()))
        .into_shape_with_order((2, 2))
        .unwrap();

    answer.invert_axis(Axis(1));
    answer.map_inplace(|a| a.invert_axis(Axis(1)));

    itertools::assert_equal(array.slice(s![.., ..;-1]).windows((2, 2)), answer.iter());

    answer.invert_axis(Axis(0));
    answer.map_inplace(|a| a.invert_axis(Axis(0)));

    itertools::assert_equal(array.slice(s![..;-1, ..;-1]).windows((2, 2)), answer.iter());

    answer.invert_axis(Axis(1));
    answer.map_inplace(|a| a.invert_axis(Axis(1)));

    itertools::assert_equal(array.slice(s![..;-1, ..]).windows((2, 2)), answer.iter());
}

#[test]
fn test_windows_with_stride_on_inverted_axis()
{
    let mut array = Array::from_iter(1..17)
        .into_shape_with_order((4, 4))
        .unwrap();

    // inverting axis results in negative stride
    array.invert_axis(Axis(0));
    itertools::assert_equal(array.windows_with_stride((2, 2), (2, 2)), vec![
            arr2(&[[13, 14], [9, 10]]),
            arr2(&[[15, 16], [11, 12]]),
            arr2(&[[5, 6], [1, 2]]),
            arr2(&[[7, 8], [3, 4]]),
        ]);

    array.invert_axis(Axis(1));
    itertools::assert_equal(array.windows_with_stride((2, 2), (2, 2)), vec![
            arr2(&[[16, 15], [12, 11]]),
            arr2(&[[14, 13], [10, 9]]),
            arr2(&[[8, 7], [4, 3]]),
            arr2(&[[6, 5], [2, 1]]),
        ]);
}
