# -*- mode: python; coding: utf-8 -*-
#
# Pigment Python tools
#
# Copyright © 2006, 2007, 2008 Fluendo Embedded S.L.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

# TODO: - Timing (ac/de)celeration

"""
"""

from pgm.utils import classinit, maths
from pgm.timing import modifier
import math, time, gobject
import pygst
#pygst.require('0.10')
import gst


# Repeat behavior constants
FORWARD, REVERSE = 0, 1
# End behavior constants
BEGIN, HOLD, END = 2, 3, 4
# Transformation constants
LINEAR, ACCELERATE, DECELERATE, SMOOTH = 5, 6, 7, 8
# Repeat constant
INFINITE = -1


class Controller(object):
    """
    """

    # Allows property fget/fset/fdel/doc overriding
    __metaclass__ = classinit.ClassInitMeta
    __classinit__ = classinit.build_properties

    def __init__(self, begin=0, duration=1000, resolution=5,
                 repeat_behavior=FORWARD, repeat_count=1, end_behavior=BEGIN,
                 modifiers=[], transformation=LINEAR, end_callback=None):
        self.begin = begin
        self.duration = duration
        self.resolution = resolution
        self.repeat_behavior = repeat_behavior
        self.repeat_count = repeat_count
        self.end_behavior = end_behavior
        self.modifiers = modifiers
        self.transformation = transformation
        self.end_callback = end_callback
        self._start_time = 0
        self._update_time = 0
        self._begin_time = 0
        self._repeat_counter = 0
        self._started = False
        self._tag = None

    def __repr__(self):
        string = {FORWARD:'FORWARD', REVERSE:'REVERSE',
                  BEGIN:'BEGIN', HOLD:'HOLD', END:'END'}
        return "<controller.Controller(begin=%r, duration=%r, resolution=%r, " \
               "repeat_behavior=%s, repeat_count=%r, end_behavior=%s, "        \
               "modifiers=%r)>"                                                \
               % (self._begin, self._duration, self._resolution,
                  string[self._repeat_behavior], self._repeat_count,
                  string[self._end_behavior], self._modifiers)

    # Property definitions

    def begin__get(self):
        """The time (in ms) to wait after a start()"""
        return self._begin

    def begin__set(self, begin):
        self._begin = max(0, int(begin))

    def duration__get(self):
        """The duration (in ms) of each animation"""
        return self._duration

    def duration__set(self, duration):
        self._duration = max(0, int(duration))

    def resolution__get(self):
        """The update frequency (in ms)"""
        return self._resolution

    def resolution__set(self, resolution):
        self._resolution = max(0, int(resolution))

    def repeat_behavior__get(self):
        """The repeat behavior at the end of an animation"""
        return self._repeat_behavior

    def repeat_behavior__set(self, repeat_behavior):
        if repeat_behavior == FORWARD or repeat_behavior == REVERSE:
            self._repeat_behavior = repeat_behavior
        else:
            self._repeat_behavior = FORWARD

    def repeat_count__get(self):
        """The number of iteration"""
        return self._repeat_count

    def repeat_count__set(self, repeat_count):
        # -1 is the INFINITE constant for the repeat mode
        self._repeat_count = max(-1, int(repeat_count))

    def end_behavior__get(self):
        """The behavior at the end of an animation"""
        return self._end_behavior

    def end_behavior__set(self, end_behavior):
        if end_behavior == BEGIN or end_behavior == HOLD \
               or end_behavior == END:
            self._end_behavior = end_behavior

    def modifiers__get(self):
        """The list of object modifiers"""
        return self._modifiers

    def modifiers__set(self, modifiers):
        if isinstance(modifiers, (list, tuple)):
            try:
                for i in modifiers:
                    if not isinstance(i, modifier.Modifier):
                        raise TypeError, 'Not a modifier.Modifier object'
            # modifiers is an empty list
            except IndexError:
                self._modifiers = []
            # All the objects in the list are Modifier
            else:
                self._modifiers = list(modifiers)
        else:
            # FIXME: I would avoid using gst.warn
            gst.warn('Controller.set_modifiers(): modifiers must be list or ' \
                     'tuple')
            self._modifiers = []

    def transformation__get(self):
        """The transformation type"""
        return self._transformation

    def transformation__set(self, transformation):
        if transformation in (LINEAR, ACCELERATE, DECELERATE, SMOOTH):
            self._transformation = transformation

    def end_callback__get(self):
        """
        The callback called at the end of the controller
        (duration*repeat_count ms).
        """
        return self._end_callback

    def end_callback__set(self, end_callback):
        if callable(end_callback):
            self._end_callback = end_callback
        else:
            self._end_callback = None

    def started__get(self):
        """Is the controller started ?"""
        return self._started

    # Public methods

    def start(self):
        """
        """
        self._start_time = long(time.time() * 1000)
        self._update_time = self._start_time
        self._begin_time = self._start_time + self._begin
        self._repeat_counter = 0
        self._started = True
        # Add the source to the mainloop
        self._tag = gobject.timeout_add(self._resolution, self._update)

    def stop(self):
        """
        """
        if self._started and self._tag != None:
            # Remove the source from the mainloop
            gobject.source_remove(self._tag)
            self._tag = None

            # Respect the end behavior by sending the appropriate factor to
            # the modifiers.
            if self._end_behavior == BEGIN:
                factor = 0.0
            elif self._end_behavior == END:
                factor = 1.0
            elif self._end_behavior == HOLD:
                if self._duration != 0:
                    factor = self._compute_factor()
                else:
                    factor = 1.0
            for modifier in self._modifiers:
                modifier.update(factor)

            # Reset internal data
            self._start_time = 0
            self._update_time = 0
            self._begin_time = 0
            self._repeat_counter = 0
            self._started = False

            # Let's call the end callback
            if self._end_callback:
                self._end_callback(self)

    # Private methods

    def _update(self):
        # Compute elapsed time
        self._update_time = long(time.time() * 1000)
        diff_begin_time = self._update_time - self._begin_time

        # Is the controller duration elapsed ?
        if diff_begin_time >= self._duration:
            self._repeat_counter += 1
            # Repeat the animation
            if (self._repeat_count == INFINITE \
               or self._repeat_counter < self._repeat_count) \
               and self._duration != 0:
                self._begin_time += self._duration
            # Stop the animation
            else:
                self.stop()
                return False

        factor = self._compute_factor()

        # Update the modifiers
        for modifier in self._modifiers:
            modifier.update(factor)

        # Return True to keep the source in the mainloop
        return True

    def _compute_factor(self):
        # Compute the factor
        elapsed_time = self._update_time - self._begin_time
        factor = min(1.0, float(elapsed_time) / self._duration)
        # Reverse the factor if we are in REVERSE mode
        if self._repeat_behavior == REVERSE and self._repeat_counter % 2:
            factor = 1.0 - factor
        # Apply the transformation on the time
        if self._transformation == SMOOTH:
            sinus = math.sin(maths.lerp(-maths.PI2, maths.PI2, factor))
            factor = (sinus + 1.0) * 0.5
        elif self._transformation == DECELERATE:
            factor = math.sin(maths.lerp(0, maths.PI2, factor))
        elif self._transformation == ACCELERATE:
            factor = math.sin(maths.lerp(-maths.PI2, 0, factor)) + 1.0

        return factor
