# vim: set fileencoding=utf-8 :
import operator
import pytest
from functools import reduce

import pyvips
from helpers import *


# point convolution
def conv(image, mask, x_position, y_position):
    s = 0.0
    for x in range(0, mask.width):
        for y in range(0, mask.height):
            m = mask(x, y)
            i = image(x + x_position, y + y_position)
            p = run_fn2(operator.mul, m, i)
            s = run_fn2(operator.add, s, p)

    return run_fn2(operator.truediv, s, mask.scale)


def compass(image, mask, x_position, y_position, n_rot, fn):
    acc = []
    for i in range(0, n_rot):
        result = conv(image, mask, x_position, y_position)
        result = run_fn(abs, result)
        acc.append(result)
        mask = mask.rot45()

    return reduce(lambda a, b: run_fn2(fn, a, b), acc)


class TestConvolution:
    @classmethod
    def setup_class(cls):
        im = pyvips.Image.mask_ideal(100, 100, 0.5, reject=True, optical=True)
        cls.colour = im * [1, 2, 3] + [2, 3, 4]
        cls.colour = cls.colour.copy(interpretation=pyvips.Interpretation.SRGB)
        cls.mono = cls.colour.extract_band(1)
        cls.mono = cls.mono.copy(interpretation=pyvips.Interpretation.B_W)
        cls.all_images = [cls.mono, cls.colour]
        cls.sharp = pyvips.Image.new_from_array([[-1, -1, -1],
                                                 [-1, 16, -1],
                                                 [-1, -1, -1]], scale=8)
        cls.blur = pyvips.Image.new_from_array([[1, 1, 1],
                                                [1, 1, 1],
                                                [1, 1, 1]], scale=9)
        cls.line = pyvips.Image.new_from_array([[1, 1, 1],
                                                [-2, -2, -2],
                                                [1, 1, 1]])
        cls.sobel = pyvips.Image.new_from_array([[1, 2, 1],
                                                 [0, 0, 0],
                                                 [-1, -2, -1]])
        cls.all_masks = [cls.sharp, cls.blur, cls.line, cls.sobel]

    @classmethod
    def teardown_class(cls):
        cls.colour = None
        cls.mono = None
        cls.all_images = None
        cls.sharp = None
        cls.blur = None
        cls.line = None
        cls.sobel = None
        cls.all_masks = None

    def test_conv(self):
        for im in self.all_images:
            for msk in self.all_masks:
                for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
                    convolved = im.conv(msk, precision=prec)

                    result = convolved(25, 50)
                    true = conv(im, msk, 24, 49)
                    assert_almost_equal_objects(result, true)

                    result = convolved(50, 50)
                    true = conv(im, msk, 49, 49)
                    assert_almost_equal_objects(result, true)

        # https://github.com/libvips/libvips/issues/4955
        # (cast to ushort to avoid vector path)
        im = pyvips.Image.mask_ideal(100, 100, 0.5, reject=True, optical=True, uchar=True).cast('ushort')
        convolved = im.conv(self.sharp, precision=pyvips.Precision.INTEGER)
        assert convolved(24, 49) == [0]

    # don't test conva, it's still not done
    def dont_est_conva(self):
        for im in self.all_images:
            for msk in self.all_masks:
                print("msk:")
                msk.matrixprint()
                print("im.bands = %s" % im.bands)

                convolved = im.conv(msk,
                                    precision=pyvips.Precision.APPROXIMATE)

                result = convolved(25, 50)
                true = conv(im, msk, 24, 49)
                print("result = %s, true = %s" % (result, true))
                assert_less_threshold(result, true, 5)

                result = convolved(50, 50)
                true = conv(im, msk, 49, 49)
                print("result = %s, true = %s" % (result, true))
                assert_less_threshold(result, true, 5)

    def test_compass(self):
        for im in self.all_images:
            for msk in self.all_masks:
                for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
                    for times in range(1, 4):
                        convolved = im.compass(msk,
                                               times=times,
                                               angle=pyvips.Angle45.D45,
                                               combine=pyvips.Combine.MAX,
                                               precision=prec)

                        result = convolved(25, 50)
                        true = compass(im, msk, 24, 49, times, max)
                        assert_almost_equal_objects(result, true)

        for im in self.all_images:
            for msk in self.all_masks:
                for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
                    for times in range(1, 4):
                        convolved = im.compass(msk,
                                               times=times,
                                               angle=pyvips.Angle45.D45,
                                               combine=pyvips.Combine.SUM,
                                               precision=prec)

                        result = convolved(25, 50)
                        true = compass(im, msk, 24, 49, times, operator.add)
                        assert_almost_equal_objects(result, true)

    def test_convsep(self):
        for im in self.all_images:
            for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
                gmask = pyvips.Image.gaussmat(2, 0.1,
                                              precision=prec)
                gmask_sep = pyvips.Image.gaussmat(2, 0.1,
                                                  separable=True,
                                                  precision=prec)

                assert gmask.width == gmask.height
                assert gmask_sep.width == gmask.width
                assert gmask_sep.height == 1

                a = im.conv(gmask, precision=prec)
                b = im.convsep(gmask_sep, precision=prec)

                a_point = a(25, 50)
                b_point = b(25, 50)

                assert_almost_equal_objects(a_point, b_point, threshold=0.1)

    def test_fastcor(self):
        for im in self.all_images:
            for fmt in noncomplex_formats:
                small = im.crop(20, 45, 10, 10).cast(fmt)
                cor = im.fastcor(small)
                v, x, y = cor.minpos()

                assert v == 0
                assert x == 25
                assert y == 50

    def test_spcor(self):
        for im in self.all_images:
            for fmt in noncomplex_formats:
                small = im.crop(20, 45, 10, 10).cast(fmt)
                cor = im.spcor(small)
                v, x, y = cor.maxpos()

                assert v == 1.0
                assert x == 25
                assert y == 50

    def test_gaussblur(self):
        for im in self.all_images:
            for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
                for i in range(5, 10):
                    sigma = i / 5.0
                    gmask = pyvips.Image.gaussmat(sigma, 0.2,
                                                  precision=prec)

                    a = im.conv(gmask, precision=prec)
                    b = im.gaussblur(sigma, min_ampl=0.2, precision=prec)

                    a_point = a(25, 50)
                    b_point = b(25, 50)

                    assert_almost_equal_objects(a_point, b_point,
                                                threshold=0.1)

    def test_sharpen(self):
        for im in self.all_images:
            for fmt in noncomplex_formats:
                # old vipses used "radius", check that that still works
                sharp = im.sharpen(radius=5)

                for sigma in [0.5, 1, 1.5, 2]:
                    im = im.cast(fmt)
                    sharp = im.sharpen(sigma=sigma)

                    # hard to test much more than this
                    assert im.width == sharp.width
                    assert im.height == sharp.height

                    # if m1 and m2 are zero, sharpen should do nothing
                    sharp = im.sharpen(sigma=sigma, m1=0, m2=0)
                    sharp = sharp.colourspace(im.interpretation)
                    # print("testing sig = %g" % sigma)
                    # print("testing fmt = %s" % fmt)
                    # print("max diff = %g" % (im - sharp).abs().max())
                    assert (im - sharp).abs().max() == 0


if __name__ == '__main__':
    pytest.main()
