import unittest
import sys

import pygame
from pygame import draw
from pygame import draw_py
from pygame.locals import SRCALPHA
from pygame.tests import test_utils

PY3 = sys.version_info >= (3, 0, 0)

RED = BG_RED = pygame.Color('red')
GREEN = FG_GREEN = pygame.Color('green')


def get_border_values(surface, width, height):
    """Returns a list containing lists with the values of the surface's
    borders.
    """
    border_top = [surface.get_at((x, 0)) for x in range(width)]
    border_left = [surface.get_at((0, y)) for y in range(height)]
    border_right = [
        surface.get_at((width - 1, y)) for y in range(height)]
    border_bottom = [
        surface.get_at((x, height - 1)) for x in range(width)]

    return [border_top, border_left, border_right, border_bottom]


def corners(surface):
    """Returns a tuple with the corner positions of the given surface.

    Clockwise from the top left corner.
    """
    width, height = surface.get_size()
    return ((0, 0), (width - 1, 0), (width - 1, height - 1), (0, height - 1))


def border_pos_and_color(surface):
    """Yields each border position and its color for a given surface.

    Clockwise from the top left corner.
    """
    width, height = surface.get_size()
    right, bottom = width - 1, height - 1

    # Top edge.
    for x in range(width):
        pos = (x, 0)
        yield pos, surface.get_at(pos)

    # Right edge.
    # Top right done in top edge loop.
    for y in range(1, height):
        pos = (right, y)
        yield pos, surface.get_at(pos)

    # Bottom edge.
    # Bottom right done in right edge loop.
    for x in range(right - 1, -1, -1):
        pos = (x, bottom)
        yield pos, surface.get_at(pos)

    # Left edge.
    # Bottom left done in bottom edge loop. Top left done in top edge loop.
    for y in range(bottom - 1, 0, -1):
        pos = (0, y)
        yield pos, surface.get_at(pos)


class DrawTestCase(unittest.TestCase):
    """Base class to test draw module functions."""
    draw_rect    = staticmethod(draw.rect)
    draw_polygon = staticmethod(draw.polygon)
    draw_circle  = staticmethod(draw.circle)
    draw_ellipse = staticmethod(draw.ellipse)
    draw_arc     = staticmethod(draw.arc)
    draw_line    = staticmethod(draw.line)
    draw_lines   = staticmethod(draw.lines)
    draw_aaline  = staticmethod(draw.aaline)
    draw_aalines = staticmethod(draw.aalines)


class PythonDrawTestCase(unittest.TestCase):
    """Base class to test draw_py module functions."""
    # draw_py is currently missing some functions.
    #draw_rect    = staticmethod(draw_py.draw_rect)
    draw_polygon = staticmethod(draw_py.draw_polygon)
    #draw_circle  = staticmethod(draw_py.draw_circle)
    #draw_ellipse = staticmethod(draw_py.draw_ellipse)
    #draw_arc     = staticmethod(draw_py.draw_arc)
    draw_line    = staticmethod(draw_py.draw_line)
    draw_lines   = staticmethod(draw_py.draw_lines)
    draw_aaline  = staticmethod(draw_py.draw_aaline)
    draw_aalines = staticmethod(draw_py.draw_aalines)


### Ellipse Testing ###########################################################

class DrawEllipseMixin(object):
    """Mixin tests for drawing ellipses.

    This class contains all the general ellipse drawing tests.
    """

    def test_ellipse(self):
        """Tests ellipses of differing sizes on surfaces of differing sizes.

        Checks if the number of sides touching the border of the surface is
        correct.
        """
        left_top = [(0, 0), (1, 0), (0, 1), (1, 1)]
        sizes = [(4, 4), (5, 4), (4, 5), (5, 5)]
        color = (1, 13, 24, 255)

        def same_size(width, height, border_width):
            """Test for ellipses with the same size as the surface."""
            surface = pygame.Surface((width, height))

            self.draw_ellipse(surface, color, (0, 0, width, height),
                              border_width)

            # For each of the four borders check if it contains the color
            borders = get_border_values(surface, width, height)
            for border in borders:
                self.assertTrue(color in border)

        def not_same_size(width, height, border_width, left, top):
            """Test for ellipses that aren't the same size as the surface."""
            surface = pygame.Surface((width, height))

            self.draw_ellipse(surface, color,
                              (left, top, width - 1, height - 1), border_width)

            borders = get_border_values(surface, width, height)

            # Check if two sides of the ellipse are touching the border
            sides_touching = [
                color in border for border in borders].count(True)
            self.assertEqual(sides_touching, 2)

        for width, height in sizes:
            for border_width in (0, 1):
                same_size(width, height, border_width)
                for left, top in left_top:
                    not_same_size(width, height, border_width, left, top)

    def _check_1_pixel_sized_ellipse(self, surface, collide_rect,
                                     surface_color, ellipse_color):
        # Helper method to check the surface for 1 pixel wide and/or high
        # ellipses.
        surf_w, surf_h = surface.get_size()

        surface.lock()  # For possible speed up.

        for pos in ((x, y) for y in range(surf_h) for x in range(surf_w)):
            # Since the ellipse is just a line we can use a rect to help find
            # where it is expected to be drawn.
            if collide_rect.collidepoint(pos):
                expected_color = ellipse_color
            else:
                expected_color = surface_color

            self.assertEqual(surface.get_at(pos), expected_color,
                'collide_rect={}, pos={}'.format(collide_rect, pos))

        surface.unlock()

    def test_ellipse__1_pixel_width(self):
        """Ensures an ellipse with a width of 1 is drawn correctly.

        An ellipse with a width of 1 pixel is a vertical line.
        """
        ellipse_color = pygame.Color('red')
        surface_color = pygame.Color('black')
        surf_w, surf_h = 10, 20

        surface = pygame.Surface((surf_w, surf_h))
        rect = pygame.Rect((0, 0), (1, 0))
        collide_rect = rect.copy()

        # Calculate some positions.
        off_left = -1
        off_right = surf_w
        off_bottom = surf_h
        center_x = surf_w // 2
        center_y = surf_h // 2

        # Test some even and odd heights.
        for ellipse_h in range(6, 10):
            # The ellipse is drawn on the edge of the rect so collide_rect
            # needs +1 height to track where it's drawn.
            collide_rect.h = ellipse_h + 1
            rect.h = ellipse_h

            # Calculate some variable positions.
            off_top = -(ellipse_h + 1)
            half_off_top = -(ellipse_h // 2)
            half_off_bottom = surf_h - (ellipse_h // 2)

            # Draw the ellipse in different positions: fully on-surface,
            # partially off-surface, and fully off-surface.
            positions = ((off_left, off_top),
                         (off_left, half_off_top),
                         (off_left, center_y),
                         (off_left, half_off_bottom),
                         (off_left, off_bottom),

                         (center_x, off_top),
                         (center_x, half_off_top),
                         (center_x, center_y),
                         (center_x, half_off_bottom),
                         (center_x, off_bottom),

                         (off_right, off_top),
                         (off_right, half_off_top),
                         (off_right, center_y),
                         (off_right, half_off_bottom),
                         (off_right, off_bottom))

            for rect_pos in positions:
                surface.fill(surface_color)  # Clear before each draw.
                rect.topleft = rect_pos
                collide_rect.topleft = rect_pos

                self.draw_ellipse(surface, ellipse_color, rect)

                self._check_1_pixel_sized_ellipse(surface, collide_rect,
                                                  surface_color, ellipse_color)

    def test_ellipse__1_pixel_width_spanning_surface(self):
        """Ensures an ellipse with a width of 1 is drawn correctly
        when spanning the height of the surface.

        An ellipse with a width of 1 pixel is a vertical line.
        """
        ellipse_color = pygame.Color('red')
        surface_color = pygame.Color('black')
        surf_w, surf_h = 10, 20

        surface = pygame.Surface((surf_w, surf_h))
        rect = pygame.Rect((0, 0), (1, surf_h + 2))  # Longer than the surface.

        # Draw the ellipse in different positions: on-surface and off-surface.
        positions = ((-1,          -1),  # (off_left,   off_top)
                     (0,           -1),  # (left_edge,  off_top)
                     (surf_w // 2, -1),  # (center_x,   off_top)
                     (surf_w - 1,  -1),  # (right_edge, off_top)
                     (surf_w,      -1))  # (off_right,  off_top)

        for rect_pos in positions:
            surface.fill(surface_color)  # Clear before each draw.
            rect.topleft = rect_pos

            self.draw_ellipse(surface, ellipse_color, rect)

            self._check_1_pixel_sized_ellipse(surface, rect, surface_color,
                                              ellipse_color)

    def test_ellipse__1_pixel_height(self):
        """Ensures an ellipse with a height of 1 is drawn correctly.

        An ellipse with a height of 1 pixel is a horizontal line.
        """
        ellipse_color = pygame.Color('red')
        surface_color = pygame.Color('black')
        surf_w, surf_h = 20, 10

        surface = pygame.Surface((surf_w, surf_h))
        rect = pygame.Rect((0, 0), (0, 1))
        collide_rect = rect.copy()

        # Calculate some positions.
        off_right = surf_w
        off_top = -1
        off_bottom = surf_h
        center_x = surf_w // 2
        center_y = surf_h // 2

        # Test some even and odd widths.
        for ellipse_w in range(6, 10):
            # The ellipse is drawn on the edge of the rect so collide_rect
            # needs +1 width to track where it's drawn.
            collide_rect.w = ellipse_w + 1
            rect.w = ellipse_w

            # Calculate some variable positions.
            off_left = -(ellipse_w + 1)
            half_off_left = -(ellipse_w // 2)
            half_off_right = surf_w - (ellipse_w // 2)

            # Draw the ellipse in different positions: fully on-surface,
            # partially off-surface, and fully off-surface.
            positions = ((off_left,       off_top),
                         (half_off_left,  off_top),
                         (center_x,       off_top),
                         (half_off_right, off_top),
                         (off_right,      off_top),

                         (off_left,       center_y),
                         (half_off_left,  center_y),
                         (center_x,       center_y),
                         (half_off_right, center_y),
                         (off_right,      center_y),

                         (off_left,       off_bottom),
                         (half_off_left,  off_bottom),
                         (center_x,       off_bottom),
                         (half_off_right, off_bottom),
                         (off_right,      off_bottom))

            for rect_pos in positions:
                surface.fill(surface_color)  # Clear before each draw.
                rect.topleft = rect_pos
                collide_rect.topleft = rect_pos

                self.draw_ellipse(surface, ellipse_color, rect)

                self._check_1_pixel_sized_ellipse(surface, collide_rect,
                                                  surface_color, ellipse_color)

    def test_ellipse__1_pixel_height_spanning_surface(self):
        """Ensures an ellipse with a height of 1 is drawn correctly
        when spanning the width of the surface.

        An ellipse with a height of 1 pixel is a horizontal line.
        """
        ellipse_color = pygame.Color('red')
        surface_color = pygame.Color('black')
        surf_w, surf_h = 20, 10

        surface = pygame.Surface((surf_w, surf_h))
        rect = pygame.Rect((0, 0), (surf_w + 2, 1))  # Wider than the surface.

        # Draw the ellipse in different positions: on-surface and off-surface.
        positions = ((-1, -1),           # (off_left, off_top)
                     (-1, 0),            # (off_left, top_edge)
                     (-1, surf_h // 2),  # (off_left, center_y)
                     (-1, surf_h - 1),   # (off_left, bottom_edge)
                     (-1, surf_h))       # (off_left, off_bottom)

        for rect_pos in positions:
            surface.fill(surface_color)  # Clear before each draw.
            rect.topleft = rect_pos

            self.draw_ellipse(surface, ellipse_color, rect)

            self._check_1_pixel_sized_ellipse(surface, rect, surface_color,
                                              ellipse_color)

    def test_ellipse__1_pixel_width_and_height(self):
        """Ensures an ellipse with a width and height of 1 is drawn correctly.

        An ellipse with a width and height of 1 pixel is a single pixel.
        """
        ellipse_color = pygame.Color('red')
        surface_color = pygame.Color('black')
        surf_w, surf_h = 10, 10

        surface = pygame.Surface((surf_w, surf_h))
        rect = pygame.Rect((0, 0), (1, 1))

        # Calculate some positions.
        off_left = -1
        off_right = surf_w
        off_top = -1
        off_bottom = surf_h
        left_edge = 0
        right_edge = surf_w - 1
        top_edge = 0
        bottom_edge = surf_h - 1
        center_x = surf_w // 2
        center_y = surf_h // 2

        # Draw the ellipse in different positions: center surface,
        # top/bottom/left/right edges, and off-surface.
        positions = ((off_left, off_top),
                     (off_left, top_edge),
                     (off_left, center_y),
                     (off_left, bottom_edge),
                     (off_left, off_bottom),

                     (left_edge, off_top),
                     (left_edge, top_edge),
                     (left_edge, center_y),
                     (left_edge, bottom_edge),
                     (left_edge, off_bottom),

                     (center_x, off_top),
                     (center_x, top_edge),
                     (center_x, center_y),
                     (center_x, bottom_edge),
                     (center_x, off_bottom),

                     (right_edge, off_top),
                     (right_edge, top_edge),
                     (right_edge, center_y),
                     (right_edge, bottom_edge),
                     (right_edge, off_bottom),

                     (off_right, off_top),
                     (off_right, top_edge),
                     (off_right, center_y),
                     (off_right, bottom_edge),
                     (off_right, off_bottom))

        for rect_pos in positions:
            surface.fill(surface_color)  # Clear before each draw.
            rect.topleft = rect_pos

            self.draw_ellipse(surface, ellipse_color, rect)

            self._check_1_pixel_sized_ellipse(surface, rect, surface_color,
                                              ellipse_color)


class DrawEllipseTest(DrawEllipseMixin, DrawTestCase):
    """Test draw module function ellipse.

    This class inherits the general tests from DrawEllipseMixin. It is also
    the class to add any draw.ellipse specific tests to.
    """


@unittest.skip('draw_py.draw_ellipse not supported yet')
class PythonDrawEllipseTest(DrawEllipseMixin, PythonDrawTestCase):
    """Test draw_py module function draw_ellipse.

    This class inherits the general tests from DrawEllipseMixin. It is also
    the class to add any draw_py.draw_ellipse specific tests to.
    """


### Line Testing ##############################################################

class LineMixin(object):
    """Mixin test for drawing lines and aalines.

    This class contains all the general line/lines/aaline/aalines drawing
    tests.
    """

    def setUp(self):
        self._colors = ((0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255),
                        (255, 255, 0), (255, 0, 255), (0, 255, 255),
                        (255, 255, 255))

        # Create some surfaces with different sizes, depths, and flags.
        self._surfaces = []
        for size in ((49, 49), (50, 50)):
            for depth in (8, 16, 24, 32):
                for flags in (0, SRCALPHA):
                    surface = pygame.display.set_mode(size, flags, depth)
                    self._surfaces.append(surface)
                    self._surfaces.append(surface.convert_alpha())

    def test_line__color(self):
        """Tests if the line drawn is the correct color."""
        pos = (0, 0)
        for surface in self._surfaces:
            for expected_color in self._colors:
                self.draw_line(surface, expected_color, pos, (1, 0))

                self.assertEqual(surface.get_at(pos), expected_color,
                                 'pos={}'.format(pos))

    def test_aaline__color(self):
        """Tests if the aaline drawn is the correct color."""
        pos = (0, 0)
        for surface in self._surfaces:
            for expected_color in self._colors:
                self.draw_aaline(surface, expected_color, pos, (1, 0))

                self.assertEqual(surface.get_at(pos), expected_color,
                                 'pos={}'.format(pos))

    def test_line__gaps(self):
        """Tests if the line drawn contains any gaps."""
        expected_color = (255, 255, 255)
        for surface in self._surfaces:
            width = surface.get_width()
            self.draw_line(surface, expected_color, (0, 0), (width - 1, 0))

            for x in range(width):
                pos = (x, 0)
                self.assertEqual(surface.get_at(pos), expected_color,
                                 'pos={}'.format(pos))

    def test_aaline__gaps(self):
        """Tests if the aaline drawn contains any gaps.

        See: #512
        """
        expected_color = (255, 255, 255)
        for surface in self._surfaces:
            width = surface.get_width()
            self.draw_aaline(surface, expected_color, (0, 0), (width - 1, 0))

            for x in range(width):
                pos = (x, 0)
                self.assertEqual(surface.get_at(pos), expected_color,
                                 'pos={}'.format(pos))

    def test_lines__color(self):
        """Tests if the lines drawn are the correct color.

        Draws lines around the border of the given surface and checks if all
        borders of the surface only contain the given color.
        """
        for surface in self._surfaces:
            for expected_color in self._colors:
                self.draw_lines(surface, expected_color, True,
                                corners(surface))

                for pos, color in border_pos_and_color(surface):
                    self.assertEqual(color, expected_color,
                                     'pos={}'.format(pos))

    def test_aalines__color(self):
        """Tests if the aalines drawn are the correct color.

        Draws aalines around the border of the given surface and checks if all
        borders of the surface only contain the given color.
        """
        for surface in self._surfaces:
            for expected_color in self._colors:
                self.draw_aalines(surface, expected_color, True,
                                  corners(surface))

                for pos, color in border_pos_and_color(surface):
                    self.assertEqual(color, expected_color,
                                     'pos={}'.format(pos))

    def test_lines__gaps(self):
        """Tests if the lines drawn contain any gaps.

        Draws lines around the border of the given surface and checks if
        all borders of the surface contain any gaps.
        """
        expected_color = (255, 255, 255)
        for surface in self._surfaces:
            self.draw_lines(surface, expected_color, True, corners(surface))

            for pos, color in border_pos_and_color(surface):
                self.assertEqual(color, expected_color, 'pos={}'.format(pos))

    def test_aalines__gaps(self):
        """Tests if the aalines drawn contain any gaps.

        Draws aalines around the border of the given surface and checks if
        all borders of the surface contain any gaps.

        See: #512
        """
        expected_color = (255, 255, 255)
        for surface in self._surfaces:
            self.draw_aalines(surface, expected_color, True, corners(surface))

            for pos, color in border_pos_and_color(surface):
                self.assertEqual(color, expected_color, 'pos={}'.format(pos))


class PythonDrawLineTest(LineMixin, DrawTestCase):
    """Test draw_py module functions: line, lines, aaline, and aalines.

    This class inherits the general tests from LineMixin. It is also the class
    to add any draw_py.draw_line/lines/aaline/aalines specific tests to.
    """


class DrawLineTest(LineMixin, PythonDrawTestCase):
    """Test draw module functions: line, lines, aaline, and aalines.

    This class inherits the general tests from LineMixin. It is also the class
    to add any draw.line/lines/aaline/aalines specific tests to.
    """

    def test_path_data_validation(self):
        """Test validation of multi-point drawing methods.

        See bug #521
        """
        surf = pygame.Surface((5, 5))
        rect = pygame.Rect(0, 0, 5, 5)
        bad_values = ('text', b'bytes', 1 + 1j,  # string, bytes, complex,
                       object(), (lambda x: x))  # object, function
        bad_points = list(bad_values) + [(1,) , (1, 2, 3)] # wrong tuple length
        bad_points.extend((1, v) for v in bad_values)  # one wrong value
        good_path = [(1, 1), (1, 3), (3, 3), (3, 1)]
        # A) draw.lines
        check_pts = [(x, y) for x in range(5) for y in range(5)]
        for method, is_polgon in ((draw.lines, 0), (draw.aalines, 0),
                                  (draw.polygon, 1)):
            for val in bad_values:
                # 1. at the beginning
                draw.rect(surf, RED, rect, 0)
                with self.assertRaises(TypeError):
                    if is_polgon:
                        method(surf, GREEN, [val] + good_path, 0)
                    else:
                        method(surf, GREEN, True, [val] + good_path)
                # make sure, nothing was drawn :
                self.assertTrue(all(surf.get_at(pt) == RED for pt in check_pts))
                # 2. not at the beginning (was not checked)
                draw.rect(surf, RED, rect, 0)
                with self.assertRaises(TypeError):
                    path = good_path[:2] + [val] + good_path[2:]
                    if is_polgon:
                        method(surf, GREEN, path, 0)
                    else:
                        method(surf, GREEN, True, path)
                # make sure, nothing was drawn :
                self.assertTrue(all(surf.get_at(pt) == RED for pt in check_pts))

    def _test_endianness(self, draw_func):
        """ test color component order
        """
        depths = 24, 32
        for depth in depths:
            surface = pygame.Surface((5, 3), 0, depth)
            surface.fill(pygame.Color(0,0,0))
            draw_func(surface, pygame.Color(255, 0, 0), (0, 1), (2, 1), 1)
            self.assertGreater(surface.get_at((1, 1)).r, 0, 'there should be red here')
            surface.fill(pygame.Color(0,0,0))
            draw_func(surface, pygame.Color(0, 0, 255), (0, 1), (2, 1), 1)
            self.assertGreater(surface.get_at((1, 1)).b, 0, 'there should be blue here')

    def test_line_endianness(self):
        """ test color component order
        """
        self._test_endianness(draw.line)

    def test_aaline_endianness(self):
        """ test color component order
        """
        self._test_endianness(draw.aaline)

    def test_color_validation(self):
        surf = pygame.Surface((10, 10))
        colors = 123456, (1, 10, 100), RED # but not '#ab12df' or 'red' ...
        points = ((0, 0), (1, 1), (1, 0))
        # 1. valid colors
        for col in colors:
            draw.line(surf, col, (0, 0), (1, 1))
            draw.aaline(surf, col, (0, 0), (1, 1))
            draw.aalines(surf, col, True, points)
            draw.lines(surf, col, True, points)
            draw.arc(surf, col, pygame.Rect(0, 0, 3, 3), 15, 150)
            draw.ellipse(surf, col, pygame.Rect(0, 0, 3, 6), 1)
            draw.circle(surf, col, (7, 3), 2)
            draw.polygon(surf, col, points, 0)
        # 2. invalid colors
        for col in ('invalid', 1.256, object(), None, '#ab12df', 'red'):
            with self.assertRaises(TypeError):
                draw.line(surf, col, (0, 0), (1, 1))
            with self.assertRaises(TypeError):
                draw.aaline(surf, col, (0, 0), (1, 1))
            with self.assertRaises(TypeError):
                draw.aalines(surf, col, True, points)
            with self.assertRaises(TypeError):
                draw.lines(surf, col, True, points)
            with self.assertRaises(TypeError):
                draw.arc(surf, col, pygame.Rect(0, 0, 3, 3), 15, 150)
            with self.assertRaises(TypeError):
                draw.ellipse(surf, col, pygame.Rect(0, 0, 3, 6), 1)
            with self.assertRaises(TypeError):
                draw.circle(surf, col, (7, 3), 2)
            with self.assertRaises(TypeError):
                draw.polygon(surf, col, points, 0)


# Using a separate class to test line anti-aliasing.
class AntiAliasedLineMixin(object):
    """Mixin tests for line anti-aliasing.

    This class contains all the general anti-aliasing line drawing tests.
    """

    def setUp(self):
        self.surface = pygame.Surface((10, 10))
        draw.rect(self.surface, BG_RED, (0, 0, 10, 10), 0)

    def _check_antialiasing(self, from_point, to_point, should, check_points,
                            set_endpoints=True):
        """Draw a line between two points and check colors of check_points."""
        if set_endpoints:
            should[from_point] = should[to_point] = FG_GREEN

        def check_one_direction(from_point, to_point, should):
            self.draw_aaline(self.surface, FG_GREEN, from_point, to_point,
                             True)

            for pt in check_points:
                color = should.get(pt, BG_RED)
                if PY3: # "subTest" is sooo helpful, but does not exist in PY2
                    with self.subTest(from_pt=from_point, pt=pt, to=to_point):
                        self.assertEqual(self.surface.get_at(pt), color)
                else:
                    self.assertEqual(self.surface.get_at(pt), color)
            # reset
            draw.rect(self.surface, BG_RED, (0, 0, 10, 10), 0)

        # it is important to test also opposite direction, the algorithm
        # is (#512) or was not symmetric
        check_one_direction(from_point, to_point, should)
        if from_point != to_point:
            check_one_direction(to_point, from_point, should)

    def test_short_non_antialiased_lines(self):
        """test very short not anti aliased lines in all directions."""
        # Horizontal, vertical and diagonal lines should not be anti-aliased,
        # even with draw.aaline ...
        check_points = [(i, j) for i in range(3, 8) for j in range(3, 8)]

        def check_both_directions(from_pt, to_pt, other_points):
            should = {pt: FG_GREEN for pt in other_points}
            self._check_antialiasing(from_pt, to_pt, should, check_points)

        # 0. one point
        check_both_directions((5, 5), (5, 5), [])
        # 1. horizontal
        check_both_directions((4, 7), (5, 7), [])
        check_both_directions((5, 4), (7, 4), [(6, 4)])

        # 2. vertical
        check_both_directions((5, 5), (5, 6), [])
        check_both_directions((6, 4), (6, 6), [(6, 5)])
        # 3. diagonals
        check_both_directions((5, 5), (6, 6), [])
        check_both_directions((5, 5), (7, 7), [(6, 6)])
        check_both_directions((5, 6), (6, 5), [])
        check_both_directions((6, 4), (4, 6), [(5, 5)])

    def test_short_line_anti_aliasing(self):
        check_points = [(i, j) for i in range(3, 8) for j in range(3, 8)]

        def check_both_directions(from_pt, to_pt, should):
            self._check_antialiasing(from_pt, to_pt, should, check_points)

        # lets say dx = abs(x0 - x1) ; dy = abs(y0 - y1)
        brown = (127, 127, 0)
        # dy / dx = 0.5
        check_both_directions((4, 4), (6, 5), {(5, 4): brown, (5, 5): brown})
        check_both_directions((4, 5), (6, 4), {(5, 4): brown, (5, 5): brown})
        # dy / dx = 2
        check_both_directions((4, 4), (5, 6), {(4, 5): brown, (5, 5): brown})
        check_both_directions((5, 4), (4, 6), {(4, 5): brown, (5, 5): brown})

        # some little longer lines; so we need to check more points:
        check_points = [(i, j) for i in range(2, 9) for j in range(2, 9)]
        # dy / dx = 0.25
        reddish = (191, 63, 0)
        greenish = (63, 191, 0)
        should = {(4, 3): greenish, (5, 3): brown, (6, 3): reddish,
                  (4, 4): reddish,  (5, 4): brown, (6, 4): greenish}
        check_both_directions((3, 3), (7, 4), should)
        should = {(4, 3): reddish,  (5, 3): brown, (6, 3): greenish,
                  (4, 4): greenish, (5, 4): brown, (6, 4): reddish}
        check_both_directions((3, 4), (7, 3), should)
        # dy / dx = 4
        should = {(4, 4): greenish, (4, 5): brown, (4, 6): reddish,
                  (5, 4): reddish,  (5, 5): brown, (5, 6): greenish,
                 }
        check_both_directions((4, 3), (5, 7), should)
        should = {(4, 4): reddish,  (4, 5): brown, (4, 6): greenish,
                  (5, 4): greenish, (5, 5): brown, (5, 6): reddish}
        check_both_directions((5, 3), (4, 7), should)

    def test_anti_aliasing_float_coordinates(self):
        """Float coordinates should be blended smoothly."""
        check_points = [(i, j) for i in range(5) for j in range(5)]
        brown = (127, 127, 0)

        # 0. identical point : current implementation does no smoothing...
        expected = {(1, 2): FG_GREEN}
        self._check_antialiasing((1.5, 2), (1.5, 2), expected,
                                 check_points, set_endpoints=False)
        expected = {(2, 2): FG_GREEN}
        self._check_antialiasing((2.5, 2.7), (2.5, 2.7), expected,
                                 check_points, set_endpoints=False)

        # 1. horizontal lines
        #  a) blend endpoints
        expected = {(1, 2): brown, (2, 2): FG_GREEN}
        self._check_antialiasing((1.5, 2), (2, 2), expected,
                                 check_points, set_endpoints=False)
        expected = {(1, 2): brown, (2, 2): FG_GREEN, (3, 2): brown}
        self._check_antialiasing((1.5, 2), (2.5, 2), expected,
                                 check_points, set_endpoints=False)
        expected = {(2, 2): brown, (1, 2): FG_GREEN, }
        self._check_antialiasing((1, 2), (1.5, 2), expected,
                                 check_points, set_endpoints=False)
        expected = {(1, 2): brown, (2, 2):  (63, 191, 0)}
        self._check_antialiasing((1.5, 2), (1.75, 2), expected,
                                 check_points, set_endpoints=False)

        #  b) blend y-coordinate
        expected = {(x, y): brown for x  in range(2, 5) for y in (1, 2)}
        self._check_antialiasing((2, 1.5), (4, 1.5), expected,
                                 check_points, set_endpoints=False)

        # 2. vertical lines
        #  a) blend endpoints
        expected = {(2, 1): brown, (2, 2): FG_GREEN, (2, 3): brown}
        self._check_antialiasing((2, 1.5), (2, 2.5), expected,
                                 check_points, set_endpoints=False)
        expected = {(2, 1): brown, (2, 2):  (63, 191, 0)}
        self._check_antialiasing((2, 1.5), (2, 1.75), expected,
                                 check_points, set_endpoints=False)
        #  b) blend x-coordinate
        expected = {(x, y): brown for x in (1, 2) for y in range(2, 5)}
        self._check_antialiasing((1.5, 2), (1.5, 4), expected,
                                 check_points, set_endpoints=False)
        # 3. diagonal lines
        #  a) blend endpoints
        expected = {(1, 1): brown, (2, 2): FG_GREEN, (3, 3): brown}
        self._check_antialiasing((1.5, 1.5), (2.5, 2.5), expected,
                                 check_points, set_endpoints=False)
        expected = {(3, 1): brown, (2, 2): FG_GREEN, (1, 3): brown}
        self._check_antialiasing((2.5, 1.5), (1.5, 2.5), expected,
                                 check_points, set_endpoints=False)
        #  b) blend sidewards
        expected = {(2, 1): brown, (2, 2): brown, (3, 2): brown, (3, 3): brown}
        self._check_antialiasing((2, 1.5), (3, 2.5), expected,
                                 check_points, set_endpoints=False)

        reddish = (191, 63, 0)
        greenish = (63, 191, 0)
        expected = {(2, 1): greenish, (2, 2): reddish,
                    (3, 2): greenish, (3, 3): reddish,
                    (4, 3): greenish, (4, 4): reddish}
        self._check_antialiasing((2, 1.25), (4, 3.25), expected,
                                 check_points, set_endpoints=False)

    def test_anti_aliasing_at_and_outside_the_border(self):
        check_points = [(i, j) for i in range(10) for j in range(10)]

        reddish = (191, 63, 0)
        brown = (127, 127, 0)
        greenish = (63, 191, 0)
        from_point, to_point = (3, 3), (7, 4)
        should = {(4, 3): greenish, (5, 3): brown, (6, 3): reddish,
                  (4, 4): reddish,  (5, 4): brown, (6, 4): greenish}

        for dx, dy in ((-4, 0), (4, 0), # moved to left and right borders
                       (0, -5), (0, -4), (0, -3), # upper border
                       (0, 5), (0,  6), (0,  7), # lower border
                       (-4, -4), (-4, -3), (-3, -4)):  # upper left corner
            first = from_point[0] + dx, from_point[1] + dy
            second = to_point[0] + dx,  to_point[1] + dy
            expected = {(x + dx, y + dy): color
                        for (x, y), color in should.items()}
            self._check_antialiasing(first, second, expected, check_points)


@unittest.expectedFailure
class AntiAliasingLineTest(AntiAliasedLineMixin, DrawTestCase):
    """Test anti-aliasing for draw.

    This class inherits the general tests from AntiAliasedLineMixin. It is
    also the class to add any anti-aliasing draw specific tests to.
    """

class PythonAntiAliasingLineTest(AntiAliasedLineMixin, PythonDrawTestCase):
    """Test anti-aliasing for draw_py.

    This class inherits the general tests from AntiAliasedLineMixin. It is
    also the class to add any anti-aliasing draw_py specific tests to.
    """


### Draw Module Testing #######################################################

# These tests should eventually be moved to their appropriate mixin/class.
class DrawModuleTest(unittest.TestCase):

    def setUp(self):
        (self.surf_w, self.surf_h) = self.surf_size = (320, 200)
        self.surf = pygame.Surface(self.surf_size, pygame.SRCALPHA)
        self.color = (1, 13, 24, 205)

    def test_rect__fill(self):
        # __doc__ (as of 2008-06-25) for pygame.draw.rect:

          # pygame.draw.rect(Surface, color, Rect, width=0): return Rect
          # draw a rectangle shape

        rect = pygame.Rect(10, 10, 25, 20)
        drawn = draw.rect(self.surf, self.color, rect, 0)

        self.assertEqual(drawn, rect)

        # Should be colored where it's supposed to be
        for pt in test_utils.rect_area_pts(rect):
            color_at_pt = self.surf.get_at(pt)
            self.assertEqual(color_at_pt, self.color)

        # And not where it shouldn't
        for pt in test_utils.rect_outer_bounds(rect):
            color_at_pt = self.surf.get_at(pt)
            self.assertNotEqual(color_at_pt, self.color)

        # Issue #310: Cannot draw rectangles that are 1 pixel high
        bgcolor = pygame.Color('black')
        self.surf.fill(bgcolor)
        hrect = pygame.Rect(1, 1, self.surf_w - 2, 1)
        vrect = pygame.Rect(1, 3, 1, self.surf_h - 4)
        drawn = draw.rect(self.surf, self.color, hrect, 0)
        self.assertEqual(drawn, hrect)
        x, y = hrect.topleft
        w, h = hrect.size
        self.assertEqual(self.surf.get_at((x - 1, y)), bgcolor)
        self.assertEqual(self.surf.get_at((x + w, y)), bgcolor)
        for i in range(x, x + w):
            self.assertEqual(self.surf.get_at((i, y)), self.color)
        drawn = draw.rect(self.surf, self.color, vrect, 0)
        self.assertEqual(drawn, vrect)
        x, y = vrect.topleft
        w, h = vrect.size
        self.assertEqual(self.surf.get_at((x, y - 1)), bgcolor)
        self.assertEqual(self.surf.get_at((x, y + h)), bgcolor)
        for i in range(y, y + h):
            self.assertEqual(self.surf.get_at((x, i)), self.color)

    def test_rect__one_pixel_lines(self):
        rect = pygame.Rect(10, 10, 56, 20)

        drawn = draw.rect(self.surf, self.color, rect, 1)
        self.assertEqual(drawn, rect)

        # Should be colored where it's supposed to be
        for pt in test_utils.rect_perimeter_pts(drawn):
            color_at_pt = self.surf.get_at(pt)
            self.assertEqual(color_at_pt, self.color)

        # And not where it shouldn't
        for pt in test_utils.rect_outer_bounds(drawn):
            color_at_pt = self.surf.get_at(pt)
            self.assertNotEqual(color_at_pt, self.color)

    # See DrawLineTest class for additional draw.line() and draw.aaline()
    # tests.
    def test_line(self):
        # (l, t), (l, t)
        drawn = draw.line(self.surf, self.color, (1, 0), (200, 0))
        self.assertEqual(drawn.right, 201,
                     "end point arg should be (or at least was) inclusive")

        # Should be colored where it's supposed to be
        for pt in test_utils.rect_area_pts(drawn):
            self.assertEqual(self.surf.get_at(pt), self.color)

        # And not where it shouldn't
        for pt in test_utils.rect_outer_bounds(drawn):
            self.assertNotEqual(self.surf.get_at(pt), self.color)

        # Line width greater that 1
        line_width = 2
        offset = 5
        a = (offset, offset)
        b = (self.surf_size[0] - offset, a[1])
        c = (a[0], self.surf_size[1] - offset)
        d = (b[0], c[1])
        e = (a[0] + offset, c[1])
        f = (b[0], c[0] + 5)
        lines = [(a, d), (b, c), (c, b), (d, a),
                 (a, b), (b, a), (a, c), (c, a),
                 (a, e), (e, a), (a, f), (f, a),
                 (a, a),]
        for p1, p2 in lines:
            msg = "%s - %s" % (p1, p2)
            if p1[0] <= p2[0]:
                plow = p1
                phigh = p2
            else:
                plow = p2
                phigh = p1
            self.surf.fill((0, 0, 0))
            rec = draw.line(self.surf, (255, 255, 255), p1, p2, line_width)
            xinc = yinc = 0
            if abs(p1[0] - p2[0]) > abs(p1[1] - p2[1]):
                yinc = 1
            else:
                xinc = 1
            for i in range(line_width):
                p = (p1[0] + xinc * i, p1[1] + yinc * i)
                self.assertEqual(self.surf.get_at(p), (255, 255, 255), msg)
                p = (p2[0] + xinc * i, p2[1] + yinc * i)
                self.assertEqual(self.surf.get_at(p), (255, 255, 255), msg)
            p = (plow[0] - 1, plow[1])
            self.assertEqual(self.surf.get_at(p), (0, 0, 0), msg)
            p = (plow[0] + xinc * line_width, plow[1] + yinc * line_width)
            self.assertEqual(self.surf.get_at(p), (0, 0, 0), msg)
            p = (phigh[0] + xinc * line_width, phigh[1] + yinc * line_width)
            self.assertEqual(self.surf.get_at(p), (0, 0, 0), msg)
            if p1[0] < p2[0]:
                rx = p1[0]
            else:
                rx = p2[0]
            if p1[1] < p2[1]:
                ry = p1[1]
            else:
                ry = p2[1]
            w = abs(p2[0] - p1[0]) + 1 + xinc * (line_width - 1)
            h = abs(p2[1] - p1[1]) + 1 + yinc * (line_width - 1)
            msg += ", %s" % (rec,)
            self.assertEqual(rec, (rx, ry, w, h), msg)

    @unittest.expectedFailure
    def test_line_for_gaps(self):
        """ |tags: ignore|
        """
        # __doc__ (as of 2008-06-25) for pygame.draw.line:

          # pygame.draw.line(Surface, color, start_pos, end_pos, width=1): return Rect
          # draw a straight line segment

        # This checks bug Thick Line Bug #448

        width = 200
        height = 200
        surf = pygame.Surface((width, height), pygame.SRCALPHA)

        def white_surrounded_pixels(x, y):
            offsets = [(1, 0), (0, 1), (-1, 0), (0, -1)]
            WHITE = (255, 255, 255, 255)
            return len([1 for dx, dy in offsets
                        if surf.get_at((x+dx, y+dy)) == WHITE])

        def check_white_line(start, end):
            surf.fill((0, 0, 0))
            pygame.draw.line(surf, (255, 255, 255), start, end, 30)

            BLACK = (0, 0, 0, 255)
            for x in range(1, width-1):
                for y in range(1, height-1):
                    if surf.get_at((x, y)) == BLACK:
                        self.assertTrue(white_surrounded_pixels(x, y) < 3)

        check_white_line((50, 50), (140, 0))
        check_white_line((50, 50), (0, 120))
        check_white_line((50, 50), (199, 198))


### Polygon Testing ###########################################################

SQUARE = ([0, 0], [3, 0], [3, 3], [0, 3])
DIAMOND = [(1, 3), (3, 5), (5, 3), (3, 1)]
CROSS = ([2, 0], [4, 0], [4, 2], [6, 2],
         [6, 4], [4, 4], [4, 6], [2, 6],
         [2, 4], [0, 4], [0, 2], [2, 2])


class DrawPolygonMixin(object):
    """Mixin tests for drawing polygons.

    This class contains all the general polygon drawing tests.
    """

    def setUp(self):
        self.surface = pygame.Surface((20, 20))

    def test_draw_square(self):
        self.draw_polygon(self.surface, RED, SQUARE, 0)
        # note : there is a discussion (#234) if draw.polygon should include or
        # not the right or lower border; here we stick with current behavior,
        # eg include those borders ...
        for x in range(4):
            for y in range(4):
                self.assertEqual(self.surface.get_at((x, y)), RED)

    def test_draw_diamond(self):
        pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0)
        self.draw_polygon(self.surface, GREEN, DIAMOND, 0)
        # this diamond shape is equivalent to its four corners, plus inner square
        for x, y in DIAMOND:
            self.assertEqual(self.surface.get_at((x, y)), GREEN, msg=str((x, y)))
        for x in range(2, 5):
            for y in range(2, 5):
                self.assertEqual(self.surface.get_at((x, y)), GREEN)

    def test_1_pixel_high_or_wide_shapes(self):
        # 1. one-pixel-high, filled
        pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0)
        self.draw_polygon(self.surface, GREEN, [(x, 2) for x, _y in CROSS], 0)
        cross_size = 6 # the maximum x or y coordinate of the cross
        for x in range(cross_size + 1):
            self.assertEqual(self.surface.get_at((x, 1)), RED)
            self.assertEqual(self.surface.get_at((x, 2)), GREEN)
            self.assertEqual(self.surface.get_at((x, 3)), RED)
        pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0)
        # 2. one-pixel-high, not filled
        self.draw_polygon(self.surface, GREEN, [(x, 5) for x, _y in CROSS], 1)
        for x in range(cross_size + 1):
            self.assertEqual(self.surface.get_at((x, 4)), RED)
            self.assertEqual(self.surface.get_at((x, 5)), GREEN)
            self.assertEqual(self.surface.get_at((x, 6)), RED)
        pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0)
        # 3. one-pixel-wide, filled
        self.draw_polygon(self.surface, GREEN, [(3, y) for _x, y in CROSS], 0)
        for y in range(cross_size + 1):
            self.assertEqual(self.surface.get_at((2, y)), RED)
            self.assertEqual(self.surface.get_at((3, y)), GREEN)
            self.assertEqual(self.surface.get_at((4, y)), RED)
        pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0)
        # 4. one-pixel-wide, not filled
        self.draw_polygon(self.surface, GREEN, [(4, y) for _x, y in CROSS], 1)
        for y in range(cross_size + 1):
            self.assertEqual(self.surface.get_at((3, y)), RED)
            self.assertEqual(self.surface.get_at((4, y)), GREEN)
            self.assertEqual(self.surface.get_at((5, y)), RED)

    def test_draw_symetric_cross(self):
        """non-regression on issue #234 : x and y where handled inconsistently.

        Also, the result is/was different whether we fill or not the polygon.
        """
        # 1. case width = 1 (not filled: `polygon` calls  internally the `lines` function)
        pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0)
        self.draw_polygon(self.surface, GREEN, CROSS, 1)
        inside = [(x, 3) for x in range(1, 6)] + [(3, y) for y in range(1, 6)]
        for x in range(10):
            for y in range(10):
                if (x, y) in inside:
                    self.assertEqual(self.surface.get_at((x, y)), RED)
                elif (x in range(2, 5) and y <7) or (y in range(2, 5) and x < 7):
                    # we are on the border of the cross:
                    self.assertEqual(self.surface.get_at((x, y)), GREEN)
                else:
                    # we are outside
                    self.assertEqual(self.surface.get_at((x, y)), RED)

        # 2. case width = 0 (filled; this is the example from #234)
        pygame.draw.rect(self.surface, RED, (0, 0, 10, 10), 0)
        self.draw_polygon(self.surface, GREEN, CROSS, 0)
        inside = [(x, 3) for x in range(1, 6)] + [(3, y) for y in range(1, 6)]
        for x in range(10):
            for y in range(10):
                if (x in range(2, 5) and y <7) or (y in range(2, 5) and x < 7):
                    # we are on the border of the cross:
                    self.assertEqual(self.surface.get_at((x, y)), GREEN, msg=str((x, y)))
                else:
                    # we are outside
                    self.assertEqual(self.surface.get_at((x, y)), RED)

    def test_illumine_shape(self):
        """non-regression on issue #313"""
        rect = pygame.Rect((0, 0, 20, 20))
        path_data = [(0, 0), (rect.width-1, 0), # upper border
                     (rect.width-5,  5-1), (5-1, 5-1),  # upper inner
                     (5- 1, rect.height-5), (0,  rect.height-1)]   # lower diagonal
        # The shape looks like this (the numbers are the indices of path_data)

        # 0**********************1              <-- upper border
        # ***********************
        # **********************
        # *********************
        # ****3**************2                  <-- upper inner border
        # *****
        # *****                   (more lines here)
        # *****
        # ****4
        # ****
        # ***
        # **
        # 5
        #

        # the current bug is that the "upper inner" line is not drawn, but only
        # if 4 or some lower corner exists
        pygame.draw.rect(self.surface, RED, (0, 0, 20, 20), 0)

        # 1. First without the corners 4 & 5
        self.draw_polygon(self.surface, GREEN, path_data[:4], 0)
        for x in range(20):
            self.assertEqual(self.surface.get_at((x, 0)), GREEN)  # upper border
        for x in range(4, rect.width-5 +1):
            self.assertEqual(self.surface.get_at((x, 4)), GREEN)  # upper inner

        # 2. with the corners 4 & 5
        pygame.draw.rect(self.surface, RED, (0, 0, 20, 20), 0)
        self.draw_polygon(self.surface, GREEN, path_data, 0)
        for x in range(4, rect.width-5 +1):
            self.assertEqual(self.surface.get_at((x, 4)), GREEN)  # upper inner

    def test_invalid_points(self):
        self.assertRaises(TypeError, lambda: self.draw_polygon(self.surface,
                          RED, ((0, 0), (0, 20), (20, 20), 20), 0))


class DrawPolygonTest(DrawPolygonMixin, DrawTestCase):
    """Test draw module function polygon.

    This class inherits the general tests from DrawPolygonMixin. It is also
    the class to add any draw.polygon specific tests to.
    """


class PythonDrawPolygonTest(DrawPolygonMixin, PythonDrawTestCase):
    """Test draw_py module function draw_polygon.

    This class inherits the general tests from DrawPolygonMixin. It is also
    the class to add any draw_py.draw_polygon specific tests to.
    """


### Rect Testing ##############################################################

class DrawRectMixin(object):
    """Mixin tests for drawing rects.

    This class contains all the general rect drawing tests.
    """

    def todo_test_circle(self):
        self.fail()


class DrawRectTest(DrawRectMixin, DrawTestCase):
    """Test draw module function rect.

    This class inherits the general tests from DrawRectMixin. It is also the
    class to add any draw.rect specific tests to.
    """


class PythonDrawRectTest(DrawRectMixin, PythonDrawTestCase):
    """Test draw_py module function draw_rect.

    This class inherits the general tests from DrawRectMixin. It is also the
    class to add any draw_py.draw_rect specific tests to.
    """


### Circle Testing ############################################################

class DrawCircleMixin(object):
    """Mixin tests for drawing circles.

    This class contains all the general circle drawing tests.
    """

    def todo_test_circle(self):
        self.fail()

class DrawCircleTest(DrawCircleMixin, DrawTestCase):
    """Test draw module function circle.

    This class inherits the general tests from DrawCircleMixin. It is also
    the class to add any draw.circle specific tests to.
    """


class PythonDrawCircleTest(DrawCircleMixin, PythonDrawTestCase):
    """Test draw_py module function draw_circle."

    This class inherits the general tests from DrawCircleMixin. It is also
    the class to add any draw_py.draw_circle specific tests to.
    """


### Arc Testing ###############################################################

class DrawArcMixin(object):
    """Mixin tests for drawing arcs.

    This class contains all the general arc drawing tests.
    """

    def todo_test_arc(self):
        self.fail()


class DrawArcTest(DrawArcMixin, DrawTestCase):
    """Test draw module function arc.

    This class inherits the general tests from DrawArcMixin. It is also the
    class to add any draw.arc specific tests to.
    """


class PythonDrawArcTest(DrawArcMixin, PythonDrawTestCase):
    """Test draw_py module function draw_arc.

    This class inherits the general tests from DrawArcMixin. It is also the
    class to add any draw_py.draw_arc specific tests to.
    """


###############################################################################


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