# -*- 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.

import pgm

from elisa.core.input_event import EventValue

from elisa.plugins.pigment.widgets.list import List
from elisa.plugins.pigment import maths

import math
import time as mod_time


class ListCarrousel(List):

    def compute_x(self, factor):
        min_x = 0.0
        max_x = 1.0-self._item_width
        x = factor*(max_x-min_x) + min_x
        return x

    def compute_y(self, factor):
        min_y = 0.0
        max_y = 1.0-self._item_height
        y = factor*(max_y-min_y) + min_y
        return y

    def compute_z(self, factor):
        min_z = -400.0
        max_z = 0.0
        z = factor*(max_z-min_z) + min_z
        return z

    def compute_opacity(self, factor):
        min_opacity = 40
        max_opacity = 255
        opacity = factor*(max_opacity-min_opacity) + min_opacity
        return opacity

    def compute_zoom(self, factor):
        min_zoom = 0.6
        max_zoom = 1.25
        zoom = factor*(max_zoom-min_zoom) + min_zoom
        return zoom

    def _create_widgets(self):
        for i in xrange(len(self._widgets)):
            self._delete_widget_at(0)

        for i in xrange(len(self.model)):
            self._create_widget_at(i)

    def _create_widget_at(self, index):
        widget = self._widget_class()
        widget.connect('clicked', self._fcl_child_clicked)
        self.add(widget, forward_signals=False)
        self._widgets.insert(index, widget)
        self._prepare_widget(widget)
        self._layout_all_widgets()

    def _delete_widget_at(self, index):
        widget = self._widgets.pop(index)
        self.remove(widget)

    def set_model(self, model):
        super(ListCarrousel, self).set_model(model)
        self._create_and_render_all()

    def _create_and_render_all(self):
        self._create_widgets()
        self._layout_all_widgets()
        self._render_all_widgets()

    def _on_items_inserted(self, notifier, index, items):
        self._item_width = 1.0/len(self.model)
        self._item_height = 0.54

        self._create_widget_at(index)
        self._render_widgets(index, len(self._widgets)-1)
        self._layout_all_widgets()

        if self.selected_item_index == -1:
            # no item from self.model was selected, select the first one by default
            self.selected_item_index = 0

    def _on_items_deleted(self, notifier, index, items):
        self._item_width = 1.0/len(self.model)
        self._item_height = 0.54

        self._delete_widget_at(index)
        self._render_widgets(index, len(self._widgets)-1)
        self._layout_all_widgets()

    def _on_items_changed(self, notifier, index, items):
        self._render_widgets(index, index+len(items)-1)

    def _on_items_reordered(self, notifier):
        self._render_all_widgets()

    def visible_range_start__set(self, visible_range_start):
        self._visible_range_start = visible_range_start
        self._layout_all_widgets()

    visible_range_start = property(List.visible_range_start__get,
                                   visible_range_start__set)

    def visible_range_size__set(self, visible_range_size):
        # disabled
        return

    visible_range_size = property(List.visible_range_size__get,
                                  visible_range_size__set)

    def selected_item_index__set(self, index):
        if len(self.model) == 0:
            return

        prev_selected = self._selected_item_index

        def positive_equivalent(index):
           index = index % len(self.model)
           if index < 0:
                index = len(self.model) - index
           return index

        def shortest_path(start, end):
            delta = end - start
            if abs(delta) > len(self.model)/2.0:
                if delta > 0:
                    delta -= len(self.model)
                else:
                    delta += len(self.model)
            return delta

        # new selected item clamped to [0, len(self.model)-1]
        self._selected_item_index = positive_equivalent(index)

        # saving the target of the animation and stopping it
        previous_target = self.animated.visible_range_start
        self.animated.stop_animations()

        # previous animation delta
        previous_delta = previous_target - self.visible_range_start
        # new animation delta
        new_delta = previous_delta + \
                    shortest_path(positive_equivalent(previous_target),
                                  self._selected_item_index)

        # reset the current visible_range_start with no visual change
        self.visible_range_start = positive_equivalent(self.visible_range_start)

        visible_range_start = self.visible_range_start + new_delta

        if self.animation_enabled:
            self.animated.visible_range_start = visible_range_start
        else:
            self.visible_range_start = visible_range_start

        if prev_selected != self._selected_item_index:
            item = self.model[self._selected_item_index]
            prev_item = self.model[prev_selected]
            self.emit('selected-item-changed', item, prev_item)

    selected_item_index = property(List.selected_item_index__get,
                             selected_item_index__set)

    def _layout_all_widgets(self):
        if not self.visible:
            return

        for i, widget in enumerate(self._widgets):
            position = -self._visible_range_start + i
            self._layout_widget(widget, position)

    def _prepare_widget(self, widget):
        widget.size = (self._item_width, self._item_height)

    def _layout_widget(self, widget, position):
        # angle from position
        offset = math.pi/2.0
        angle = position/len(self.model)*math.pi*2.0 + offset

        # common factors
        factor_x = (1.0-math.cos(angle))/2.0
        factor_y = (1.0+math.sin(angle))/2.0

        # compute widget properties given angle
        zoom = self.compute_zoom(factor_y)
        width = self._item_width
        height = self._item_height

        zoomed_width = width*zoom
        zoomed_height = height*zoom

        x = self.compute_x(factor_x)-(zoomed_width-width)/2.0
        y = self.compute_y(factor_y)-(zoomed_height-height)/2.0
        z = self.compute_z(factor_y)
        opacity = self.compute_opacity(factor_y)

        # update widget properties
        widget.opacity = opacity
        widget.size = (zoomed_width, zoomed_height)
        widget.position = (x, y, z)

    def _render_widgets(self, start, end):
        if self._renderer is None:
            return

        for i, widget in enumerate(self._widgets[start:end+1]):
            item = self.model[i+start]
            self._renderer(item, widget)
            widget.visible = True

    def _item_index_from_widget_index(self, widget_index):
        item_index = widget_index
        return item_index

    def _widget_index_from_item_index(self, item_index):
        widget_index = item_index
        return widget_index

    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

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

    # Signals support methods

    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
        return True

    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)
        try:
            item = self.model[item_index]
        except IndexError:
            return True
        self.emit('item-activated', 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


    # Inertia support methods
    # FIXME: they should be part of the animation framework

    def _range_start_to_selected(self):
        selected = self.visible_range_start
        selected = int(round(selected))
        return selected

    deceleration = 8.0
    def _decelerate(self):
        self._previous_time = self._current_time
        self._current_time = mod_time.time()
        delta = self._current_time - self._previous_time

        if self.speed > 0.0:
            self.speed -= self.deceleration*delta
            if self.speed > 0.0:
                self.visible_range_start -= self.speed*delta
                return True

        elif self.speed < 0.0:
            self.speed += self.deceleration*delta
            if self.speed < 0.0:
                self.visible_range_start -= self.speed*delta
                return True

        self.selected_item_index = self._range_start_to_selected()
        return False

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

        widget = cls(Text)
        widget.visible = True

        model = range(5)
        def renderer(item, widget):
            widget.label = str(item)
            widget.bg_a = 0
        widget.set_renderer(renderer)
        widget.set_model(model)

        widget.position = (0.5, 0.5, 0.0)
        widget.size = (3.0, 1.3)

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

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

        return widget


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

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