import platform
import unittest

try:
    from pygame.tests.test_utils import arrinter
except NameError:
    pass
import pygame
from pygame.locals import *
from pygame.pixelcopy import surface_to_array, map_array, array_to_surface, make_surface

IS_PYPY = "PyPy" == platform.python_implementation()


def unsigned32(i):
    """cast signed 32 bit integer to an unsigned integer"""
    return i & 0xFFFFFFFF


@unittest.skipIf(IS_PYPY, "pypy having illegal instruction on mac")
class PixelcopyModuleTest(unittest.TestCase):
    bitsizes = [8, 16, 32]

    test_palette = [
        (0, 0, 0, 255),
        (10, 30, 60, 255),
        (25, 75, 100, 255),
        (100, 150, 200, 255),
        (0, 100, 200, 255),
    ]

    surf_size = (10, 12)
    test_points = [
        ((0, 0), 1),
        ((4, 5), 1),
        ((9, 0), 2),
        ((5, 5), 2),
        ((0, 11), 3),
        ((4, 6), 3),
        ((9, 11), 4),
        ((5, 6), 4),
    ]

    def __init__(self, *args, **kwds):
        pygame.display.init()
        try:
            unittest.TestCase.__init__(self, *args, **kwds)
            self.sources = [
                self._make_src_surface(8),
                self._make_src_surface(16),
                self._make_src_surface(16, srcalpha=True),
                self._make_src_surface(24),
                self._make_src_surface(32),
                self._make_src_surface(32, srcalpha=True),
            ]
        finally:
            pygame.display.quit()

    def _make_surface(self, bitsize, srcalpha=False, palette=None):
        if palette is None:
            palette = self.test_palette
        flags = 0
        if srcalpha:
            flags |= SRCALPHA
        surf = pygame.Surface(self.surf_size, flags, bitsize)
        if bitsize == 8:
            surf.set_palette([c[:3] for c in palette])
        return surf

    def _fill_surface(self, surf, palette=None):
        if palette is None:
            palette = self.test_palette
        surf.fill(palette[1], (0, 0, 5, 6))
        surf.fill(palette[2], (5, 0, 5, 6))
        surf.fill(palette[3], (0, 6, 5, 6))
        surf.fill(palette[4], (5, 6, 5, 6))

    def _make_src_surface(self, bitsize, srcalpha=False, palette=None):
        surf = self._make_surface(bitsize, srcalpha, palette)
        self._fill_surface(surf, palette)
        return surf

    def setUp(self):
        pygame.display.init()

    def tearDown(self):
        pygame.display.quit()

    def test_surface_to_array_2d(self):
        alpha_color = (0, 0, 0, 128)

        for surf in self.sources:
            src_bitsize = surf.get_bitsize()
            for dst_bitsize in self.bitsizes:
                # dst in a surface standing in for a 2 dimensional array
                # of unsigned integers. The byte order is system dependent.
                dst = pygame.Surface(surf.get_size(), 0, dst_bitsize)
                dst.fill((0, 0, 0, 0))
                view = dst.get_view("2")
                self.assertFalse(surf.get_locked())
                if dst_bitsize < src_bitsize:
                    self.assertRaises(ValueError, surface_to_array, view, surf)
                    self.assertFalse(surf.get_locked())
                    continue
                surface_to_array(view, surf)
                self.assertFalse(surf.get_locked())
                for posn, i in self.test_points:
                    sp = surf.get_at_mapped(posn)
                    dp = dst.get_at_mapped(posn)
                    self.assertEqual(
                        dp,
                        sp,
                        "%s != %s: flags: %i"
                        ", bpp: %i, posn: %s"
                        % (dp, sp, surf.get_flags(), surf.get_bitsize(), posn),
                    )
                del view

                if surf.get_masks()[3]:
                    dst.fill((0, 0, 0, 0))
                    view = dst.get_view("2")
                    posn = (2, 1)
                    surf.set_at(posn, alpha_color)
                    self.assertFalse(surf.get_locked())
                    surface_to_array(view, surf)
                    self.assertFalse(surf.get_locked())
                    sp = surf.get_at_mapped(posn)
                    dp = dst.get_at_mapped(posn)
                    self.assertEqual(
                        dp, sp, "%s != %s: bpp: %i" % (dp, sp, surf.get_bitsize())
                    )

        if IS_PYPY:
            return
        # Swapped endian destination array
        pai_flags = arrinter.PAI_ALIGNED | arrinter.PAI_WRITEABLE
        for surf in self.sources:
            for itemsize in [1, 2, 4, 8]:
                if itemsize < surf.get_bytesize():
                    continue
                a = arrinter.Array(surf.get_size(), "u", itemsize, flags=pai_flags)
                surface_to_array(a, surf)
                for posn, i in self.test_points:
                    sp = unsigned32(surf.get_at_mapped(posn))
                    dp = a[posn]
                    self.assertEqual(
                        dp,
                        sp,
                        "%s != %s: itemsize: %i, flags: %i"
                        ", bpp: %i, posn: %s"
                        % (
                            dp,
                            sp,
                            itemsize,
                            surf.get_flags(),
                            surf.get_bitsize(),
                            posn,
                        ),
                    )

    def test_surface_to_array_3d(self):
        self.iter_surface_to_array_3d((0xFF, 0xFF00, 0xFF0000, 0))
        self.iter_surface_to_array_3d((0xFF0000, 0xFF00, 0xFF, 0))

    def iter_surface_to_array_3d(self, rgba_masks):
        dst = pygame.Surface(self.surf_size, 0, 24, masks=rgba_masks)

        for surf in self.sources:
            dst.fill((0, 0, 0, 0))
            src_bitsize = surf.get_bitsize()
            view = dst.get_view("3")
            self.assertFalse(surf.get_locked())
            surface_to_array(view, surf)
            self.assertFalse(surf.get_locked())
            for posn, i in self.test_points:
                sc = surf.get_at(posn)[0:3]
                dc = dst.get_at(posn)[0:3]
                self.assertEqual(
                    dc,
                    sc,
                    "%s != %s: flags: %i"
                    ", bpp: %i, posn: %s"
                    % (dc, sc, surf.get_flags(), surf.get_bitsize(), posn),
                )
            view = None

    def test_map_array(self):
        targets = [
            self._make_surface(8),
            self._make_surface(16),
            self._make_surface(16, srcalpha=True),
            self._make_surface(24),
            self._make_surface(32),
            self._make_surface(32, srcalpha=True),
        ]
        source = pygame.Surface(
            self.surf_size, 0, 24, masks=[0xFF, 0xFF00, 0xFF0000, 0]
        )
        self._fill_surface(source)
        source_view = source.get_view("3")  # (w, h, 3)
        for t in targets:
            map_array(t.get_view("2"), source_view, t)
            for posn, i in self.test_points:
                sc = t.map_rgb(source.get_at(posn))
                dc = t.get_at_mapped(posn)
                self.assertEqual(
                    dc,
                    sc,
                    "%s != %s: flags: %i"
                    ", bpp: %i, posn: %s"
                    % (dc, sc, t.get_flags(), t.get_bitsize(), posn),
                )

        color = pygame.Color("salmon")
        color.set_length(3)
        for t in targets:
            map_array(t.get_view("2"), color, t)
            sc = t.map_rgb(color)
            for posn, i in self.test_points:
                dc = t.get_at_mapped(posn)
                self.assertEqual(
                    dc,
                    sc,
                    "%s != %s: flags: %i"
                    ", bpp: %i, posn: %s"
                    % (dc, sc, t.get_flags(), t.get_bitsize(), posn),
                )

        # mismatched shapes
        w, h = source.get_size()
        target = pygame.Surface((w, h + 1), 0, 32)
        self.assertRaises(ValueError, map_array, target, source, target)
        target = pygame.Surface((w - 1, h), 0, 32)
        self.assertRaises(ValueError, map_array, target, source, target)

    def test_array_to_surface_broadcasting(self):
        # target surfaces
        targets = [
            self._make_surface(8),
            self._make_surface(16),
            self._make_surface(16, srcalpha=True),
            self._make_surface(24),
            self._make_surface(32),
            self._make_surface(32, srcalpha=True),
        ]

        w, h = self.surf_size

        # broadcast column
        column = pygame.Surface((1, h), 0, 32)
        for target in targets:
            source = pygame.Surface((1, h), 0, target)
            for y in range(h):
                source.set_at((0, y), pygame.Color(y + 1, y + h + 1, y + 2 * h + 1))
            pygame.pixelcopy.surface_to_array(column.get_view("2"), source)
            pygame.pixelcopy.array_to_surface(target, column.get_view("2"))
            for x in range(w):
                for y in range(h):
                    self.assertEqual(
                        target.get_at_mapped((x, y)), column.get_at_mapped((0, y))
                    )

        # broadcast row
        row = pygame.Surface((w, 1), 0, 32)
        for target in targets:
            source = pygame.Surface((w, 1), 0, target)
            for x in range(w):
                source.set_at((x, 0), pygame.Color(x + 1, x + w + 1, x + 2 * w + 1))
            pygame.pixelcopy.surface_to_array(row.get_view("2"), source)
            pygame.pixelcopy.array_to_surface(target, row.get_view("2"))
            for x in range(w):
                for y in range(h):
                    self.assertEqual(
                        target.get_at_mapped((x, y)), row.get_at_mapped((x, 0))
                    )

        # broadcast pixel
        pixel = pygame.Surface((1, 1), 0, 32)
        for target in targets:
            source = pygame.Surface((1, 1), 0, target)
            source.set_at((0, 0), pygame.Color(13, 47, 101))
            pygame.pixelcopy.surface_to_array(pixel.get_view("2"), source)
            pygame.pixelcopy.array_to_surface(target, pixel.get_view("2"))
            p = pixel.get_at_mapped((0, 0))
            for x in range(w):
                for y in range(h):
                    self.assertEqual(target.get_at_mapped((x, y)), p)


@unittest.skipIf(IS_PYPY, "pypy having illegal instruction on mac")
class PixelCopyTestWithArrayNumpy(unittest.TestCase):
    try:
        import numpy
    except ImportError:
        __tags__ = ["ignore", "subprocess_ignore"]
    else:
        pygame.surfarray.use_arraytype("numpy")

    bitsizes = [8, 16, 32]

    test_palette = [
        (0, 0, 0, 255),
        (10, 30, 60, 255),
        (25, 75, 100, 255),
        (100, 150, 200, 255),
        (0, 100, 200, 255),
    ]

    surf_size = (10, 12)
    test_points = [
        ((0, 0), 1),
        ((4, 5), 1),
        ((9, 0), 2),
        ((5, 5), 2),
        ((0, 11), 3),
        ((4, 6), 3),
        ((9, 11), 4),
        ((5, 6), 4),
    ]

    pixels2d = {8, 16, 32}
    pixels3d = {24, 32}
    array2d = {8, 16, 24, 32}
    array3d = {24, 32}

    def __init__(self, *args, **kwds):
        import numpy

        self.dst_types = [numpy.uint8, numpy.uint16, numpy.uint32]
        try:
            self.dst_types.append(numpy.uint64)
        except AttributeError:
            pass
        pygame.display.init()
        try:
            unittest.TestCase.__init__(self, *args, **kwds)
            self.sources = [
                self._make_src_surface(8),
                self._make_src_surface(16),
                self._make_src_surface(16, srcalpha=True),
                self._make_src_surface(24),
                self._make_src_surface(32),
                self._make_src_surface(32, srcalpha=True),
            ]
        finally:
            pygame.display.quit()

    def _make_surface(self, bitsize, srcalpha=False, palette=None):
        if palette is None:
            palette = self.test_palette
        flags = 0
        if srcalpha:
            flags |= SRCALPHA
        surf = pygame.Surface(self.surf_size, flags, bitsize)
        if bitsize == 8:
            surf.set_palette([c[:3] for c in palette])
        return surf

    def _fill_surface(self, surf, palette=None):
        if palette is None:
            palette = self.test_palette
        surf.fill(palette[1], (0, 0, 5, 6))
        surf.fill(palette[2], (5, 0, 5, 6))
        surf.fill(palette[3], (0, 6, 5, 6))
        surf.fill(palette[4], (5, 6, 5, 6))

    def _make_src_surface(self, bitsize, srcalpha=False, palette=None):
        surf = self._make_surface(bitsize, srcalpha, palette)
        self._fill_surface(surf, palette)
        return surf

    def setUp(self):
        pygame.display.init()

    def tearDown(self):
        pygame.display.quit()

    def test_surface_to_array_2d(self):
        try:
            from numpy import empty, dtype
        except ImportError:
            return

        palette = self.test_palette
        alpha_color = (0, 0, 0, 128)

        dst_dims = self.surf_size
        destinations = [empty(dst_dims, t) for t in self.dst_types]
        if pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN:
            swapped_dst = empty(dst_dims, dtype(">u4"))
        else:
            swapped_dst = empty(dst_dims, dtype("<u4"))

        for surf in self.sources:
            src_bytesize = surf.get_bytesize()
            for dst in destinations:
                if dst.itemsize < src_bytesize:
                    self.assertRaises(ValueError, surface_to_array, dst, surf)
                    continue
                dst[...] = 0
                self.assertFalse(surf.get_locked())
                surface_to_array(dst, surf)
                self.assertFalse(surf.get_locked())
                for posn, i in self.test_points:
                    sp = unsigned32(surf.get_at_mapped(posn))
                    dp = dst[posn]
                    self.assertEqual(
                        dp,
                        sp,
                        "%s != %s: flags: %i"
                        ", bpp: %i, dtype: %s,  posn: %s"
                        % (
                            dp,
                            sp,
                            surf.get_flags(),
                            surf.get_bitsize(),
                            dst.dtype,
                            posn,
                        ),
                    )

                if surf.get_masks()[3]:
                    posn = (2, 1)
                    surf.set_at(posn, alpha_color)
                    surface_to_array(dst, surf)
                    sp = unsigned32(surf.get_at_mapped(posn))
                    dp = dst[posn]
                    self.assertEqual(
                        dp, sp, "%s != %s: bpp: %i" % (dp, sp, surf.get_bitsize())
                    )

            swapped_dst[...] = 0
            self.assertFalse(surf.get_locked())
            surface_to_array(swapped_dst, surf)
            self.assertFalse(surf.get_locked())
            for posn, i in self.test_points:
                sp = unsigned32(surf.get_at_mapped(posn))
                dp = swapped_dst[posn]
                self.assertEqual(
                    dp,
                    sp,
                    "%s != %s: flags: %i"
                    ", bpp: %i, dtype: %s,  posn: %s"
                    % (dp, sp, surf.get_flags(), surf.get_bitsize(), dst.dtype, posn),
                )

            if surf.get_masks()[3]:
                posn = (2, 1)
                surf.set_at(posn, alpha_color)
                self.assertFalse(surf.get_locked())
                surface_to_array(swapped_dst, surf)
                self.assertFalse(surf.get_locked())
                sp = unsigned32(surf.get_at_mapped(posn))
                dp = swapped_dst[posn]
                self.assertEqual(
                    dp, sp, "%s != %s: bpp: %i" % (dp, sp, surf.get_bitsize())
                )

    def test_surface_to_array_3d(self):
        try:
            from numpy import empty, dtype
        except ImportError:
            return

        palette = self.test_palette

        dst_dims = self.surf_size + (3,)
        destinations = [empty(dst_dims, t) for t in self.dst_types]
        if pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN:
            swapped_dst = empty(dst_dims, dtype(">u4"))
        else:
            swapped_dst = empty(dst_dims, dtype("<u4"))

        for surf in self.sources:
            src_bitsize = surf.get_bitsize()
            for dst in destinations:
                dst[...] = 0
                self.assertFalse(surf.get_locked())
                surface_to_array(dst, surf)
                self.assertFalse(surf.get_locked())
                for posn, i in self.test_points:
                    r_surf, g_surf, b_surf, a_surf = surf.get_at(posn)
                    r_arr, g_arr, b_arr = dst[posn]
                    self.assertEqual(
                        r_arr,
                        r_surf,
                        "%i != %i, color: red, flags: %i"
                        ", bpp: %i, posn: %s"
                        % (r_arr, r_surf, surf.get_flags(), surf.get_bitsize(), posn),
                    )
                    self.assertEqual(
                        g_arr,
                        g_surf,
                        "%i != %i, color: green, flags: %i"
                        ", bpp: %i, posn: %s"
                        % (r_arr, r_surf, surf.get_flags(), surf.get_bitsize(), posn),
                    )
                    self.assertEqual(
                        b_arr,
                        b_surf,
                        "%i != %i, color: blue, flags: %i"
                        ", bpp: %i, posn: %s"
                        % (r_arr, r_surf, surf.get_flags(), surf.get_bitsize(), posn),
                    )

            swapped_dst[...] = 0
            self.assertFalse(surf.get_locked())
            surface_to_array(swapped_dst, surf)
            self.assertFalse(surf.get_locked())
            for posn, i in self.test_points:
                r_surf, g_surf, b_surf, a_surf = surf.get_at(posn)
                r_arr, g_arr, b_arr = swapped_dst[posn]
                self.assertEqual(
                    r_arr,
                    r_surf,
                    "%i != %i, color: red, flags: %i"
                    ", bpp: %i, posn: %s"
                    % (r_arr, r_surf, surf.get_flags(), surf.get_bitsize(), posn),
                )
                self.assertEqual(
                    g_arr,
                    g_surf,
                    "%i != %i, color: green, flags: %i"
                    ", bpp: %i, posn: %s"
                    % (r_arr, r_surf, surf.get_flags(), surf.get_bitsize(), posn),
                )
                self.assertEqual(
                    b_arr,
                    b_surf,
                    "%i != %i, color: blue, flags: %i"
                    ", bpp: %i, posn: %s"
                    % (r_arr, r_surf, surf.get_flags(), surf.get_bitsize(), posn),
                )

    def test_map_array(self):
        try:
            from numpy import array, zeros, uint8, int32, all as alltrue
        except ImportError:
            return

        surf = pygame.Surface((1, 1), 0, 32)

        # color fill
        color = array([11, 17, 59], uint8)
        target = zeros((5, 7), int32)
        map_array(target, color, surf)

        self.assertTrue(alltrue(target == surf.map_rgb(color)))

        # array column stripes
        stripe = array([[2, 5, 7], [11, 19, 23], [37, 53, 101]], uint8)
        target = zeros((4, stripe.shape[0]), int32)
        map_array(target, stripe, surf)
        target_stripe = array([surf.map_rgb(c) for c in stripe], int32)

        self.assertTrue(alltrue(target == target_stripe))

        # array row stripes
        stripe = array(
            [[[2, 5, 7]], [[11, 19, 24]], [[10, 20, 30]], [[37, 53, 101]]], uint8
        )
        target = zeros((stripe.shape[0], 3), int32)
        map_array(target, stripe, surf)
        target_stripe = array([[surf.map_rgb(c)] for c in stripe[:, 0]], int32)

        self.assertTrue(alltrue(target == target_stripe))

        # mismatched shape
        w = 4
        h = 5
        source = zeros((w, h, 3), uint8)
        target = zeros((w,), int32)
        self.assertRaises(ValueError, map_array, target, source, surf)
        source = zeros((12, w, h + 1), uint8)
        self.assertRaises(ValueError, map_array, target, source, surf)
        source = zeros((12, w - 1, 5), uint8)
        self.assertRaises(ValueError, map_array, target, source, surf)

    ## def test_array_to_surface(self):
    ##     array_to_surface gets a good workout in the surfarray module's
    ##     unit tests under the alias blit_array.

    try:
        numpy
    except NameError:
        # Ensure no methods requiring numpy are run when
        # pixelcopy_test is '__main__'.
        del __init__
        del test_surface_to_array_2d
        del test_surface_to_array_3d
        del test_map_array
    else:
        del numpy


@unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented")
@unittest.skipIf(IS_PYPY, "pypy having illegal instruction on mac")
class PixelCopyTestWithArrayNewBuf(unittest.TestCase):
    if pygame.HAVE_NEWBUF:
        from pygame.tests.test_utils import buftools

        class Array2D(buftools.Exporter):
            def __init__(self, initializer):
                from ctypes import cast, POINTER, c_uint32

                Array2D = PixelCopyTestWithArrayNewBuf.Array2D
                super().__init__((3, 5), format="=I", strides=(20, 4))
                self.content = cast(self.buf, POINTER(c_uint32))
                for i, v in enumerate(initializer):
                    self.content[i] = v

            def __getitem__(self, key):
                byte_index = key[0] * 5 + key[1]
                if not (0 <= byte_index < 15):
                    raise IndexError("%s is out of range", key)
                return self.content[byte_index]

        class Array3D(buftools.Exporter):
            def __init__(self, initializer):
                from ctypes import cast, POINTER, c_uint8

                Array3D = PixelCopyTestWithArrayNewBuf.Array3D
                super().__init__((3, 5, 3), format="B", strides=(20, 4, 1))
                self.content = cast(self.buf, POINTER(c_uint8))
                for i, v in enumerate(initializer):
                    self.content[i] = v

            def __getitem__(self, key):
                byte_index = key[0] * 20 + key[1] * 4 + key[2]
                if not (0 <= byte_index < 60):
                    raise IndexError("%s is out of range", key)
                return self.content[byte_index]

    surface = pygame.Surface((3, 5), 0, 32)

    def setUp(self):
        surf = self.surface
        for y in range(5):
            for x in range(3):
                surf.set_at((x, y), (x + 1, 0, y + 1))

    def assertCopy2D(self, surface, array):
        for x in range(0, 3):
            for y in range(0, 5):
                self.assertEqual(surface.get_at_mapped((x, y)), array[x, y])

    def test_surface_to_array_newbuf(self):
        array = self.Array2D(range(0, 15))
        self.assertNotEqual(array.content[0], self.surface.get_at_mapped((0, 0)))
        surface_to_array(array, self.surface)
        self.assertCopy2D(self.surface, array)

    def test_array_to_surface_newbuf(self):
        array = self.Array2D(range(0, 15))
        self.assertNotEqual(array.content[0], self.surface.get_at_mapped((0, 0)))
        array_to_surface(self.surface, array)
        self.assertCopy2D(self.surface, array)

    def test_map_array_newbuf(self):
        array2D = self.Array2D([0] * 15)
        elements = [i + (255 - i << 8) + (99 << 16) for i in range(0, 15)]
        array3D = self.Array3D(elements)
        map_array(array2D, array3D, self.surface)
        for x in range(0, 3):
            for y in range(0, 5):
                p = array3D[x, y, 0], array3D[x, y, 1], array3D[x, y, 2]
                self.assertEqual(self.surface.unmap_rgb(array2D[x, y]), p)

    def test_make_surface_newbuf(self):
        array = self.Array2D(range(10, 160, 10))
        surface = make_surface(array)
        self.assertCopy2D(surface, array)

    def test_format_newbuf(self):
        Exporter = self.buftools.Exporter
        surface = self.surface
        shape = surface.get_size()
        w, h = shape
        for format in [
            "=i",
            "=I",
            "=l",
            "=L",
            "=q",
            "=Q",
            "<i",
            ">i",
            "!i",
            "1i",
            "=1i",
            "@q",
            "q",
            "4x",
            "8x",
        ]:
            surface.fill((255, 254, 253))
            exp = Exporter(shape, format=format)
            exp._buf[:] = [42] * exp.buflen
            array_to_surface(surface, exp)
            for x in range(w):
                for y in range(h):
                    self.assertEqual(surface.get_at((x, y)), (42, 42, 42, 255))
        # Some unsupported formats for array_to_surface and a 32 bit surface
        for format in ["f", "d", "?", "x", "1x", "2x", "3x", "5x", "6x", "7x", "9x"]:
            exp = Exporter(shape, format=format)
            self.assertRaises(ValueError, array_to_surface, surface, exp)


if __name__ == "__main__":
    unittest.main()
