use super::*;
use burn_tensor::module::avg_pool2d;
use burn_tensor::{Shape, Tolerance};

#[test]
fn test_avg_pool2d_simple() {
    let test = AvgPool2dTestCase {
        batch_size: 1,
        channels: 1,
        kernel_size_1: 3,
        kernel_size_2: 3,
        padding_1: 0,
        padding_2: 0,
        stride_1: 1,
        stride_2: 1,
        height: 6,
        width: 6,
        count_include_pad: true,
    };

    test.assert_output(TestTensor::from_data(
        [[[
            [0.11111, 0.22222, 0.33333, 0.33333, 0.22222, 0.11111],
            [0.22222, 0.44444, 0.66667, 0.66667, 0.44444, 0.22222],
            [0.33333, 0.66667, 1.00000, 1.00000, 0.66667, 0.33333],
            [0.33333, 0.66667, 1.00000, 1.00000, 0.66667, 0.33333],
            [0.22222, 0.44444, 0.66667, 0.66667, 0.44444, 0.22222],
            [0.11111, 0.22222, 0.33333, 0.33333, 0.22222, 0.11111],
        ]]],
        &AutodiffDevice::new(),
    ));
}

#[test]
fn test_avg_pool2d_complex() {
    let test = AvgPool2dTestCase {
        batch_size: 1,
        channels: 1,
        kernel_size_1: 3,
        kernel_size_2: 4,
        padding_1: 1,
        padding_2: 2,
        stride_1: 1,
        stride_2: 2,
        height: 4,
        width: 6,
        count_include_pad: true,
    };

    test.assert_output(TestTensor::from_data(
        [[[
            [0.33333, 0.33333, 0.33333, 0.33333, 0.33333, 0.33333],
            [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
            [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
            [0.33333, 0.33333, 0.33333, 0.33333, 0.33333, 0.33333],
        ]]],
        &AutodiffDevice::new(),
    ));
}

#[test]
fn test_avg_pool2d_complex_dont_include_pad() {
    let test = AvgPool2dTestCase {
        batch_size: 1,
        channels: 1,
        kernel_size_1: 3,
        kernel_size_2: 4,
        padding_1: 1,
        padding_2: 2,
        stride_1: 1,
        stride_2: 2,
        height: 4,
        width: 6,
        count_include_pad: false,
    };

    test.assert_output(TestTensor::from_data(
        [[[
            [0.6250, 0.6250, 0.41667, 0.41667, 0.6250, 0.6250],
            [0.8750, 0.8750, 0.58333, 0.58333, 0.8750, 0.8750],
            [0.8750, 0.8750, 0.58333, 0.58333, 0.8750, 0.8750],
            [0.6250, 0.6250, 0.41667, 0.41667, 0.6250, 0.6250],
        ]]],
        &AutodiffDevice::new(),
    ));
}

struct AvgPool2dTestCase {
    batch_size: usize,
    channels: usize,
    kernel_size_1: usize,
    kernel_size_2: usize,
    padding_1: usize,
    padding_2: usize,
    stride_1: usize,
    stride_2: usize,
    height: usize,
    width: usize,
    count_include_pad: bool,
}

impl AvgPool2dTestCase {
    fn assert_output(self, x_grad: TestTensor<4>) {
        let shape_x = Shape::new([self.batch_size, self.channels, self.height, self.width]);
        let device = AutodiffDevice::new();
        let x = TestTensor::from_data(
            TestTensorInt::arange(0..shape_x.num_elements() as i64, &device)
                .reshape::<4, _>(shape_x)
                .into_data(),
            &device,
        )
        .require_grad();
        let output = avg_pool2d(
            x.clone(),
            [self.kernel_size_1, self.kernel_size_2],
            [self.stride_1, self.stride_2],
            [self.padding_1, self.padding_2],
            self.count_include_pad,
            false,
        );
        let grads = output.backward();
        let x_grad_actual = x.grad(&grads).unwrap();

        x_grad.to_data().assert_approx_eq::<FloatElem>(
            &x_grad_actual.into_data(),
            Tolerance::default().set_half_precision_relative(1e-3),
        );
    }
}
