# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

from elisa.plugins.pigment import maths
import math, time
from twisted.internet import reactor

# Repeat behavior constants
FORWARD, REVERSE = 0, 1
# Transformation constants
LINEAR, ACCELERATE, DECELERATE, SMOOTH = 5, 6, 7, 8

INFINITE = -1

class Animation(object):
    """
    An Animation instance controls the evolution of an animation. It is the
    object controlling when a modifier should update its corresponding
    value(s).

    @ivar duration: the duration of the animation -- should not be modified
                    when the animation is running
    @type duration: C{int}

    @ivar repeat:   whether the animation should be repeated when it is
                    finished
    @type repeat:   C{bool}

    @ivar repeat_behavior:  way of repeating the animation
    @type repeat_behavior:  C{int}, chosen among (C{FORWARD}, C{REVERSE})

    @ivar transformation:   transformation to apply on "wall clock" time --
                            should not be modified when the animation is running
    @type transformation:   C{int}, chosen among (C{LINEAR}, C{ACCELERATE},
                            C{DECELERATE}, C{SMOOTH})

    @ivar end_callback:     callback to be called when the animation ends or
                            is stopped
    @type end_callback:     C{callable}

    @ivar started:  whether the animation is running
    @type started:  C{bool}

    @ivar modifiers:    the modifiers that will be updated by this animation
    @type modifiers:    C{list} of
                        C{elisa.plugins.pigment.animation.modifier.Modifier}
                        instances.

    """

    # The ticker used by animations
    _ticker = None

    def __init__(self, modifiers):
        self.duration = 1000
        self.repeat = False
        self.repeat_behavior = FORWARD
        self.transformation = LINEAR
        self.end_callback = None

        self.started = False
        self.modifiers = modifiers
        self._start_time = 0

        self._repeat_count = 0

    # Class methods

    @classmethod
    def set_ticker(cls, ticker):
        """
        Define the Ticker to use for all the animations that will be created.
        """
        cls._ticker = ticker

    @classmethod
    def get_ticker(cls):
        """
        Get the Ticker used by animations.
        """
        return cls._ticker

    # Public methods

    def start(self):
        """
        """
        if not self.started:
            self.started = True
            self._start_time = long(time.time() * 1000)

            # Add the animation to the ticker
            Animation._ticker.add_animation(self)

            # FIXME: this is Pigment specific to make sure that the ticker
            # will start ticking
            self.update(self._start_time)

    def stop(self):
        """
        """
        if self.started:
            self.started = False

            # Remove the animation from the ticker
            Animation._ticker.remove_animation(self)

            # Reset internal data
            self._start_time = 0
            self._repeat_count = 0

            # Call the end of animation callback
            if self.end_callback != None:
                # FIXME: sending the animation object is only done for
                # backward compatibility sake
                reactor.callFromThread(self.end_callback, self)

    # Protected methods

    def update(self, update_time):
        elapsed_time = update_time - self._start_time

        if elapsed_time < self.duration:
            # Update the modifiers
            fraction = self._compute_fraction(elapsed_time)

            # Update the modifiers
            for modifier in self.modifiers:
                modifier.update(fraction)
        else:
            # Animation's duration elapsed
            if self.repeat:
                # Repeat the animation
                self._start_time = update_time
                self._repeat_count += 1
            else:
                # Perform a last update
                for modifier in self.modifiers:
                    modifier.update(1.0)

                # End the animation
                self.stop()

    # Private methods

    def _compute_fraction(self, elapsed_time):
        # Compute the fraction
        fraction = min(1.0, float(elapsed_time) / self.duration)

        # Handle REVERSE repeat mode
        if self.repeat_behavior == REVERSE and self._repeat_count % 2:
            fraction = 1.0 - fraction

        fraction = max(0.0, fraction)

        # Apply the transformation on the time
        if self.transformation == SMOOTH:
            sinus = math.sin(maths.lerp(-maths.PI2, maths.PI2, fraction))
            fraction = (sinus + 1.0) * 0.5
        elif self.transformation == DECELERATE:
            fraction = math.sin(maths.lerp(0, maths.PI2, fraction))
        elif self.transformation == ACCELERATE:
            fraction = math.sin(maths.lerp(-maths.PI2, 0, fraction)) + 1.0

        return fraction
