"""
Rabbyt is a fast 2d sprite library for Python.
"""

__credits__ = (
"""
Copyright (C) 2007  Matthew Marshall

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
""")

__author__ = "Matthew Marshall <matthew@matthewmarshall.org>"
__version__ = "0.6"

import heapq
import os.path

from rabbyt._rabbyt import *
from rabbyt.sprites import *
from rabbyt.anims import *
import rabbyt.physics, rabbyt.collisions

extend_types = dict(constant=1, extrapolate=2, repeat=3, reverse=4)

def _handle_time_args(startt, endt, dt):
    if startt is None:
        startt = get_time()
    if endt is None:
        if dt is None:
            raise ValueError("Either dt or endt must be given.")
        endt = startt + dt

    assert startt < endt

    return int(startt), int(endt)


def lerp(start, end, startt=None, endt=None, dt=None, extend="constant"):
    """
    ``lerp(start, end, [startt,] [endt,] [dt,] [extend])``

    Linearly interpolates between ``start`` and ``end`` as time moves from
    ``startt`` to ``endt``.

    ``startt`` is the time to start.  It defaults to ``rabbyt.get_time()``.

    To specify the ending time, use either ``endt``, which is the absolute
    time, or ``dt``, which is relative from ``startt``.

    For example, all of the following are equivalent:

        .. sourcecode:: python

            lerp(0, 1, rabbyt.get_time(), rabbyt.get_time()+1000)
            lerp(0, 1, rabbyt.get_time(), dt=1000)
            lerp(0, 1, dt=1000)

    ``extend`` is a string defining what to do before ``startt`` and after
    ``endt``. Possible values are "constant", "extrapolate", "repeat",
    "reverse".  The default is "constant".  Check out the extend_modes.py
    example for more info.

    ``start`` and ``end`` can either be numbers, or tuples of numbers.  If
    they are tuples, a tuple of anims will be returned.  For example, this
    line:

        .. sourcecode:: python

            sprite.rgba = lerp((0,1,0,.5), (1,0,1,1), dt=1000)

    is equivalent to this:

        .. sourcecode:: python

            sprite.red   = lerp(0, 1, dt=1000)
            sprite.green = lerp(1, 0, dt=1000)
            sprite.blue  = lerp(0, 1, dt=1000)
            sprite.alpha = lerp(.5,1, dt=1000)

    """
    extend = extend_types[extend]

    startt, endt = _handle_time_args(startt, endt, dt)

    try:
        iter(start), iter(end)
    except TypeError:
        return AnimLerp(start, end, startt, endt, extend)
    else:
        return [AnimLerp(s, e, startt, endt, extend)
                for s,e in zip(start, end)]

def wrap(bounds, parent, static=True):
    """
    ``wrap(bounds, parent, static=True) -> AnimWrap or tuple of AnimWraps``

    Wraps a parent ``Anim`` to fit within ``bounds``.  ``bounds`` should be an
    object that supports item access for at least ``bounds[0]`` and
    ``bounds[1]``.  (A list or tuple with a length of 2 would work great.)

    If ``static`` is ``True``, ``bounds`` is only read once and stored in C
    variables for fast access. This is much faster, but doesn't work if
    ``bounds`` is an object you wish to mutate.

    If ``parent`` is a iterable, a tuple of anims will be returned instead
    of a single one.  (This is similar to ``lerp()``.)
    """
    try:
        iter(parent)
    except TypeError:
        return AnimWrap(bounds, parent, static)
    else:
        return tuple([AnimWrap(bounds, p, static) for p in parent])

def bezier3(p0, p1, p2, p3, startt=None, endt=None, dt=None, extend="constant"):
    """
    ``bezier3(p0, p1, p2, p3, [startt,] [endt,] [dt,] [extend])``

    Interpolates along a cubic bezier curve as defined by ``p0``, ``p1``,
    ``p2``, and ``p3``.

    ``startt``, ``endt``, ``dt``, and ``extend`` work as in ``lerp()``.

    ``p0``, ``p1``, ``p2``, and ``p3`` can be tuples, but they must all be the
    same length.
    """
    extend = extend_types[extend]
    startt, endt = _handle_time_args(startt, endt, dt)

    try:
        [iter(p) for p in [p0,p1,p2,p3]]
    except TypeError:
        return AnimStaticCubicBezier(p0, p1, p2, p3, startt, endt, extend)
    else:
        return [AnimStaticCubicBezier(p0, p1, p2, p3, startt, endt, extend)
                for p0, p1, p2, p3 in zip(p0, p1, p2, p3)]


class Scheduler(object):
    """
    ``Scheduler()``

    Scheduler provides... (wait for it...)  scheduling!

    You may create your own scheduler instances, or use the default
    ``rabbyt.scheduler``
    """
    def __init__(self):
        self.heap = []

    def add(self, time, callback):
        """
        ``add(time, callback)``

        Schedules a ``callback`` to be called at a given ``time``.
        """
        heapq.heappush(self.heap, (time, callback))

    def pump(self, time=None):
        """
        ``pump([time])``

        Calls all callbacks that have been scheduled for before ``time``.

        If ``time`` is not given, the value returned by ``rabbyt.get_time()``
        will be used.
        """
        if time is None:
            time = get_time()
        try:
            while self.heap[0][0] <= time:
                heapq.heappop(self.heap)[1]()
        except IndexError, e:
            # If the IndexError was raised due to something other than an
            # empty heap we don't want to silence it.
            if len(self.heap) != 0:
                raise e

scheduler = Scheduler()


def init_display(size=(640, 480)):
    """
    init_display(size=(640, 480))

    This is a small shortcut to create a pygame window, set the viewport, and
    set the opengl attributes need for rabbyt.

    This function depends on pygame.
    """
    import pygame
    pygame.init()
    pygame.display.set_mode(size, pygame.OPENGL | pygame.DOUBLEBUF)
    set_viewport(size)
    set_default_attribs()

_texture_cache = {}

data_directory = ""

def pygame_load_texture(filename, filter=True, mipmap=True):
    """
    ``pygame_load_texture(filename, filter=True, mipmap=True) -> texture_id,
    size``

    Reads an image from a file and loads it as a texture.  Pygame is used for
    reading the image.

    If ``filename`` is a relative path, the working directory is searched
    first, then ``rabbyt.data_directory`` is searched for the file.
    """
    if filename not in _texture_cache:
        import pygame
        if os.path.exists(filename):
            img = pygame.image.load(filename)
        else:
            img = pygame.image.load(os.path.join(data_directory, filename))
        data, size = pygame.image.tostring(img, 'RGBA', True), img.get_size()
        _texture_cache[filename] = load_texture(data, size, "RGBA",
                filter, mipmap), size
    return _texture_cache[filename]
set_load_texture_file_hook(pygame_load_texture)

__all__ = __docs_all__ = ('anims collisions fonts sprites physics vertexarrays '
'Scheduler '
'lerp wrap bezier3 '
'init_display set_viewport set_default_attribs set_gl_color clear '
'get_gl_vendor '
'render_unsorted '
'load_texture update_texture unload_texture pygame_load_texture '
'set_load_texture_file_hook ').split()