# -*- 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.core.utils import locale_helper
from elisa.core.input_event import *
from elisa.core.utils.cancellable_defer import CancelledError

from elisa.plugins.pigment.widgets.widget import Widget
from elisa.plugins.pigment.widgets.list import List
from elisa.plugins.pigment.graph.text import Text
from elisa.plugins.pigment.graph.image import Image
from elisa.plugins.pigment.graph.group import Group
from elisa.plugins.pigment.widgets.list_horizontal import ListHorizontal

from elisa.plugins.poblesec.widgets.menu_item import MenuItemWidget

from elisa.plugins.poblesec.base.list import BaseListController
from elisa.plugins.poblesec.widgets.sliced_image import SlicedImageHorizontal
from elisa.plugins.poblesec.widgets.image_with_reflection import \
    ImageWithReflection

import pgm
from elisa.plugins.pigment import maths
from elisa.plugins.pigment.animation.animation import DECELERATE
from elisa.plugins.pigment.animation.implicit import AnimatedObject


class CoverflowList(List):

    def visible_range_size__set(self, visible_range_size):
        self._item_width = 0.3
        self._item_height = 0.6
        super(CoverflowList, self).visible_range_size__set(visible_range_size)

    visible_range_size = property(List.visible_range_size__get,
                                  visible_range_size__set)

    def _piecewise_interpolation(self, x, y, factor):
        factor += 0.5
        t = self._visible_range_size
        x = map(lambda a: t*a, x)

        # clamp after lower and upper limits
        if factor < x[0]:
            return y[0]
        elif factor > x[-1]:
            return y[-1]
        else:
            # general case: looking for the segment where factor belongs
            i = 0
            while factor > x[i+1]:
                i += 1

            # factor must be between 0.0 and 1.0
            new_factor = (factor - x[i]) / (x[i+1] - x[i])
            return maths.lerp(y[i], y[i+1], new_factor)

    def compute_x(self, index):
        w = 1.0-self._item_width
        x = [0.0, 0.4, 0.5, 0.6, 1.0]
        y = [0.0, w*0.1, w*0.5, w*0.9, w]
#        y = [e-self._widget_width/2.0 for e in y]

        return self._piecewise_interpolation(x, y, index)

    def compute_z(self, index):
        min_z = -200.0
        max_z = 200.0

        x = [0.0, 0.4, 0.5, 0.6, 1.0]
        y = [min_z, min_z/2.0, max_z, min_z/2.0, min_z]

        return self._piecewise_interpolation(x, y, index)

    def compute_opacity(self, index):
        x = [0.0, 0.1, 0.9, 1.0]
        y = [0, 255, 255, 0]

        return self._piecewise_interpolation(x, y, index)

    def _prepare_widget(self, widget):
        # center the items
        # assuming that each item has a reflection of the same height as
        # itself
        widget.position = (0.0, self._item_height/4.0, 0.0)
        widget.size = (self._item_width, self._item_height)

    def _layout_widget(self, widget, position):
        widget.x = self.compute_x(position)
        widget.z = self.compute_z(position)
        widget.opacity = self.compute_opacity(position)

    def _selected_to_range_start(self, selected_item_index):
        half_size = (self.visible_range_size-1.0)/2.0
        prev_selected = selected_item_index
        visible_range_start = selected_item_index-half_size
        return visible_range_start

    def _create_widgets(self):
        # FIXME: do not destroy and reload everything
        for widget in self._widgets:
            self.remove(widget)
        self._widgets[:] = []

        nb_widgets = self._visible_range_size+1
        for i in xrange(nb_widgets):
            widget = self._widget_class()
            widget.connect('clicked', self._fcl_child_clicked)
            self._widgets.append(widget)
            self.add(widget, forward_signals=False)
            self._prepare_widget(widget)

    def handle_input(self, manager, event):
        if event.value == EventValue.KEY_GO_LEFT:
            self.selected_item_index -= 1
            return True
        elif event.value == EventValue.KEY_GO_RIGHT:
            self.selected_item_index += 1
            return True
        if event.value == EventValue.KEY_PAGE_UP:
            self.selected_item_index -= (self.visible_range_size - 1)
            return True
        if event.value == EventValue.KEY_PAGE_DOWN:
            self.selected_item_index += (self.visible_range_size - 1)
            return True

        return super(CoverflowList, self).handle_input(manager, event)

    # Signals support methods

    def do_key_press_event(self, viewport, event, widget):
        if event.keyval == pgm.keysyms.Left:
            self.selected_item_index -= 1
        elif event.keyval == pgm.keysyms.Right:
            self.selected_item_index += 1
        elif event.keyval == pgm.keysyms.Home:
            self.selected_item_index = 0
        elif event.keyval == pgm.keysyms.End:
            self.selected_item_index = len(self.model) - 1

    def do_scrolled(self, x, y, z, direction, time):
        if direction == pgm.SCROLL_UP:
            self.selected_item_index -= 1
        else:
            self.selected_item_index += 1

    def _fcl_child_clicked(self, drawable, x, y, z, button, time, pressure):
        if self._dragging and self._drag_accum > self.drag_threshold:
            return True

        index = self._widgets.index(drawable)
        item_index = self._item_index_from_widget_index(index)
        item = self.model[item_index]
        self.emit('item-clicked', item)
        return True

    def do_clicked(self, x, y, z, button, time, pressure):
        # always let the children handle the clicks
        return False

    def do_drag_motion(self, x, y, z, button, time, pressure):
        time_since_last = time - self._last_drag_motion
        if time_since_last > self.drag_motion_resolution:
            self._last_drag_motion = time
        else:
            return True

        relative_item_width = self._item_width
        absolute_item_width = relative_item_width*self.absolute_width
        motion = (x-self._initial[0])/absolute_item_width
        self.visible_range_start -= motion

        time_delta = time - self._initial[2]
        if time_delta != 0:
            self.speed = motion/time_delta*1000.0
            self.speed = maths.clamp(self.speed, -14.0, 14.0)

        self._initial = (x, y, time)
        self._drag_accum += abs(motion) 

        return True

    @classmethod
    def _demo_widget(cls, *args, **kwargs):
        from elisa.plugins.pigment.graph.image import Image

        widget = cls(Image)
        widget.visible = True

        model = range(15)
        def renderer(item, widget):
            return
        widget.set_renderer(renderer)
        widget.set_model(model)

        widget.position = (0.0, 0.0, 0.0)
        widget.size = (4.0, 3.0)

        def item_clicked_cb(self, item):
            print "item clicked", item

        widget.connect('item-clicked', item_clicked_cb)

        return widget


class CustomListHorizontal(ListHorizontal):
    def _selected_to_range_start(self, selected):
        half_size = (self.visible_range_size-1.0)/2.0
        if selected <= half_size or len(self.model) <= self.visible_range_size:
            visible_range_start = 0.0
        else:
            visible_range_start = selected-half_size
        return visible_range_start

class CoverflowController(BaseListController):

    def nodes_setup(self):
        # FIXME: bad naming: looks like a model not a widget
        self.nodes = CoverflowList(self.node_widget, 9)
        self.widget.add(self.nodes)
        self.nodes.size = (1.0, 1.0)
        self.nodes.position = (0.0, 0.1, 0.0)
        self.nodes.visible = True

        self.titles_setup()

    def titles_setup(self):
        self._captions_group = Group()
        self.widget.add(self._captions_group)
        self._captions_group.size = (1.0, 1.0)
        self._captions_group.position = (0, 0, 0)
        self._captions_group.visible = True

        self._animated_captions_group = AnimatedObject(self._captions_group)

        self.title = Text()
        self._captions_group.add(self.title)
        self.title.ellipsize = pgm.TEXT_ELLIPSIZE_MIDDLE
        self.title.alignment = pgm.TEXT_ALIGN_CENTER
        self.title.bg_a = 0
        # FIXME: hardcoded values
        self.title.font_size = "medium"
        self.title.weight = pgm.TEXT_WEIGHT_BOLD
        self.title.size = (0.65, 0.04)
        self.title.position = ((1.0-self.title.width)/2.0, 0.08, 0.0)
        self.title.visible = True

        source_y = 0.65
        source_height = 0.04

        self.source_label = Text()
        self._captions_group.add(self.source_label)
        self.source_label.size = (0.2, source_height)
        self.source_label.position = ((1.0-self.source_label.width)/2.0, source_y, 0.0)
        self.source_label.fg_color = (81, 81, 81, 255)
        self.source_label.bg_color = (0, 0, 0, 0)
        self.source_label.alignment = pgm.TEXT_ALIGN_CENTER
        self.source_label.visible = True

        self.source_icon = Image()
        self._captions_group.add(self.source_icon)
        self.source_icon.size = (0.2, source_height)
        self.source_icon.position = (0.50, source_y, 0.0)
        self.source_icon.bg_color = (0, 0, 0, 0)
        self.source_icon.visible = True

    def node_selected(self, widget, item, previous_item):
        self._fade_out_title()

    def set_title_from_item(self, item):
        pass

    def _fade_out_title(self):
        # do not do anything if it was already fading out
        if self._animated_captions_group.opacity == 0.0:
            return

        self._animated_captions_group.stop_animations()

        def faded_out(timer):
            self._fade_in_title()
            selected_item = self.model[self.nodes.selected_item_index]
            self.set_title_from_item(selected_item)

            source_icon = getattr(selected_item, 'source_icon', None)
            if source_icon:
                self.frontend.load_from_theme(source_icon, self.source_icon)
            else:
                self.source_icon.clear()

            source_label = getattr(selected_item, 'source_label', None)
            if source_icon:
                self.source_label.label = source_label
            else:
                self.source_label.label = ''

        # fade out
        self._animated_captions_group.setup_next_animations(duration=200,
                                      transformation=DECELERATE,
                                      end_callback=faded_out)
        self._animated_captions_group.opacity = 0.0

    def _fade_in_title(self):
        # fade in
        self._animated_captions_group.setup_next_animations(duration=200,
                                        transformation=DECELERATE)
        self._animated_captions_group.opacity = 255


class ImageWithReflectionCoverflowController(CoverflowController):

    """
    Coverflow controller tied to the image with reflection widget.
    """

    node_widget = ImageWithReflection

    def node_renderer(self, item, widget):
        """
        Render a node using the common API methods defined by the
        L{elisa.plugins.poblesec.base.list.GenericListViewMode} class.
        """
        # Cancel previous deferred calls for this widget
        self.cancel_deferreds(widget)

        def _failure(failure):
            # Swallow errbacks only when the deferred has been cancelled
            failure.trap(CancelledError)

        # Set the default image of the widget
        widget.image.clear()
        default_image = self._view_mode.get_default_image(item)
        if default_image:
            self.frontend.load_from_theme(default_image, widget.image)

        # Set the real image of the widget
        theme = self.frontend.get_theme()
        image_deferred = self._view_mode.get_image(item, theme)
        if image_deferred is not None:
            def got_thumbnail(thumbnail_file):
                if thumbnail_file is not None:
                    # Pigment supports unicode, let's decode the
                    # thumbnail path if it is not unicode
                    if not isinstance(thumbnail_file, unicode):
                        thumbnail_file = thumbnail_file.decode(locale_helper.system_encoding())
                    widget.image.set_from_file(thumbnail_file)

            self.register_deferred(widget, image_deferred)
            image_deferred.addCallbacks(got_thumbnail, _failure)

    def set_frontend(self, frontend):
        super(ImageWithReflectionCoverflowController, self).set_frontend(frontend)

        # visible items set to 3.6 in order to fit a little bit more than 3
        # items thus making the ones after easily discoverable
        self.actions_list = CustomListHorizontal(MenuItemWidget, 3.6)
        self.widget.add(self.actions_list)
        # adjust the widget's size: easy way to have nicer look
        self.actions_list.size = (1.0, 0.08)
        self.actions_list.position = (0.05, 0.8, 0.0)

        def renderer(item, widget):
            self.frontend.load_from_theme(item.icon, widget.icon)
            widget.label.label = item.title

        self.actions_list.set_renderer(renderer)
        self.actions_list.set_model(self.actions)
        self.actions_list.set_selector(self._create_selector())

        self.actions_list.connect('item-clicked', self._action_clicked)

        self.actions_list.visible = True

        self.widget.add_navigation_rule(self.nodes, EventValue.KEY_GO_DOWN,
                                        self.actions_list)
        self.widget.add_navigation_rule(self.actions_list,
                                        EventValue.KEY_GO_UP, self.nodes)

    def _action_clicked(self, widget, action):
        action_index = widget._widget_index_from_item_index(self.actions.index(action))
        selected_action = widget._widgets[action_index]

        if selected_action != self._previous_clicked:
            selected_action.activate(previous=self._previous_clicked)
            self._previous_clicked = selected_action
            action.run()

    def _create_selector(self):
        selector = SlicedImageHorizontal()
        return selector

    def set_title_from_item(self, item):
        """
        Set the title of the coverflow for an item using the common API methods
        defined by the L{elisa.plugins.poblesec.base.list.GenericListViewMode}
        class.
        """
        # Cancel previous deferred calls for this widget
        self.cancel_deferreds(self.title)

        def _failure(failure):
            # Swallow errbacks only when the deferred has been cancelled
            failure.trap(CancelledError)

        def got_label(text):
            self.title.label = text

        label_deferred = self._view_mode.get_label(item)
        label_deferred.addCallbacks(got_label, _failure)
        self.register_deferred(self.title, label_deferred)


if __name__ == "__main__":
    import logging
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    lst = CoverflowList.demo()
    try:
        __IPYTHON__
    except NameError:
        pgm.main()
