import numpy as np
from scipy._lib._array_api import xp_assert_close

from scipy import ndimage
from scipy.ndimage import _ctest
from scipy.ndimage import _cytest
from scipy._lib._ccallback import LowLevelCallable

FILTER1D_FUNCTIONS = [
    lambda filter_size: _ctest.filter1d(filter_size),
    lambda filter_size: _cytest.filter1d(filter_size, with_signature=False),
    lambda filter_size: LowLevelCallable(
                            _cytest.filter1d(filter_size, with_signature=True)
                        ),
    lambda filter_size: LowLevelCallable.from_cython(
                            _cytest, "_filter1d",
                            _cytest.filter1d_capsule(filter_size),
                        ),
]

FILTER2D_FUNCTIONS = [
    lambda weights: _ctest.filter2d(weights),
    lambda weights: _cytest.filter2d(weights, with_signature=False),
    lambda weights: LowLevelCallable(_cytest.filter2d(weights, with_signature=True)),
    lambda weights: LowLevelCallable.from_cython(_cytest,
                                                 "_filter2d",
                                                 _cytest.filter2d_capsule(weights),),
]

TRANSFORM_FUNCTIONS = [
    lambda shift: _ctest.transform(shift),
    lambda shift: _cytest.transform(shift, with_signature=False),
    lambda shift: LowLevelCallable(_cytest.transform(shift, with_signature=True)),
    lambda shift: LowLevelCallable.from_cython(_cytest,
                                               "_transform",
                                               _cytest.transform_capsule(shift),),
]


def test_generic_filter():
    def filter2d(footprint_elements, weights):
        return (weights*footprint_elements).sum()

    def check(j):
        func = FILTER2D_FUNCTIONS[j]

        im = np.ones((20, 20))
        im[:10,:10] = 0
        footprint = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])
        footprint_size = np.count_nonzero(footprint)
        weights = np.ones(footprint_size)/footprint_size

        res = ndimage.generic_filter(im, func(weights),
                                     footprint=footprint)
        std = ndimage.generic_filter(im, filter2d, footprint=footprint,
                                     extra_arguments=(weights,))
        xp_assert_close(res, std, err_msg=f"#{j} failed")

    for j, func in enumerate(FILTER2D_FUNCTIONS):
        check(j)


def test_generic_filter1d():
    def filter1d(input_line, output_line, filter_size):
        for i in range(output_line.size):
            output_line[i] = 0
            for j in range(filter_size):
                output_line[i] += input_line[i+j]
        output_line /= filter_size

    def check(j):
        func = FILTER1D_FUNCTIONS[j]

        im = np.tile(np.hstack((np.zeros(10), np.ones(10))), (10, 1))
        filter_size = 3

        res = ndimage.generic_filter1d(im, func(filter_size),
                                       filter_size)
        std = ndimage.generic_filter1d(im, filter1d, filter_size,
                                       extra_arguments=(filter_size,))
        xp_assert_close(res, std, err_msg=f"#{j} failed")

    for j, func in enumerate(FILTER1D_FUNCTIONS):
        check(j)


def test_geometric_transform():
    def transform(output_coordinates, shift):
        return output_coordinates[0] - shift, output_coordinates[1] - shift

    def check(j):
        func = TRANSFORM_FUNCTIONS[j]

        im = np.arange(12).reshape(4, 3).astype(np.float64)
        shift = 0.5

        res = ndimage.geometric_transform(im, func(shift))
        std = ndimage.geometric_transform(im, transform, extra_arguments=(shift,))
        xp_assert_close(res, std, err_msg=f"#{j} failed")

    for j, func in enumerate(TRANSFORM_FUNCTIONS):
        check(j)
