# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
#  * Neither the name of pyglet nor the names of its
#    contributors may be used to endorse or promote products
#    derived from this software without specific prior written
#    permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------

'''Application-wide functionality.

Most applications need only call `run` after creating one or more windows
to begin processing events.  For example, a simple application consisting of
one window is::

    from pyglet import app
    from pyglet import window

    win = window.Window()
    app.run()

To handle events on the main event loop, instantiate it manually.  The
following example exits the application as soon as any window is closed (the
default policy is to wait until all windows are closed)::

    event_loop = app.EventLoop()

    @event_loop.event
    def on_window_close(window):
        event_loop.exit()

:since: pyglet 1.1
'''

__docformat__ = 'restructuredtext'
__version__ = '$Id: __init__.py 2140 2008-07-27 04:15:52Z Alex.Holkner $'

import sys
import weakref

from pyglet import clock
from pyglet import event

_is_epydoc = hasattr(sys, 'is_epydoc') and sys.is_epydoc

class WeakSet(object):
    '''Set of objects, referenced weakly.

    Adding an object to this set does not prevent it from being garbage
    collected.  Upon being garbage collected, the object is automatically
    removed from the set.
    '''
    def __init__(self):
        self._dict = weakref.WeakKeyDictionary()

    def add(self, value):
        self._dict[value] = True

    def remove(self, value):
        del self._dict[value]

    def __iter__(self):
        for key in self._dict.keys():
            yield key

    def __contains__(self, other):
        return other in self._dict

    def __len__(self):
        return len(self._dict)

#: Set of all open displays.  Instances of `Display` are automatically added
#: to this set upon construction.  The set uses weak references, so displays
#: are removed from the set when they are no longer referenced.
#:
#: :type: `WeakSet`
displays = WeakSet()

#: Set of all open windows (including invisible windows).  Instances of
#: `Window` are automatically added to this set upon construction.  The set
#: uses weak references, so windows are removed from the set when they are no
#: longer referenced or are closed explicitly.
#:
#: :type: `WeakSet`
windows = WeakSet()


class BaseEventLoop(event.EventDispatcher):
    '''The main run loop of the application.

    Calling `run` begins the application event loop, which processes
    operating system events, calls `pyglet.clock.tick` to call scheduled
    functions and calls `pyglet.window.Window.on_draw` and
    `pyglet.window.Window.flip` to update window contents.

    Applications can subclass `EventLoop` and override certain methods
    to integrate another framework's run loop, or to customise processing
    in some other way.  You should not in general override `run`, as
    this method contains platform-specific code that ensures the application
    remains responsive to the user while keeping CPU usage to a minimum.
    '''

    #: Flag indicating if the event loop will exit in the next iteration.
    #: This is
    has_exit = False

    def run(self):
        '''Begin processing events, scheduled functions and window updates.

        This method returns when `has_exit` is set to True.

        Developers are discouraged from overriding this method, as the
        implementation is platform-specific.
        '''
        raise NotImplementedError('abstract')

    def _setup(self):
        global event_loop
        event_loop = self

        # Disable event queuing for dispatch_events
        from pyglet.window import Window
        Window._enable_event_queue = False

        # Dispatch pending events
        for window in windows:
            window.switch_to()
            window.dispatch_pending_events()

    def _idle_chance(self):
        '''If timeout has expired, manually force an idle loop.

        Called by window that have blocked the event loop (e.g. during
        resizing).
        '''

    def idle(self):
        '''Called during each iteration of the event loop.

        The method is called immediately after any window events (i.e., after
        any user input).  The method can return a duration after which
        the idle method will be called again.  The method may be called
        earlier if the user creates more input events.  The method
        can return `None` to only wait for user events.

        For example, return ``1.0`` to have the idle method called every
        second, or immediately after any user events.

        The default implementation dispatches the
        `pyglet.window.Window.on_draw` event for all windows and uses
        `pyglet.clock.tick` and `pyglet.clock.get_sleep_time` on the default
        clock to determine the return value.

        This method should be overridden by advanced users only.  To have
        code execute at regular intervals, use the
        `pyglet.clock.schedule` methods.

        :rtype: float
        :return: The number of seconds before the idle method should
            be called again, or `None` to block for user input.
        '''
        dt = clock.tick(True)

        # Redraw all windows
        for window in windows:
            if window.invalid:
                window.switch_to()
                window.dispatch_event('on_draw')
                window.flip()

        # Update timout
        return clock.get_sleep_time(True)

    def exit(self):
        '''Safely exit the event loop at the end of the current iteration.

        This method is convenience for setting `has_exit` to ``True``.
        '''
        self.has_exit = True

    def on_window_close(self, window):
        '''Default window close handler.'''
        if not windows:
            self.exit()

    if _is_epydoc:
        def on_window_close(window):
            '''A window was closed.

            This event is dispatched when a window is closed.  It is not
            dispatched if the window's close button was pressed but the
            window did not close.

            The default handler calls `exit` if no more windows are open.  You
            can override this handler to base your application exit on some
            other policy.

            :event:
            '''

        def on_enter():
            '''The event loop is about to begin.

            This is dispatched when the event loop is prepared to enter
            the main run loop, and represents the last chance for an
            application to initialise itself.

            :event:
            '''

        def on_exit():
            '''The event loop is about to exit.

            After dispatching this event, the `run` method returns (the
            application may not actually exit if you have more code
            following the `run` invocation).

            :event:
            '''

BaseEventLoop.register_event_type('on_window_close')
BaseEventLoop.register_event_type('on_enter')
BaseEventLoop.register_event_type('on_exit')

#: The global event loop.  Set to the correct instance when an `EventLoop` is
#: started.
#:
#: :type: `EventLoop`
event_loop = None

def run():
    '''Begin processing events, scheduled functions and window updates.

    This is a convenience function, equivalent to::

        EventLoop().run()

    '''
    EventLoop().run()

def exit():
    '''Exit the application event loop.

    Causes the application event loop to finish, if an event loop is currently
    running.  The application may not necessarily exit (for example, there may
    be additional code following the `run` invocation).

    This is a convenience function, equivalent to::

        event_loop.exit()

    '''
    if event_loop:
        event_loop.exit()

if _is_epydoc:
    EventLoop = BaseEventLoop
    EventLoop.__name__ = 'EventLoop'
    del BaseEventLoop
else:
    # Permit cyclic import.
    import pyglet
    pyglet.app = sys.modules[__name__]

    if sys.platform == 'darwin':
        from pyglet.app.carbon import CarbonEventLoop as EventLoop
    elif sys.platform in ('win32', 'cygwin'):
        from pyglet.app.win32 import Win32EventLoop as EventLoop
    else:
        from pyglet.app.xlib import XlibEventLoop as EventLoop


