import collections
import time
import unittest

import pygame

EVENT_TYPES = (
    #   pygame.NOEVENT,
    #   pygame.ACTIVEEVENT,
    pygame.KEYDOWN,
    pygame.KEYUP,
    pygame.MOUSEMOTION,
    pygame.MOUSEBUTTONDOWN,
    pygame.MOUSEBUTTONUP,
    pygame.JOYAXISMOTION,
    pygame.JOYBALLMOTION,
    pygame.JOYHATMOTION,
    pygame.JOYBUTTONDOWN,
    pygame.JOYBUTTONUP,
    pygame.VIDEORESIZE,
    pygame.VIDEOEXPOSE,
    pygame.QUIT,
    pygame.SYSWMEVENT,
    pygame.USEREVENT,
    #   pygame.NUMEVENTS,
)

EVENT_TEST_PARAMS = collections.defaultdict(dict)
EVENT_TEST_PARAMS.update(
    {
        pygame.KEYDOWN: {"key": pygame.K_SPACE},
        pygame.KEYUP: {"key": pygame.K_SPACE},
        pygame.MOUSEMOTION: dict(),
        pygame.MOUSEBUTTONDOWN: dict(button=1),
        pygame.MOUSEBUTTONUP: dict(button=1),
    }
)


NAMES_AND_EVENTS = (
    ("NoEvent", pygame.NOEVENT),
    ("ActiveEvent", pygame.ACTIVEEVENT),
    ("KeyDown", pygame.KEYDOWN),
    ("KeyUp", pygame.KEYUP),
    ("MouseMotion", pygame.MOUSEMOTION),
    ("MouseButtonDown", pygame.MOUSEBUTTONDOWN),
    ("MouseButtonUp", pygame.MOUSEBUTTONUP),
    ("JoyAxisMotion", pygame.JOYAXISMOTION),
    ("JoyBallMotion", pygame.JOYBALLMOTION),
    ("JoyHatMotion", pygame.JOYHATMOTION),
    ("JoyButtonDown", pygame.JOYBUTTONDOWN),
    ("JoyButtonUp", pygame.JOYBUTTONUP),
    ("VideoResize", pygame.VIDEORESIZE),
    ("VideoExpose", pygame.VIDEOEXPOSE),
    ("Quit", pygame.QUIT),
    ("SysWMEvent", pygame.SYSWMEVENT),
    ("MidiIn", pygame.MIDIIN),
    ("MidiOut", pygame.MIDIOUT),
    ("UserEvent", pygame.USEREVENT),
    ("Unknown", 0xFFFF),
    ("FingerMotion", pygame.FINGERMOTION),
    ("FingerDown", pygame.FINGERDOWN),
    ("FingerUp", pygame.FINGERUP),
    ("MultiGesture", pygame.MULTIGESTURE),
    ("MouseWheel", pygame.MOUSEWHEEL),
    ("TextInput", pygame.TEXTINPUT),
    ("TextEditing", pygame.TEXTEDITING),
    ("ControllerAxisMotion", pygame.CONTROLLERAXISMOTION),
    ("ControllerButtonDown", pygame.CONTROLLERBUTTONDOWN),
    ("ControllerButtonUp", pygame.CONTROLLERBUTTONUP),
    ("ControllerDeviceAdded", pygame.CONTROLLERDEVICEADDED),
    ("ControllerDeviceRemoved", pygame.CONTROLLERDEVICEREMOVED),
    ("ControllerDeviceMapped", pygame.CONTROLLERDEVICEREMAPPED),
    ("DropFile", pygame.DROPFILE),
    ("AudioDeviceAdded", pygame.AUDIODEVICEADDED),
    ("AudioDeviceRemoved", pygame.AUDIODEVICEREMOVED),
    ("DropText", pygame.DROPTEXT),
    ("DropBegin", pygame.DROPBEGIN),
    ("DropComplete", pygame.DROPCOMPLETE),
)


class EventTypeTest(unittest.TestCase):
    def test_Event_alias(self):
        """Check if pygame.Event is present and the correct type."""
        self.assertIs(pygame.Event, pygame.event.Event)

    def test_Event(self):
        """Ensure an Event object can be created."""
        e = pygame.event.Event(pygame.USEREVENT, some_attr=1, other_attr="1")

        self.assertEqual(e.some_attr, 1)
        self.assertEqual(e.other_attr, "1")

        # Event now uses tp_dictoffset and tp_members:
        # https://github.com/pygame-community/pygame-ce/issues/77
        self.assertEqual(e.type, pygame.USEREVENT)
        self.assertIs(e.dict, e.__dict__)

        e.some_attr = 12

        self.assertEqual(e.some_attr, 12)

        e.new_attr = 15

        self.assertEqual(e.new_attr, 15)

        self.assertRaises(AttributeError, setattr, e, "type", 0)
        self.assertRaises(AttributeError, setattr, e, "dict", None)

        # Ensure attributes are visible to dir(), part of the original
        # posted request.
        d = dir(e)
        attrs = ("type", "dict", "__dict__", "some_attr", "other_attr", "new_attr")

        for attr in attrs:
            self.assertIn(attr, d)

        # redundant type field as kwarg
        self.assertRaises(ValueError, pygame.event.Event, 10, type=100)

    def test_as_str(self):
        # Bug reported on Pygame mailing list July 24, 2011:
        # For Python 3.x str(event) to raises an UnicodeEncodeError when
        # an event attribute is a string with a non-ascii character.
        try:
            str(pygame.event.Event(EVENT_TYPES[0], a="\xed"))
        except UnicodeEncodeError:
            self.fail("Event object raised exception for non-ascii character")
        # Passed.

    def test_event_bool(self):
        self.assertFalse(pygame.event.Event(pygame.NOEVENT))
        for event_type in [
            pygame.MOUSEBUTTONDOWN,
            pygame.ACTIVEEVENT,
            pygame.WINDOWLEAVE,
            pygame.USEREVENT_DROPFILE,
        ]:
            self.assertTrue(pygame.event.Event(event_type))

    def test_event_equality(self):
        """Ensure that events can be compared correctly."""
        a = pygame.event.Event(EVENT_TYPES[0], a=1)
        b = pygame.event.Event(EVENT_TYPES[0], a=1)
        c = pygame.event.Event(EVENT_TYPES[1], a=1)
        d = pygame.event.Event(EVENT_TYPES[0], a=2)

        self.assertTrue(a == a)
        self.assertFalse(a != a)
        self.assertTrue(a == b)
        self.assertFalse(a != b)
        self.assertTrue(a != c)
        self.assertFalse(a == c)
        self.assertTrue(a != d)
        self.assertFalse(a == d)

    def test_event_args(self):
        """Test event creation with args"""
        NESTED_DICT = {"haha": True, "no": 92138023, "test": ("y", "e", "s")}
        EXPECTED_ATTRIBUTES = {
            "foo": "bar",
            "ratio": 100,
            "favourite_numbers": [3, 26, 42],
            "nested_dict": NESTED_DICT,
            "complex": 3 + 4j,
        }
        for event in (
            pygame.event.Event(1, EXPECTED_ATTRIBUTES),  # pass as dict
            pygame.event.Event(1, **EXPECTED_ATTRIBUTES),  # pass as kwargs
            pygame.event.Event(  # test passing both dict and kwargs
                1,
                {"nested_dict": NESTED_DICT, "complex": 3 + 4j},
                foo="bar",
                ratio=100,
                favourite_numbers=[3, 26, 42],
            ),
        ):
            self.assertDictEqual(event.dict, EXPECTED_ATTRIBUTES)

        for out_of_range_val in (-1, -10, pygame.NUMEVENTS, pygame.NUMEVENTS + 1):
            self.assertRaises(ValueError, pygame.event.Event, out_of_range_val)

        for incorrect_type in ("string", 4 + 3j, [1, 2, 3], {"a": "b"}):
            self.assertRaises(TypeError, pygame.event.Event, incorrect_type)

    def test_event_common_dict_changes(self):
        d = {}
        events = [pygame.Event(pygame.event.custom_type(), d) for _ in range(2)]
        self.assertNotEqual(events[0], events[1])

        d["hi"] = "hello"
        events[0].hello = "hi"
        events[1].dict["amongus"] = "sus"

        self.assertEqual(d, {"hi": "hello", "hello": "hi", "amongus": "sus"})
        for ev in events:
            self.assertIs(d, ev.dict)
            self.assertEqual(ev.dict, {"hi": "hello", "hello": "hi", "amongus": "sus"})
            self.assertEqual(ev.hi, "hello")
            self.assertEqual(ev.hello, "hi")
            self.assertEqual(ev.amongus, "sus")


race_condition_notification = """
This test is dependent on timing. The event queue is cleared in preparation for
tests. There is a small window where outside events from the OS may have effected
results. Try running the test again.
"""


class EventModuleArgsTest(unittest.TestCase):
    def setUp(self):
        pygame.display.init()
        pygame.event.clear()

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

    def test_get(self):
        pygame.event.get()
        pygame.event.get(None)
        pygame.event.get(None, True)

        pygame.event.get(pump=False)
        pygame.event.get(pump=True)
        pygame.event.get(eventtype=None)
        pygame.event.get(eventtype=[pygame.KEYUP, pygame.KEYDOWN])
        pygame.event.get(eventtype=pygame.USEREVENT, pump=False)

        # event type out of range
        self.assertRaises(ValueError, pygame.event.get, 0x00010000)
        self.assertRaises(TypeError, pygame.event.get, 1 + 2j)
        self.assertRaises(TypeError, pygame.event.get, "foo")

    def test_clear(self):
        pygame.event.clear()
        pygame.event.clear(None)
        pygame.event.clear(None, True)

        pygame.event.clear(pump=False)
        pygame.event.clear(pump=True)
        pygame.event.clear(eventtype=None)
        pygame.event.clear(eventtype=[pygame.KEYUP, pygame.KEYDOWN])
        pygame.event.clear(eventtype=pygame.USEREVENT, pump=False)

        # event type out of range
        self.assertRaises(ValueError, pygame.event.clear, 0x0010FFFFF)
        self.assertRaises(TypeError, pygame.event.get, ["a", "b", "c"])

    def test_peek(self):
        pygame.event.peek()
        pygame.event.peek(None)
        pygame.event.peek(None, True)

        pygame.event.peek(pump=False)
        pygame.event.peek(pump=True)
        pygame.event.peek(eventtype=None)
        pygame.event.peek(eventtype=[pygame.KEYUP, pygame.KEYDOWN])
        pygame.event.peek(eventtype=pygame.USEREVENT, pump=False)

        class Foo:
            pass

        # event type out of range
        self.assertRaises(ValueError, pygame.event.peek, -1)
        self.assertRaises(ValueError, pygame.event.peek, [-10])
        self.assertRaises(TypeError, pygame.event.peek, Foo())


class EventCustomTypeTest(unittest.TestCase):
    """Those tests are special in that they need the _custom_event counter to
    be reset before and/or after being run."""

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

    def tearDown(self):
        pygame.quit()

    def test_custom_type(self):
        self.assertEqual(pygame.event.custom_type(), pygame.USEREVENT + 1)
        atype = pygame.event.custom_type()
        atype2 = pygame.event.custom_type()

        self.assertEqual(atype, atype2 - 1)

        ev = pygame.event.Event(atype)
        pygame.event.post(ev)
        queue = pygame.event.get(atype)
        self.assertEqual(len(queue), 1)
        self.assertEqual(queue[0].type, atype)

    def test_custom_type__end_boundary(self):
        """Ensure custom_type() raises error when no more custom types.

        The last allowed custom type number should be (pygame.NUMEVENTS - 1).
        """
        last = -1
        start = pygame.event.custom_type() + 1
        for _ in range(start, pygame.NUMEVENTS):
            last = pygame.event.custom_type()

        self.assertEqual(last, pygame.NUMEVENTS - 1)
        with self.assertRaises(pygame.error):
            pygame.event.custom_type()

    def test_custom_type__reset(self):
        """Ensure custom events get 'deregistered' by quit()."""
        before = pygame.event.custom_type()
        self.assertEqual(before, pygame.event.custom_type() - 1)
        pygame.quit()
        pygame.init()
        pygame.display.init()
        self.assertEqual(before, pygame.event.custom_type())


class EventModuleTest(unittest.TestCase):
    def _assertCountEqual(self, *args, **kwargs):
        # Handle method name differences between Python versions.
        # Is this still needed?
        self.assertCountEqual(*args, **kwargs)

    def _assertExpectedEvents(self, expected, got):
        """Find events like expected events, raise on unexpected or missing,
        ignore additional event properties if expected properties are present."""

        # This does greedy matching, don't encode an NP-hard problem
        # into your input data, *please*
        items_left = got[:]
        for expected_element in expected:
            for item in items_left:
                for key in expected_element.__dict__:
                    if item.__dict__[key] != expected_element.__dict__[key]:
                        break
                else:
                    # found item!
                    items_left.remove(item)
                    break
            else:
                raise AssertionError(
                    "Expected "
                    + str(expected_element)
                    + " among remaining events "
                    + str(items_left)
                    + " out of "
                    + str(got)
                )
        if len(items_left) > 0:
            raise AssertionError("Unexpected Events: " + str(items_left))

    def setUp(self):
        pygame.display.init()
        pygame.event.clear()  # flush events

    def tearDown(self):
        pygame.event.clear()  # flush events
        pygame.display.quit()

    def test_event_numevents(self):
        """Ensures NUMEVENTS does not exceed the maximum SDL number of events."""
        # Ref: https://www.libsdl.org/tmp/SDL/include/SDL_events.h
        MAX_SDL_EVENTS = 0xFFFF  # SDL_LASTEVENT = 0xFFFF

        self.assertLessEqual(pygame.NUMEVENTS, MAX_SDL_EVENTS)

    def test_event_attribute(self):
        e1 = pygame.event.Event(pygame.USEREVENT, attr1="attr1")
        self.assertEqual(e1.attr1, "attr1")

    def test_set_blocked(self):
        """Ensure events can be blocked from the queue."""
        event = EVENT_TYPES[0]
        unblocked_event = EVENT_TYPES[1]
        pygame.event.set_blocked(event)

        self.assertTrue(pygame.event.get_blocked(event))
        self.assertFalse(pygame.event.get_blocked(unblocked_event))

        posted = pygame.event.post(
            pygame.event.Event(event, **EVENT_TEST_PARAMS[event])
        )
        self.assertFalse(posted)

        # post an unblocked event
        posted = pygame.event.post(
            pygame.event.Event(unblocked_event, **EVENT_TEST_PARAMS[unblocked_event])
        )
        self.assertTrue(posted)

        ret = pygame.event.get()
        should_be_blocked = [e for e in ret if e.type == event]
        should_be_allowed_types = [e.type for e in ret if e.type != event]

        self.assertEqual(should_be_blocked, [])
        self.assertTrue(unblocked_event in should_be_allowed_types)

    def test_set_blocked__event_sequence(self):
        """Ensure a sequence of event types can be blocked."""
        event_types = [
            pygame.KEYDOWN,
            pygame.KEYUP,
            pygame.MOUSEMOTION,
            pygame.MOUSEBUTTONDOWN,
            pygame.MOUSEBUTTONUP,
            pygame.WINDOWFOCUSLOST,
            pygame.USEREVENT,
        ]

        pygame.event.set_blocked(event_types)

        for etype in event_types:
            self.assertTrue(pygame.event.get_blocked(etype))

    def test_set_blocked_all(self):
        """Ensure all events can be unblocked at once."""
        pygame.event.set_blocked(None)

        for e in EVENT_TYPES:
            self.assertTrue(pygame.event.get_blocked(e))

    def test_post__and_poll(self):
        """Ensure events can be posted to the queue."""
        e1 = pygame.event.Event(pygame.USEREVENT, attr1="attr1")
        pygame.event.post(e1)
        posted_event = pygame.event.poll()

        self.assertEqual(e1.attr1, posted_event.attr1, race_condition_notification)

        # fuzzing event types
        for i in range(1, 13):
            pygame.event.post(
                pygame.event.Event(EVENT_TYPES[i], **EVENT_TEST_PARAMS[EVENT_TYPES[i]])
            )

            self.assertEqual(
                pygame.event.poll().type, EVENT_TYPES[i], race_condition_notification
            )

    def test_post_and_get_keydown(self):
        """Ensure keydown events can be posted to the queue."""
        activemodkeys = pygame.key.get_mods()

        events = [
            pygame.event.Event(pygame.KEYDOWN, key=pygame.K_p),
            pygame.event.Event(pygame.KEYDOWN, key=pygame.K_y, mod=activemodkeys),
            pygame.event.Event(pygame.KEYDOWN, key=pygame.K_g, unicode="g"),
            pygame.event.Event(pygame.KEYDOWN, key=pygame.K_a, unicode=None),
            pygame.event.Event(pygame.KEYDOWN, key=pygame.K_m, mod=None, window=None),
            pygame.event.Event(
                pygame.KEYDOWN, key=pygame.K_e, mod=activemodkeys, unicode="e"
            ),
        ]

        for e in events:
            pygame.event.post(e)
            posted_event = pygame.event.poll()
            self.assertEqual(e, posted_event, race_condition_notification)

    def test_post_large_user_event(self):
        pygame.event.post(
            pygame.event.Event(
                pygame.USEREVENT, {"a": "a" * 1024}, test=list(range(100))
            )
        )
        e = pygame.event.poll()

        self.assertEqual(e.type, pygame.USEREVENT)
        self.assertEqual(e.a, "a" * 1024)
        self.assertEqual(e.test, list(range(100)))

    def test_post_same_reference(self):
        """
        Test that event.post posts an event with a common reference to a dict.
        """
        for event in (
            pygame.event.Event(pygame.event.custom_type(), attr=(1, 2, 3, 4)),
            pygame.event.Event(pygame.event.custom_type()),
        ):
            pygame.event.post(event)
            e = pygame.event.poll()
            self.assertEqual(e.type, event.type)
            self.assertIs(e.dict, event.dict)

    def test_post_blocked(self):
        """
        Test blocked events are not posted. Also test whether post()
        returns a boolean correctly
        """
        pygame.event.set_blocked(pygame.USEREVENT)
        self.assertFalse(pygame.event.post(pygame.event.Event(pygame.USEREVENT)))
        self.assertFalse(pygame.event.poll())
        pygame.event.set_allowed(pygame.USEREVENT)
        self.assertTrue(pygame.event.post(pygame.event.Event(pygame.USEREVENT)))
        self.assertEqual(pygame.event.poll(), pygame.event.Event(pygame.USEREVENT))

    def test_get(self):
        """Ensure get() retrieves all the events on the queue."""
        event_cnt = 10
        for _ in range(event_cnt):
            pygame.event.post(pygame.event.Event(pygame.USEREVENT))

        queue = pygame.event.get()

        self.assertEqual(len(queue), event_cnt)
        self.assertTrue(all(e.type == pygame.USEREVENT for e in queue))

    def test_get_type(self):
        ev = pygame.event.Event(pygame.USEREVENT)
        pygame.event.post(ev)
        queue = pygame.event.get(pygame.USEREVENT)
        self.assertEqual(len(queue), 1)
        self.assertEqual(queue[0].type, pygame.USEREVENT)

        TESTEVENTS = 10
        for _ in range(TESTEVENTS):
            pygame.event.post(ev)
        q = pygame.event.get([pygame.USEREVENT])
        self.assertEqual(len(q), TESTEVENTS)
        for event in q:
            self.assertEqual(event, ev)

    def test_get_exclude_throw(self):
        self.assertRaises(
            pygame.error, pygame.event.get, pygame.KEYDOWN, False, pygame.KEYUP
        )

    def test_get_exclude(self):
        pygame.event.post(pygame.event.Event(pygame.USEREVENT))
        pygame.event.post(pygame.event.Event(pygame.KEYDOWN))

        queue = pygame.event.get(exclude=pygame.KEYDOWN)
        self.assertEqual(len(queue), 1)
        self.assertEqual(queue[0].type, pygame.USEREVENT)

        pygame.event.post(pygame.event.Event(pygame.KEYUP))
        pygame.event.post(pygame.event.Event(pygame.USEREVENT))
        queue = pygame.event.get(exclude=(pygame.KEYDOWN, pygame.KEYUP))
        self.assertEqual(len(queue), 1)
        self.assertEqual(queue[0].type, pygame.USEREVENT)

        queue = pygame.event.get()
        self.assertEqual(len(queue), 2)

    def test_get__empty_queue(self):
        """Ensure get() works correctly on an empty queue."""
        expected_events = []
        pygame.event.clear()

        # Ensure all events can be checked.
        retrieved_events = pygame.event.get()

        self.assertListEqual(retrieved_events, expected_events)

        # Ensure events can be checked individually.
        for event_type in EVENT_TYPES:
            retrieved_events = pygame.event.get(event_type)

            self.assertListEqual(retrieved_events, expected_events)

        # Ensure events can be checked as a sequence.
        retrieved_events = pygame.event.get(EVENT_TYPES)

        self.assertListEqual(retrieved_events, expected_events)

    def test_get__event_sequence(self):
        """Ensure get() can handle a sequence of event types."""
        event_types = [pygame.KEYDOWN, pygame.KEYUP, pygame.MOUSEMOTION]
        other_event_type = pygame.MOUSEBUTTONUP

        # Test when no events in the queue.
        expected_events = []
        pygame.event.clear()
        retrieved_events = pygame.event.get(event_types)

        # don't use self._assertCountEqual here. This checks for
        # expected properties in events, and ignores unexpected ones, for
        # forward compatibility with SDL2.
        self._assertExpectedEvents(expected=expected_events, got=retrieved_events)

        # Test when an event type not in the list is in the queue.
        expected_events = []
        pygame.event.clear()
        pygame.event.post(
            pygame.event.Event(other_event_type, **EVENT_TEST_PARAMS[other_event_type])
        )

        retrieved_events = pygame.event.get(event_types)

        self._assertExpectedEvents(expected=expected_events, got=retrieved_events)

        # Test when 1 event type in the list is in the queue.
        expected_events = [
            pygame.event.Event(event_types[0], **EVENT_TEST_PARAMS[event_types[0]])
        ]
        pygame.event.clear()
        pygame.event.post(expected_events[0])

        retrieved_events = pygame.event.get(event_types)

        self._assertExpectedEvents(expected=expected_events, got=retrieved_events)

        # Test all events in the list are in the queue.
        pygame.event.clear()
        expected_events = []

        for etype in event_types:
            expected_events.append(
                pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype])
            )
            pygame.event.post(expected_events[-1])

        retrieved_events = pygame.event.get(event_types)

        self._assertExpectedEvents(expected=expected_events, got=retrieved_events)

    def test_get_clears_queue(self):
        """Ensure get() clears the event queue after a call"""
        pygame.event.get()  # should clear the queue completely by getting all events
        self.assertEqual(pygame.event.get(), [])

    def test_clear(self):
        """Ensure clear() removes all the events on the queue."""
        for e in EVENT_TYPES:
            pygame.event.post(pygame.event.Event(e, **EVENT_TEST_PARAMS[e]))
        poll_event = pygame.event.poll()

        self.assertNotEqual(poll_event.type, pygame.NOEVENT)

        pygame.event.clear()
        poll_event = pygame.event.poll()

        self.assertEqual(poll_event.type, pygame.NOEVENT, race_condition_notification)

    def test_clear__empty_queue(self):
        """Ensure clear() works correctly on an empty queue."""
        expected_events = []
        pygame.event.clear()

        # Test calling clear() on an already empty queue.
        pygame.event.clear()

        retrieved_events = pygame.event.get()

        self.assertListEqual(retrieved_events, expected_events)

    def test_clear__event_sequence(self):
        """Ensure a sequence of event types can be cleared from the queue."""
        cleared_event_types = EVENT_TYPES[:5]
        expected_event_types = EVENT_TYPES[5:10]
        expected_events = []

        # Add the events to the queue.
        for etype in cleared_event_types:
            pygame.event.post(pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype]))

        for etype in expected_events:
            expected_events.append(
                pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype])
            )
            pygame.event.post(expected_events[-1])

        # Clear the cleared_events from the queue.
        pygame.event.clear(cleared_event_types)

        # Check the rest of the events in the queue.
        remaining_events = pygame.event.get()

        self._assertCountEqual(remaining_events, expected_events)

    def test_event_name(self):
        """Ensure event_name() returns the correct event name."""
        for expected_name, event in NAMES_AND_EVENTS:
            self.assertEqual(
                pygame.event.event_name(event), expected_name, f"0x{event:X}"
            )

    def test_event_name__userevent_range(self):
        """Ensures event_name() returns the correct name for user events.

        Tests the full range of user events.
        """
        expected_name = "UserEvent"

        for event in range(pygame.USEREVENT, pygame.NUMEVENTS):
            self.assertEqual(
                pygame.event.event_name(event), expected_name, f"0x{event:X}"
            )

    def test_event_name__userevent_boundary(self):
        """Ensures event_name() does not return 'UserEvent' for events
        just outside the user event range.
        """
        unexpected_name = "UserEvent"

        for event in (pygame.USEREVENT - 1, pygame.NUMEVENTS):
            self.assertNotEqual(
                pygame.event.event_name(event), unexpected_name, f"0x{event:X}"
            )

    def test_peek(self):
        """Ensure queued events can be peeked at."""
        event_types = [pygame.KEYDOWN, pygame.KEYUP, pygame.MOUSEMOTION]

        for event_type in event_types:
            pygame.event.post(
                pygame.event.Event(event_type, **EVENT_TEST_PARAMS[event_type])
            )

        # Ensure events can be checked individually.
        for event_type in event_types:
            self.assertTrue(pygame.event.peek(event_type))

        # Ensure events can be checked as a sequence.
        self.assertTrue(pygame.event.peek(event_types))

    def test_peek__event_sequence(self):
        """Ensure peek() can handle a sequence of event types."""
        event_types = [pygame.KEYDOWN, pygame.KEYUP, pygame.MOUSEMOTION]
        other_event_type = pygame.MOUSEBUTTONUP

        # Test when no events in the queue.
        pygame.event.clear()
        peeked = pygame.event.peek(event_types)

        self.assertFalse(peeked)

        # Test when an event type not in the list is in the queue.
        pygame.event.clear()
        pygame.event.post(
            pygame.event.Event(other_event_type, **EVENT_TEST_PARAMS[other_event_type])
        )

        peeked = pygame.event.peek(event_types)

        self.assertFalse(peeked)

        # Test when 1 event type in the list is in the queue.
        pygame.event.clear()
        pygame.event.post(
            pygame.event.Event(event_types[0], **EVENT_TEST_PARAMS[event_types[0]])
        )

        peeked = pygame.event.peek(event_types)

        self.assertTrue(peeked)

        # Test all events in the list are in the queue.
        pygame.event.clear()
        for etype in event_types:
            pygame.event.post(pygame.event.Event(etype, **EVENT_TEST_PARAMS[etype]))

        peeked = pygame.event.peek(event_types)

        self.assertTrue(peeked)

    def test_peek__empty_queue(self):
        """Ensure peek() works correctly on an empty queue."""
        pygame.event.clear()

        # Ensure all events can be checked.
        peeked = pygame.event.peek()

        self.assertFalse(peeked)

        # Ensure events can be checked individually.
        for event_type in EVENT_TYPES:
            peeked = pygame.event.peek(event_type)
            self.assertFalse(peeked)

        # Ensure events can be checked as a sequence.
        peeked = pygame.event.peek(EVENT_TYPES)

        self.assertFalse(peeked)

    def test_set_allowed(self):
        """Ensure a blocked event type can be unblocked/allowed."""
        event = EVENT_TYPES[0]
        pygame.event.set_blocked(event)

        self.assertTrue(pygame.event.get_blocked(event))

        pygame.event.set_allowed(event)

        self.assertFalse(pygame.event.get_blocked(event))

    def test_set_allowed__event_sequence(self):
        """Ensure a sequence of blocked event types can be unblocked/allowed."""
        event_types = [
            pygame.KEYDOWN,
            pygame.KEYUP,
            pygame.MOUSEMOTION,
            pygame.MOUSEBUTTONDOWN,
            pygame.MOUSEBUTTONUP,
        ]
        pygame.event.set_blocked(event_types)

        pygame.event.set_allowed(event_types)

        for etype in event_types:
            self.assertFalse(pygame.event.get_blocked(etype))

    def test_set_allowed_all(self):
        """Ensure all events can be unblocked/allowed at once."""
        pygame.event.set_blocked(None)

        for e in EVENT_TYPES:
            self.assertTrue(pygame.event.get_blocked(e))

        pygame.event.set_allowed(None)

        for e in EVENT_TYPES:
            self.assertFalse(pygame.event.get_blocked(e))

    def test_pump(self):
        """Ensure pump() functions properly."""
        pygame.event.pump()

    # @unittest.skipIf(
    #     os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER,
    #     'requires the SDL_VIDEODRIVER to be a non-null value',
    # )
    # Fails on SDL 2.0.18
    @unittest.skip("flaky test, and broken on 2.0.18 windows")
    def test_set_grab__and_get_symmetric(self):
        """Ensure event grabbing can be enabled and disabled.

        WARNING: Moving the mouse off the display during this test can cause it
                 to fail.
        """
        surf = pygame.display.set_mode((10, 10))
        pygame.event.set_grab(True)

        self.assertTrue(pygame.event.get_grab())

        pygame.event.set_grab(False)

        self.assertFalse(pygame.event.get_grab())

    def test_get_blocked(self):
        """Ensure an event's blocked state can be retrieved."""
        # Test each event is not blocked.
        pygame.event.set_allowed(None)

        for etype in EVENT_TYPES:
            blocked = pygame.event.get_blocked(etype)

            self.assertFalse(blocked)

        # Test each event type is blocked.
        pygame.event.set_blocked(None)

        for etype in EVENT_TYPES:
            blocked = pygame.event.get_blocked(etype)

            self.assertTrue(blocked)

    def test_get_blocked__event_sequence(self):
        """Ensure get_blocked() can handle a sequence of event types."""
        event_types = [
            pygame.KEYDOWN,
            pygame.KEYUP,
            pygame.MOUSEMOTION,
            pygame.MOUSEBUTTONDOWN,
            pygame.MOUSEBUTTONUP,
            pygame.WINDOWMINIMIZED,
            pygame.USEREVENT,
        ]

        # Test no event types in the list are blocked.
        blocked = pygame.event.get_blocked(event_types)

        self.assertFalse(blocked)

        # Test when 1 event type in the list is blocked.
        pygame.event.set_blocked(event_types[2])

        blocked = pygame.event.get_blocked(event_types)

        self.assertTrue(blocked)

        # Test all event types in the list are blocked.
        pygame.event.set_blocked(event_types)

        blocked = pygame.event.get_blocked(event_types)

        self.assertTrue(blocked)

    # @unittest.skipIf(
    #     os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER,
    #     'requires the SDL_VIDEODRIVER to be a non-null value',
    # )
    # Fails on SDL 2.0.18
    @unittest.skip("flaky test, and broken on 2.0.18 windows")
    def test_get_grab(self):
        """Ensure get_grab() works as expected"""
        surf = pygame.display.set_mode((10, 10))
        # Test 5 times
        for i in range(5):
            pygame.event.set_grab(i % 2)
            self.assertEqual(pygame.event.get_grab(), i % 2)

    def test_poll(self):
        """Ensure poll() works as expected"""
        pygame.event.clear()
        ev = pygame.event.poll()
        # poll() on empty queue should return NOEVENT
        self.assertEqual(ev.type, pygame.NOEVENT)

        # test poll returns stuff in same order
        e1 = pygame.event.Event(pygame.USEREVENT)
        e2 = pygame.event.Event(pygame.KEYDOWN, key=pygame.K_a)
        e3 = pygame.event.Event(pygame.KEYUP, key=pygame.K_a)
        pygame.event.post(e1)
        pygame.event.post(e2)
        pygame.event.post(e3)

        self.assertEqual(pygame.event.poll().type, e1.type)
        self.assertEqual(pygame.event.poll().type, e2.type)
        self.assertEqual(pygame.event.poll().type, e3.type)
        self.assertEqual(pygame.event.poll().type, pygame.NOEVENT)


class EventModuleTestsWithTiming(unittest.TestCase):
    __tags__ = ["timing"]

    def setUp(self):
        pygame.display.init()
        pygame.event.clear()  # flush events

    def tearDown(self):
        pygame.event.clear()  # flush events
        pygame.display.quit()

    def test_event_wait(self):
        """Ensure wait() waits for an event on the queue."""
        # Test case without timeout.
        event = pygame.event.Event(EVENT_TYPES[0], **EVENT_TEST_PARAMS[EVENT_TYPES[0]])
        pygame.event.post(event)
        wait_event = pygame.event.wait()

        self.assertEqual(wait_event.type, event.type)

        # Test case with timeout and no event in the queue.
        wait_event = pygame.event.wait(100)
        self.assertEqual(wait_event.type, pygame.NOEVENT)

        # Test case with timeout and an event in the queue.
        event = pygame.event.Event(EVENT_TYPES[0], **EVENT_TEST_PARAMS[EVENT_TYPES[0]])
        pygame.event.post(event)
        wait_event = pygame.event.wait(100)

        self.assertEqual(wait_event.type, event.type)

        # test wait with timeout waits for the correct duration
        pygame.time.set_timer(pygame.USEREVENT, 50, 3)

        for wait_time, expected_type, expected_time in (
            (60, pygame.USEREVENT, 50),
            (65, pygame.USEREVENT, 50),
            (20, pygame.NOEVENT, 20),
            (45, pygame.USEREVENT, 30),
            (70, pygame.NOEVENT, 70),
        ):
            start_time = time.perf_counter()
            self.assertEqual(pygame.event.wait(wait_time).type, expected_type)
            self.assertAlmostEqual(
                time.perf_counter() - start_time, expected_time / 1000, delta=0.01
            )

        # test wait without timeout waits for the full duration
        pygame.time.set_timer(pygame.USEREVENT, 100, 1)

        start_time = time.perf_counter()
        self.assertEqual(pygame.event.wait().type, pygame.USEREVENT)
        self.assertAlmostEqual(time.perf_counter() - start_time, 0.1, delta=0.01)

        # test wait returns no event if event is arriving later
        pygame.time.set_timer(pygame.USEREVENT, 50, 1)
        self.assertEqual(pygame.event.wait(40).type, pygame.NOEVENT)


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

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