"""
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.0.5"

import heapq
import os.path

from rabbyt._rabbyt import *
from rabbyt.sprites import *
from rabbyt.anims import *

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 start 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:

        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::

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

    is equivalent to this::

        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 | 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 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)
