# -*- 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: - Spline interpolation

"""
"""

from pgm.utils import classinit, maths
from pgm.timing import keyframe

# Interpolation constants
LINEAR, SPLINE = 0, 1
ABSOLUTE, RELATIVE = 0, 1


class Modifier(object):
    """
    """

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

    def __init__(self, obj=[], prop=None, timeline=[], interpolation=LINEAR,
                 operation=ABSOLUTE):
        self._lerp = None
        self._operation = operation
        self._prop_initial_values = []

        self.obj = obj
        self.prop = prop
        self.timeline = timeline
        self.interpolation = interpolation

    def __repr__(self):
        string = {LINEAR:'LINEAR', SPLINE:'SPLINE'}
        return "<modifier.Modifier(obj=%r, prop=%r, timeline=%r, " \
               "interpolation=%s)>" % (self._obj, self._prop, self._timeline,
                                       string[self._interpolation])

    # Property definitions

    def obj__get(self):
        """The list of objects"""
        return self._obj

    def obj__set(self, obj):
        if isinstance(obj, (list, tuple)):
            try:
                obj_type = type(obj[0])
                for i in obj[1:]:
                    pass
##                     if (not issubclass(obj_type, type(i))) or (not issubclass(type(i),obj_type)):
##                         error = 'Mismatching types in the list (%r vs %r)'
##                         raise TypeError, error % (type(i), obj_type)
            # obj is an empty list
            except IndexError:
                self._obj = []
            # All the objects in the list are of the same type
            else:
                self._obj = list(obj)
        else:
            raise TypeError, 'Modifier.obj__set(): obj must be list or tuple'
            self._obj = []

    def prop__get(self):
        """The property of the object class"""
        return self._prop

    def prop__set(self, prop):
        # Check if prop is in the namespace of the obj class
        try:
            attrib = getattr(self._obj[0], prop)
            self._prop = prop
            # Store a pointer to the adapted lerp func
            if isinstance(attrib, (list, tuple)):
                self._lerp = maths.lerp_seq
            else:
                self._lerp = maths.lerp

            if self._operation == RELATIVE:
                self._save_property_initial_value()

        except TypeError:
            raise TypeError, 'Modifier.prop__set(): prop must be string'
            self._prop = None
        except IndexError:
            raise TypeError, 'Modifier.prop__set(): obj list is empty'
            self._prop = None
        except AttributeError:
            raise TypeError, 'Modifier.prop__set(): No attribute %s in %r' \
                % (prop, self._obj[0])
            self._prop = None

    def timeline__get(self):
        """The timeline (list of keyframes) to interpolate"""
        return self._timeline

    def timeline__set(self, timeline):
        if isinstance(timeline, (list, tuple)):
            try:
                for i in timeline:
                    if not isinstance(i, keyframe.KeyFrame):
                        raise TypeError, 'Not a keyframe.KeyFrame object'
            # obj is an empty list
            except IndexError:
                self._timeline = []
            # All the objects in the list are KeyFrame
            # FIXME: A sort on the KeyFrame.time parameter is needed
            #        here because we can't assume the user give us a
            #        sorted list.
            else:
                self._timeline = timeline
        else:
            raise TypeError, 'Modifier.timeline__set(): ' \
                'timeline must be list or tuple'
            self._timeline = []
        if len(self._timeline) < 2:
            raise TypeError, 'timeline must at least contain 2 KeyFrame'

    def interpolation__get(self):
        """The interpolation type"""
        return self._interpolation

    def interpolation__set(self, interpolation):
        if interpolation == LINEAR or interpolation == SPLINE:
            self._interpolation = interpolation
        else:
            print 'Modifier.interpolation__set():' \
                  'Bad interpolation type, reseting to LINEAR.'
            self._interpolation = LINEAR

    def _save_property_initial_value(self):
        """
        """
        for obj in self._obj:
            value = obj.__getattribute__(self._prop)
            self._prop_initial_values.append(value)

    # Public methods

    def next_keyframe_index(self, factor):
        """
        Return the index of the next KeyFrame that will be reached in the
        timeline starting from time factor.
        """
        # We assume here that self._timeline is sorted.
        timeline_len = len(self._timeline)
        i = 0
        while self._timeline[i].time < factor:
            i += 1
            # The user gives us a partial timeline finishing before 1.0.
            if i >= timeline_len:
                return -1

        return i

    def update(self, factor):
        """
        """
        i = self.next_keyframe_index(factor)
        if i == -1:
            return

        # Expand the factor from the time range
        # [self._timeline[i-1].time, self._timeline[i].time]
        # to the range [0, 1].
        new_factor = (factor - self._timeline[i-1].time) \
                      / (self._timeline[i].time - \
                         self._timeline[i-1].time)
        # Linearly interpolate the new_factor between the KeyFrame values.
        value = self._lerp(self._timeline[i-1].value,
                           self._timeline[i].value, new_factor)

        # Let's modify the objects.
        rank = 0
        for obj in self._obj:
            if self._operation == RELATIVE:
                initial_value = self._prop_initial_values[rank]
                if isinstance(initial_value, (list, tuple)):
                    relative_value = tuple(map(lambda x,y: x+y,
                                               initial_value, value))
                else:
                    relative_value = initial_value + value
                obj.__setattr__(self._prop, relative_value)
            else:
                obj.__setattr__(self._prop, value)
            rank += 1
