import os
import random
import tempfile

import numpy as np
import pytest
import torch
from osgeo import gdal

from helpers import gdal_dataset_with_data
import yirgacheffe
from yirgacheffe.layers import RasterLayer, ConstantLayer
from yirgacheffe.operators import LayerOperation
from yirgacheffe.backends import backend

def test_add_byte_layers() -> None:
    data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]).astype(np.uint8)
    data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]]).astype(np.uint8)

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    assert layer1.datatype == gdal.GDT_Byte
    assert layer2.datatype == gdal.GDT_Byte

    comp = layer1 + layer2
    comp.save(result)

    expected = data1 + data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_error_of_pixel_scale_wrong() -> None:
    data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]).astype(np.uint8)
    data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]]).astype(np.uint8)
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.01, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))

    with pytest.raises(ValueError):
        _ = layer1 + layer2

def test_error_of_pixel_scale_wrong_three_param() -> None:
    data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]).astype(np.uint8)
    data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]]).astype(np.uint8)
    data3 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]]).astype(np.uint8)
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    layer3 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.01, data3))

    with pytest.raises(ValueError):
        _ = LayerOperation.where(layer1, layer2, layer3)

@pytest.mark.parametrize("skip,expected_steps", [
    (1, [0.0, 0.5, 1.0]),
    (2, [0.0, 1.0]),
])
def test_add_byte_layers_with_callback(skip, expected_steps) -> None:
    data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]).astype(np.uint8)
    data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]]).astype(np.uint8)

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    assert layer1.datatype == gdal.GDT_Byte
    assert layer2.datatype == gdal.GDT_Byte

    callback_possitions = []

    comp = layer1 + layer2
    comp.ystep = skip
    comp.save(result, callback=lambda x: callback_possitions.append(x))

    expected = data1 + data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

    assert callback_possitions == expected_steps

def test_sub_byte_layers() -> None:
    data1 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
    data2 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 - layer2
    comp.save(result)

    expected = data1 - data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_add_float_layers() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    data2 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 + layer2
    comp.save(result)

    expected = data1 + data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_sub_float_layers() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    data2 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 - layer2
    comp.save(result)

    expected = data1 - data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_mult_float_layers() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    data2 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 * layer2
    comp.save(result)

    expected = backend.promote(data1) * backend.promote(data2)
    backend.eval_op(expected)

    actual = backend.demote_array(result.read_array(0, 0, 4, 2))

    assert (expected == actual).all()

def test_div_float_layers() -> None:
    data1 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])
    data2 = np.array([[1.0, 2.0, 3.0, 4.0], [5.5, 6.5, 7.5, 8.5]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 / layer2
    comp.save(result)

    expected = backend.promote(data1) / backend.promote(data2)
    backend.eval_op(expected)

    actual = backend.demote_array(result.read_array(0, 0, 4, 2))

    assert (expected == actual).all()

def test_floor_div_float_layers() -> None:
    data1 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])
    data2 = np.array([[1.0, 2.0, 3.0, 4.0], [5.5, 6.5, 7.5, 8.5]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 // layer2
    comp.save(result)

    expected = backend.promote(data1) // backend.promote(data2)
    backend.eval_op(expected)

    actual = backend.demote_array(result.read_array(0, 0, 4, 2))

    assert (expected == actual).all()

def test_remainder_float_layers() -> None:
    data1 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])
    data2 = np.array([[1.0, 2.0, 3.0, 4.0], [5.5, 6.5, 7.5, 8.5]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 % layer2
    comp.save(result)

    expected = backend.promote(data1) % backend.promote(data2)
    backend.eval_op(expected)

    actual = backend.demote_array(result.read_array(0, 0, 4, 2))

    assert (expected == actual).all()

@pytest.mark.parametrize("c", [
    (float(2.5)),
    (int(2)),
    (np.uint16(2)),
    (np.float32(2.5)),
])
def test_add_to_float_layer_by_const(c) -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 + c
    comp.save(result)

    expected = data1 + c
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_sub_from_float_layer_by_const() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 - 0.5
    comp.save(result)

    expected = data1 - 0.5
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_mult_float_layer_by_const() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 * 2.5
    comp.save(result)

    expected = data1 * 2.5
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_div_float_layer_by_const() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 / 2.5
    comp.save(result)

    expected = backend.promote(data1) / 2.5
    backend.eval_op(expected)

    actual = backend.demote_array(result.read_array(0, 0, 4, 2))

    assert (expected == actual).all()

def test_floordiv_float_layer_by_const() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 // 2.5
    comp.save(result)

    expected = backend.promote(data1) // 2.5
    backend.eval_op(expected)

    actual = backend.demote_array(result.read_array(0, 0, 4, 2))

    assert (expected == actual).all()

def test_remainder_float_layer_by_const() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 % 2.5
    comp.save(result)

    expected = backend.promote(data1) % 2.5
    backend.eval_op(expected)

    actual = backend.demote_array(result.read_array(0, 0, 4, 2))

    assert (expected == actual).all()

def test_power_float_layer_by_const() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 ** 2.5
    comp.save(result)

    expected = backend.promote(data1) ** 2.5
    backend.eval_op(expected)

    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_simple_unary_numpy_apply() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    def simple_add(chunk):
        return chunk + 1.0

    comp = layer1.numpy_apply(simple_add)
    comp.save(result)

    expected = data1 + 1.0
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_isin_unary_numpy_apply() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    def simple_add(chunk):
        return np.isin(chunk, [2.0, 3.0])

    comp = layer1.numpy_apply(simple_add)
    comp.save(result)

    # The * 1.0 is because the numpy result will be bool, but we bounced
    # our answer via a float gdal dataset
    expected = np.isin(data1, [2.0, 3.0]) * 1.0
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_simple_binary_numpy_apply() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    data2 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    def simple_add(chunk1, chunk2):
        return chunk1 + chunk2

    comp = layer1.numpy_apply(simple_add, layer2)
    comp.save(result)

    expected = data1 + data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_simple_unary_shader_apply() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    def simple_add(pixel):
        return pixel + 1.0

    comp = layer1.shader_apply(simple_add)
    comp.save(result)

    expected = data1 + 1.0
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_simple_binary_shader_apply() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    data2 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    def simple_add(pixel1, pixel2):
        return pixel1 + pixel2

    comp = layer1.shader_apply(simple_add, layer2)
    comp.save(result)

    expected = data1 + data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

@pytest.mark.parametrize("operator",
    [
        lambda a, b: a == b,
        lambda a, b: a != b,
        lambda a, b: a > b,
        lambda a, b: a >= b,
        lambda a, b: a < b,
        lambda a, b: a <= b,
    ]
)
def test_comparison_float_layer_by_const(operator) -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = operator(layer1, 3.0)
    comp.save(result)

    expected = operator(data1, 3.0)
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_sum_layer() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))

    # a no-op just to get us into an operator
    comp = layer1 + 0.0

    actual = comp.sum()

    expected = np.sum(data1)
    assert expected == actual

def test_constant_layer_result_rhs() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    const_layer = ConstantLayer(1.0)
    result = RasterLayer.empty_raster_layer_like(layer1)

    layers = [layer1, const_layer, result]
    intersection = RasterLayer.find_intersection(layers)
    for layer in layers:
        layer.set_window_for_intersection(intersection)

    comp = layer1 + const_layer
    comp.save(result)
    result.reset_window()
    actual = result.read_array(0, 0, 4, 2)

    expected = 1.0 + data1

    assert (expected == actual).all()

def test_constant_layer_result_lhs() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    const_layer = ConstantLayer(1.0)
    result = RasterLayer.empty_raster_layer_like(layer1)

    intersection = RasterLayer.find_intersection([layer1, const_layer])
    const_layer.set_window_for_intersection(intersection)
    layer1.set_window_for_intersection(intersection)

    comp = const_layer + layer1
    comp.save(result)

    actual = result.read_array(0, 0, 4, 2)

    expected = 1.0 + data1

    assert (expected == actual).all()

def test_shader_apply_constant_lhs() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    const_layer = ConstantLayer(1.0)
    result = RasterLayer.empty_raster_layer_like(layer1)

    def simple_add(pixel1, pixel2):
        return pixel1 + pixel2

    comp = const_layer.shader_apply(simple_add, layer1)
    comp.save(result)

    expected = data1 + 1.0
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_shader_apply_constant_rhs() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    const_layer = ConstantLayer(1.0)
    result = RasterLayer.empty_raster_layer_like(layer1)

    def simple_add(pixel1, pixel2):
        return pixel1 + pixel2

    comp = layer1.shader_apply(simple_add, const_layer)
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp.save(result)

    expected = data1 + 1.0
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_direct_layer_save() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    layer1.save(result)
    actual = result.read_array(0, 0, 4, 2)

    assert (data1 == actual).all()

def test_direct_layer_sum() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))

    actual = layer1.sum()

    expected = np.sum(data1)
    assert expected == actual

def test_direct_layer_sum_chunked() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))

    calc = LayerOperation(layer1)
    calc.ystep = 1
    actual = calc.sum()

    expected = np.sum(data1)
    assert expected == actual

def test_direct_layer_min() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))

    actual = layer1.min()

    expected = np.min(data1)
    assert expected == actual

def test_direct_layer_min_chunked() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))

    calc = LayerOperation(layer1)
    calc.ystep = 1
    actual = calc.min()

    expected = np.min(data1)
    assert expected == actual

def test_direct_layer_max() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))

    actual = layer1.max()

    expected = np.max(data1)
    assert expected == actual

def test_direct_layer_max_chunked() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))

    calc = LayerOperation(layer1)
    calc.ystep = 1
    actual = calc.max()

    expected = np.max(data1)
    assert expected == actual

def test_direct_layer_save_and_sum() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    actual_sum = layer1.save(result, and_sum=True)
    actual_data = result.read_array(0, 0, 4, 2)
    expected_sum = np.sum(data1)

    assert (data1 == actual_data).all()
    assert expected_sum == actual_sum

@pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
def test_add_to_float_layer_by_np_array() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 + np.array(2.5)
    comp.save(result)

    expected = data1 + 2.5
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_write_mulitband_raster() -> None:
    data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
    data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    layers = [layer1, layer2]

    with tempfile.TemporaryDirectory() as tempdir:
        filename = os.path.join(tempdir, "test.tif")
        with RasterLayer.empty_raster_layer_like(layer1, filename=filename, bands=2) as result:
            for i in range(2):
                layers[i].save(result, band=i+1)

        for i in range(2):
            layer = RasterLayer.layer_from_file(filename, band=i+1)
            expected = layers[i].read_array(0, 0, 4, 2)
            actual = layer.read_array(0, 0, 4, 2)

            assert (expected == actual).all()

@pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
def test_save_and_sum_float32(monkeypatch) -> None:
    random.seed(42)
    data = []
    for _ in range(10):
        row = []
        for _ in range(10):
            row.append(random.random())
        data.append(row)

    data1 = np.array(data, dtype=np.float32)
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    assert layer1.datatype == gdal.GDT_Float32

    # Sum forces things to float64
    expected = np.sum(data1.astype(np.float64))

    with monkeypatch.context() as m:
        for blocksize in range(1,11):
            m.setattr(yirgacheffe.constants, "YSTEP", blocksize)
            with RasterLayer.empty_raster_layer_like(layer1) as store:
                actual = layer1.save(store, and_sum=True)
            assert expected == actual

@pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
def test_parallel_save_and_sum_float32(monkeypatch) -> None:
    random.seed(42)
    data = []
    for _ in range(10):
        row = []
        for _ in range(10):
            row.append(random.random())
        data.append(row)

    with tempfile.TemporaryDirectory() as tempdir:
        path1 = os.path.join(tempdir, "test1.tif")
        data1 = np.array(data, dtype=np.float32)
        dataset1 = gdal_dataset_with_data((0.0, 0.0), 0.02, data1, filename=path1)
        dataset1.Close()
        layer1 = RasterLayer.layer_from_file(path1)
        assert layer1.datatype == gdal.GDT_Float32

        # Sum forces things to float64
        expected = np.sum(data1.astype(np.float64))

        with monkeypatch.context() as m:
            for blocksize in range(1,11):
                m.setattr(yirgacheffe.constants, "YSTEP", blocksize)
                with RasterLayer.empty_raster_layer_like(layer1) as store:
                    actual = layer1.parallel_save(store, and_sum=True)
                assert expected == actual

@pytest.mark.skipif(yirgacheffe.backends.BACKEND != "NUMPY", reason="Only applies for numpy")
def test_sum_float32(monkeypatch) -> None:
    random.seed(42)
    data = []
    for _ in range(10):
        row = []
        for _ in range(10):
            row.append(random.random())
        data.append(row)

    data1 = np.array(data, dtype=np.float32)
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    assert layer1.datatype == gdal.GDT_Float32

    # Sum forces things to float64
    expected = np.sum(data1.astype(np.float64))

    with monkeypatch.context() as m:
        for blocksize in range(1,11):
            m.setattr(yirgacheffe.constants, "YSTEP", blocksize)
            actual = layer1.sum()
            assert expected == actual

def test_and_byte_layers() -> None:
    data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]]).astype(np.uint8)
    data2 = np.array([[1, 1, 0, 0], [2, 2, 2, 2]]).astype(np.uint8)

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    assert layer1.datatype == gdal.GDT_Byte
    assert layer2.datatype == gdal.GDT_Byte

    comp = layer1 & layer2
    comp.save(result)

    expected = data1 & data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_or_byte_layers() -> None:
    data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]]).astype(np.uint8)
    data2 = np.array([[1, 1, 0, 0], [2, 2, 2, 2]]).astype(np.uint8)

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    assert layer1.datatype == gdal.GDT_Byte
    assert layer2.datatype == gdal.GDT_Byte

    comp = layer1 | layer2
    comp.save(result)

    expected = data1 | data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_and_int_layers() -> None:
    data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]]).astype(np.int16)
    data2 = np.array([[1, 1, 0, 0], [2, 2, 2, 2]]).astype(np.int16)

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    assert layer1.datatype == gdal.GDT_Int16
    assert layer2.datatype == gdal.GDT_Int16

    comp = layer1 & layer2
    comp.save(result)

    expected = data1 & data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_or_int_layers() -> None:
    data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]]).astype(np.int16)
    data2 = np.array([[1, 1, 0, 0], [2, 2, 2, 2]]).astype(np.int16)

    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    assert layer1.datatype == gdal.GDT_Int16
    assert layer2.datatype == gdal.GDT_Int16

    comp = layer1 | layer2
    comp.save(result)

    expected = data1 | data2
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_nan_to_num() -> None:
    data1 = np.array([[float('nan'), float('nan'), float('nan'), float('nan')], [1, 2, 3, 0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.nan_to_num(nan=42)
    comp.ystep = 1
    comp.save(result)

    expected = np.array([[42, 42, 42, 42], [1, 2, 3, 0]])
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

def test_nan_to_num_default() -> None:
    data1 = np.array([[float('nan'), float('nan'), float('nan'), float('nan')], [1, 2, 3, 0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.nan_to_num()
    comp.ystep = 1
    comp.save(result)

    expected = np.array([[0, 0, 0, 0], [1, 2, 3, 0]])
    actual = result.read_array(0, 0, 4, 2)

    assert (expected == actual).all()

@pytest.mark.parametrize("ct", [
    float,
    int,
    np.uint16,
    np.float32,
])
def test_where_simple(ct) -> None:
    data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.where(layer1 > 0, ct(1), ct(2))
    comp.ystep = 1
    comp.save(result)

    expected = np.where(data1 > 0, ct(1), ct(2))
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_where_layers() -> None:
    data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]])
    data_a = np.array([[10, 10, 10, 10], [20, 20, 20, 20]])
    data_b = np.array([[100, 100, 100, 100], [200, 200, 200, 200]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer_a = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data_a))
    layer_b = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data_b))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.where(layer1 > 0, layer_a, layer_b)
    comp.ystep = 1
    comp.save(result)

    expected = np.where(data1 > 0, data_a, data_b)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_isin_simple_method() -> None:
    data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.isin([2, 3])
    comp.ystep = 1
    comp.save(result)

    expected = backend.isin(backend.promote(data1), [2, 3])


    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_isin_simple_module() -> None:
    data1 = np.array([[0, 1, 0, 2], [0, 0, 1, 1]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.isin(layer1, [2, 3])
    comp.ystep = 1
    comp.save(result)

    expected = np.isin(data1, [2, 3])
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

@pytest.mark.parametrize("val", [
    (float(2.0)),
    (int(2)),
    (np.uint16(2)),
    (np.float32(2.0)),
])
def test_layer_comparison_to_value(val) -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 == val
    comp.save(result)

    expected = data1 == val
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_less_than_value() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 < 2.0
    comp.save(result)

    expected = data1 < 2.0
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_less_than_or_equal_to_value() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 <= 2.0
    comp.save(result)

    expected = data1 <= 2.0
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_greater_than_value() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 > 2.0
    comp.save(result)

    expected = data1 > 2.0
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_greater_than_or_equal_to_value() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 >= 2.0
    comp.save(result)

    expected = data1 >= 2.0
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_comparison_to_layer() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    data2 = np.array([[3.0, 2.0, 1.0, 4.0], [7.0, 2.0, 5.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 == layer2
    comp.save(result)

    expected = data1 == data2
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_less_than_layer() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    data2 = np.array([[3.0, 2.0, 1.0, 4.0], [7.0, 2.0, 5.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 < layer2
    comp.save(result)

    expected = data1 < data2
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_less_than_or_equal_to_layer() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    data2 = np.array([[3.0, 2.0, 1.0, 4.0], [7.0, 2.0, 5.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 <= layer2
    comp.save(result)

    expected = data1 <= data2
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_greater_than_layer() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    data2 = np.array([[3.0, 2.0, 1.0, 4.0], [7.0, 2.0, 5.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 > layer2
    comp.save(result)

    expected = data1 > data2
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_layer_greater_than_or_equal_to_layer() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    data2 = np.array([[3.0, 2.0, 1.0, 4.0], [7.0, 2.0, 5.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1 >= layer2
    comp.save(result)

    expected = data1 >= data2
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_log_method() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.log()
    comp.save(result)

    expected = backend.log(backend.promote(data1))
    backend.eval_op(expected)

    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_log_module() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.log(layer1)
    comp.save(result)

    expected = backend.log(backend.promote(data1))
    backend.eval_op(expected)

    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_log2() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.log2()
    comp.save(result)

    expected = backend.log2(backend.promote(data1))
    backend.eval_op(expected)

    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_log10() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.log10()
    comp.save(result)

    expected = backend.log10(backend.promote(data1))
    backend.eval_op(expected)

    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_exp_method() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.exp()
    comp.save(result)

    expected = backend.exp(backend.promote(data1))
    backend.eval_op(expected)

    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_exp_module() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.exp(layer1)
    comp.save(result)

    expected = backend.exp(backend.promote(data1))
    backend.eval_op(expected)

    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_exp2() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.exp2()
    comp.save(result)

    expected = backend.exp2(backend.promote(data1))
    backend.eval_op(expected)

    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_minimum_layers() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    data2 = np.array([[3.0, 2.0, 1.0, 4.0], [8.0, 2.0, 5.0, 7.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.minimum(layer1, layer2)
    comp.save(result)

    expected = np.minimum(data1, data2)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_maximum_layers() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    data2 = np.array([[3.0, 2.0, 1.0, 4.0], [8.0, 2.0, 5.0, 7.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    layer2 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.maximum(layer1, layer2)
    comp.save(result)

    expected = np.maximum(data1, data2)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_clip_both_method() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.clip(3.0, 6.0)
    comp.save(result)

    expected = data1.clip(3.0, 6.0)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_clip_both_module() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.clip(layer1, 3.0, 6.0)
    comp.save(result)

    expected = np.clip(data1, 3.0, 6.0)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_clip_upper_method() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.clip(max=6.0)
    comp.save(result)

    expected = data1.clip(max=6.0)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_clip_upper_module() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.clip(layer1, max=6.0)
    comp.save(result)

    expected = np.clip(data1, a_min=None, a_max=6.0)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_clip_lower_method() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = layer1.clip(min=3.0)
    comp.save(result)

    expected = data1.clip(min=3.0)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

def test_clip_lower_module() -> None:
    data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 2.0, 7.0, 8.0]])
    layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
    result = RasterLayer.empty_raster_layer_like(layer1)

    comp = LayerOperation.clip(layer1, min=3.0)
    comp.save(result)

    expected = np.clip(data1, a_min=3.0, a_max=None)
    actual = result.read_array(0, 0, 4, 2)
    assert (expected == actual).all()

@pytest.mark.parametrize("skip", [
    1,
    2,
    5,
])
def test_simple_conv2d_unity(skip) -> None:
    data1 = np.array([
        [0, 0, 5, 0, 0],
        [0, 1, 1, 1, 0],
        [4, 1, 1, 1, 3],
        [0, 1, 1, 1, 0],
        [0, 0, 2, 0, 0],
    ]).astype(np.float32)
    weights = np.array([
        [0, 0, 0],
        [0, 1, 0],
        [0, 0, 0],
    ])
    with RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1:
        calc = layer1.conv2d(weights)
        calc.ystep = skip
        with RasterLayer.empty_raster_layer_like(layer1) as res:
            calc.save(res)
            actual = res.read_array(0, 0, 5, 5)
            assert (data1 == actual).all()

@pytest.mark.parametrize("skip", [
    1,
    2,
    5,
])
def test_simple_conv2d_blur(skip) -> None:
    data1 = np.array([
        [0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 0, 0, 0, 0],
    ]).astype(np.float32)
    weights = np.array([
        [0.0, 0.1, 0.0],
        [0.1, 0.6, 0.1],
        [0.0, 0.1, 0.0],
    ])

    conv = torch.nn.Conv2d(1, 1, 3, padding=1, bias=False)
    conv.weight = torch.nn.Parameter(torch.from_numpy(np.array([[weights.astype(np.float32)]])))
    tensorres = conv(torch.from_numpy(np.array([[data1]])))
    expected = tensorres.detach().numpy()[0][0]

    with RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1:

        calc = layer1.conv2d(weights)
        calc.ystep = skip
        with RasterLayer.empty_raster_layer_like(layer1) as res:
            calc.save(res)
            actual = res.read_array(0, 0, 5, 5)

            # Torch and MLX give slightly different rounding
            assert np.isclose(expected, actual).all()

@pytest.mark.parametrize("skip", [
    1,
    2,
    5,
])
def test_simple_conv2d_over_calculated_result(skip) -> None:
    # This test is interesting as it'll pull expanded data from the child calculation
    # datasets
    data1 = np.array([
        [0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 0, 0, 0, 0],
    ]).astype(np.float32)
    data2 = np.array([
        [2, 0, 0, 0, 2],
        [0, 2, 0, 2, 0],
        [0, 0, 2, 0, 0],
        [0, 2, 0, 2, 0],
        [2, 0, 0, 0, 2],
    ]).astype(np.float32)
    weights = np.array([
        [0.0, 0.1, 0.0],
        [0.1, 0.6, 0.1],
        [0.0, 0.1, 0.0],
    ])

    joined_data = data1 * data2

    conv = torch.nn.Conv2d(1, 1, 3, padding=1, bias=False)
    conv.weight = torch.nn.Parameter(torch.from_numpy(np.array([[weights.astype(np.float32)]])))
    tensorres = conv(torch.from_numpy(np.array([[joined_data]])))
    expected = tensorres.detach().numpy()[0][0]

    with RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1:
        with RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2)) as layer2:

            calc = (layer1 * layer2).conv2d(weights)
            calc.ystep = skip
            with RasterLayer.empty_raster_layer_like(layer1) as res:
                calc.save(res)
                actual = res.read_array(0, 0, 5, 5)

                # Torch and MLX give slightly different rounding
                assert np.isclose(expected, actual).all()
