# PyEPL: hardware/eventpoll.pyx
#
# Copyright (C) 2003-2005 Michael J. Kahana
# Authors: Ian Schleifer, Per Sederberg, Aaron Geller, Josh Jacobs
# URL: http://memory.psych.upenn.edu/programming/pyepl
#
# Distributed under the terms of the GNU Lesser General Public License
# (LGPL). See the license.txt that came with this file.

"""
This module is responsible for continual event polling for input
devices.
"""
# import the pygame package
import pygame

# have certain pygame event constants in this namespace
from pygame.locals import KEYDOWN, KEYUP, MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN, JOYAXISMOTION, JOYBALLMOTION, JOYHATMOTION, JOYBUTTONUP, JOYBUTTONDOWN, USEREVENT

# import the pyepl timing module
import timing

# declare python variables to hold callbacks for particular kinds of events...
cdef object keyboard_callback
cdef object mouse_movement_callback
cdef object mouse_buttons_callback
cdef object joystick_axis_callback
cdef object joystick_balls_callback
cdef object joystick_buttons_callback
cdef object joystick_hats_callback
cdef object poll_callbacks  # this one is called at every iteration of the event loop

# declare a variable for a python list - it will be a queue for events read
# from pygame, but not yet processed
cdef object event_queue

# declare an integer which holds the number of times the pollEvents function is
# currently in the call stack
cdef int recurse_level

# declare a python object for store the time of the last event polling
cdef object last_poll

# declare a floating point variable to store the threshold below which the
# absolute value of a joystick axis is considered to be zero
cdef float js_zero_threshold

def setJSZeroThreshold(float x):
    """
    Set the joystick zero threshold to x.
    """
    global js_zero_threshold
    js_zero_threshold = x

def setJoystickCallbacks(axis, balls, buttons, hats):
    """
    Set the four callbacks for joystick-related events...
    axis - Called when a joystick axis changes position
    balls - Called when a joystick ball is moved
    buttons - Called when a joystick button is pressed or released
    hats - Called when a joystick hat is moved from one position to another
    """
    # set variables as global in this function...
    global joystick_axis_callback
    global joystick_balls_callback
    global joystick_buttons_callback
    global joystick_hats_callback

    # assign variables...
    joystick_axis_callback = axis
    joystick_balls_callback = balls
    joystick_buttons_callback = buttons
    joystick_hats_callback = hats

def setKeyboardCallback(c):
    """
    After this call, any keyboard event will result in the call
    "c(key, pressed, timestamp)", where key is a key identifier and pressed
    is True or False for depressed and released, respectively.
    """
    global keyboard_callback
    keyboard_callback = c

def setMouseCallbacks(movement, buttons):
    """
    After this call, any mouse movement event will result in the call
    "movement(absolute_position, position_change, timestamp)" and any
    mouse button event will result in the call "buttons(button,
    pressed, timestamp)".
    """
    # set variables as global in this function...
    global mouse_movement_callback
    global mouse_buttons_callback

    # assign variables...
    mouse_movement_callback = movement
    mouse_buttons_callback = buttons

def addPollCallback(c, *targs, **dargs):
    """
    Call function c at every polling of events.  All remaining parameters, both
    positional and keyword, will be passed to c when it is called.
    """
    global poll_callbacks
    poll_callbacks.append((c, targs, dargs))

def removePollCallback(c):
    """
    Stop calling function c at each polling of events.
    """
    global poll_callbacks
    
    # for every registered poll callback...
    for n, (cb, targs, dargs) in enumerate(poll_callbacks):
	# if the associated function is c...
        if cb==c:
            # remove the callback
            del poll_callbacks[n]

	    # and exit this function
            return

def initialize(**options):
    """
    Do any preparation necessary for event polling.
    """
    # set variables as global in this function...
    global recurse_level
    global last_poll
    global event_queue
    global poll_callbacks

    # initialize recursion level to zero
    recurse_level = 0

    # set the time of the last polling to now
    last_poll = timing.universal_time()

    # create an empty list to store queued events
    event_queue = []

    # create an empty list to store poll callbacks
    poll_callbacks = []

def finalize():
    """
    Shut down event polling.
    """
    # presently, nothing needs to be done to finalize this module
    pass

# global variables to implemets periodic sleeps to make Macs happy in realtime mode
isRealtime = 0
sleepTime = 500 # in uSec

def pollEvents():
    # set the realtime mode related variables as global...
    global isRealtime
    global sleepTime
    
    # set remaining variables as global in this function...
    global keyboard_callback
    global mouse_movement_callback
    global mouse_buttons_callback
    global joystick_axis_callback
    global joystick_balls_callback
    global joystick_buttons_callback
    global joystick_hats_callback
    global poll_callbacks
    global event_queue
    global recurse_level
    global last_poll
    global js_zero_threshold

    # declare variable to store current time
    cdef object now

    # declare variable for timestamp with maximum latency
    cdef object t

    # declare variable to store events read from pygame
    cdef object evs

    # temporary float variable (used for joystick axis events)
    cdef float v

    # get the current time
    now = timing.universal_time()

    # calculate maximum latency based on current time and last polling time
    t = (last_poll, now - last_poll)

    # update last polling time
    last_poll = now

    # get all waiting events from pygame
    evs = pygame.event.get()

    # for all the events we just read...
    for ev in evs:
    	# ...put them into the event queue along with the timestamp
        event_queue.append((t, ev))

    # see if we should add sleep to lower CPU use
    if isRealtime:
        if len(event_queue)==0:
            #and timing.universal_time()-lastSleep >= sleepDelay:
            # Do the sleep
            timing.uSleep(sleepTime)
            #lastSleep = timing.universal_time()

    # don't allow the rest of this to be called recursively...
    if not recurse_level:
        # set the recurse level so that we do not get loops
        recurse_level = 1

        # for all registered polling callbacks...
        for c, targs, dargs in poll_callbacks:
            # ...call them with the registered parameters
            c(*targs, **dargs)
            
        # process events until the queue is empty (even if more are added)...
        while len(event_queue):
            # read an event and timestamp from the queue [is it a stack now?]
            t, e = event_queue.pop(0)

            # recurse_level = 1 # was here, but we moved it up
            
	    # SEE IF WE HAVE AN EVENT TYPE WE CARE ABOUT...
            if e.type == KEYDOWN:
                # if it's a keydown event...
                if keyboard_callback:
                    # call the keyboard callback with relevent info
                    keyboard_callback(e.key, True, t)

            elif e.type == KEYUP:
                # if it's a key up event...
                if keyboard_callback:
                    # call the keyboard callback with relevent info
                    keyboard_callback(e.key, False, t)

            elif e.type == MOUSEMOTION:
                # if it's a mouse motion event...
                if mouse_movement_callback:
                    # call the mouse movement callback with relevent info
                    mouse_movement_callback(e.pos, e.rel, t)

            elif e.type == MOUSEBUTTONUP:
                # if it's a mouse button up event...
                if mouse_buttons_callback:
                    # ...mouse buttons callback
                    mouse_buttons_callback(e.button, False, t)

            elif e.type == MOUSEBUTTONDOWN:
                # mouse down event?...
                if mouse_buttons_callback:
                    # ...mouse buttons callback
                    mouse_buttons_callback(e.button, True, t)

            elif e.type == JOYAXISMOTION:
                # mouse axis motion?...
                if joystick_axis_callback:
                    # put the axis value into a local variable
                    v = e.value

                    # if it's absolute value is less than the threshold...
                    if v < js_zero_threshold and v > -js_zero_threshold:
                        # ...use zero instead
                        v = 0.0

                    # joystick axis callback
                    joystick_axis_callback(e.joy, e.axis, v, t)

            elif e.type == JOYBALLMOTION:
                # joystick ball motion?...
                if joystick_balls_callback:
                    # ...joystick balls callback
                    joystick_balls_callback(e.joy, e.ball, e.rel, t)

            elif e.type == JOYHATMOTION:
                # joystick hat motion?...
                if joystick_hats_callback:
                    # ...joystick hats callback
                    joystick_hats_callback(e.joy, e.hat, e.value, t)

            elif e.type == JOYBUTTONUP:
                # joystick button up?...
                if joystick_buttons_callback:
                    # ...joystick buttons callback
                    joystick_buttons_callback(e.joy, e.button, False, t)

            elif e.type == JOYBUTTONDOWN:
                # joystick button down?...
                if joystick_buttons_callback:
                    # ...joystick buttons callback
                    joystick_buttons_callback(e.joy, e.button, True, t)

        # all done, set recursion level back to zero
        recurse_level = 0
