# -*- 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.input_event import EventValue

import pgm
from elisa.plugins.pigment.widgets.list_horizontal import List
from elisa.plugins.pigment.widgets.const import *
from elisa.plugins.pigment import maths

import math
import time as mod_time

import logging


class GridHorizontal(List):

    def __init__(self, widget_class, rows=3, columns=5, widget_args=[], widget_kwargs={}):
        self.rows = rows
        self.columns = columns
        self._widget_args = widget_args 
        self._widget_kwargs = widget_kwargs 
        super(GridHorizontal, self).__init__(widget_class, visible_range_size=columns, widget_args=self._widget_args, widget_kwargs=self._widget_kwargs)

    def visible_range_start__set(self, visible_range_start):
        old_start = self._visible_range_start
        self._visible_range_start = visible_range_start
        delta = int(math.modf(visible_range_start)[1]-math.modf(old_start)[1])

        total_columns = self.columns
        if abs(delta) >= total_columns:
            self._render_widgets(0, len(self._widgets)-1)
        elif delta >= 1:
            self._move_widgets_from_beginning_to_end(delta * self.rows)
            self._render_widgets(len(self._widgets)-(delta * self.rows),
                                 len(self._widgets)-1)
        elif delta <= -1:
            self._move_widgets_from_end_to_beginning(-delta * self.rows)
            self._render_widgets(0, -delta * self.rows - 1)

        self._layout_all_widgets()

    visible_range_start = property(List.visible_range_start__get,
                                   visible_range_start__set)

    def _compute_relative_spacing(self, spacing):
        fx, fy = self.get_factors_relative(spacing.unit)
        return fx*spacing.value, fy*spacing.value

    def _prepare_all_widgets(self):
        self._relative_spacing = self._compute_relative_spacing(self._spacing)
        self.columns = self._visible_range_size
        # total amount of spacing needed
        total_spacing_x = self._relative_spacing[0]*(self.columns-1)
        total_spacing_y = self._relative_spacing[1]*(self.rows-1)

        self._item_width = (1.0-total_spacing_x)/self.columns
        self._item_height = (1.0-total_spacing_y)/self.rows

        for widget in self._widgets:
            widget.size = (self._item_width, self._item_height)

    def _create_widgets(self):
        nb_widgets = int((math.ceil(self._visible_range_size)+1) * self.rows)
        for i in xrange(nb_widgets):
            widget = self._widget_class(*self._widget_args, **self._widget_kwargs)
            widget.visible = True
            self._widgets.append(widget)
            self._connect_widget(widget)

    def _selected_to_range_start(self, selected):
        # FIXME: check me!
        start = self._animated.visible_range_start
        offset = 1
        threshold = 2

        total_columns = int(math.ceil(len(self.model) / self.rows))
        selected_column = int(selected / self.rows)
        size = math.floor(self._visible_range_size)

        if selected_column <= threshold-offset or \
           total_columns <= size-self.start_offset*2:
            visible_range_start = -offset
        elif selected_column >= total_columns-threshold+offset:
            visible_range_start = total_columns-size+offset
        else:
            # only moves the list if newly selected item is not visible
            if selected_column <= start+threshold:
                visible_range_start = selected_column-threshold
            elif selected_column >= start+size-threshold:
                visible_range_start = selected_column-size+1+threshold
            else:
                visible_range_start = start

        return visible_range_start

    def _range_start_to_selected(self, range_start):
        # FIXME: check me!
        half_size = (self.visible_range_size-1.0)/2.0
        selected = (range_start + half_size) * self.rows
        selected = int(round(selected))
        selected = maths.clamp(selected, 0, len(self.model)-1)
        return selected

    def _item_index_from_widget_index(self, widget_index):
        toto = int(self._visible_range_start) * self.rows
        item_index = widget_index + toto
        return item_index

    def _widget_index_from_item_index(self, item_index):
        toto = int(self._visible_range_start) * self.rows
        widget_index = item_index - toto
        return widget_index

    def _prepare_selector(self):
        if self._selector != None:
            self._selector.size = (self._item_width, self._item_height)
            self._selector.position = (0.0, 0.0, 0.0)

    def _layout_widget(self, widget, row, column):
        x = self.compute_x(row, column)
        y = self.compute_y(row, column)
        widget.opacity = self.compute_opacity(row, column)
        widget.position = (x, y, 0.0)

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

        # we assume that the widgets are created and loaded with their
        # corresponding data from the model
        delta, start = math.modf(self._visible_range_start)
        start *= self.rows

        i = 0
        for widget in self._widgets:
            # only layout the widget if it is meant to be visible: it renders
            # an item from the model
            item_index = i + start
            if i+start >= 0 and i+start < len(self.model):
                row = i % self.rows
                column = i/self.rows - delta
                self._layout_widget(widget, row, column)
                if not widget.parent:
                    self.add(widget, forward_signals=False)
                    if self.focus and item_index == self._selected_item_index:
                        widget.set_focus()
            else:
                if widget.parent:
                    self.remove(widget)
            i += 1

    def _layout_selector(self):
        if not self._selector:
            return

        visible_range_start = self._selected_to_range_start(self.selected_item_index)
        position = self.selected_item_index - int(visible_range_start) * self.rows
        row = int(position) % self.rows
        column = position/self.rows - math.modf(visible_range_start)[0]

        if self._selector.visible and self._selector.opacity != 0:
            selector = self._animated_selector
        else:
            selector = self._selector
            selector.z = -1.0

        selector.x = self.compute_x(row, column)
        selector.y = self.compute_y(row, column)

    def compute_x(self, row, column):
        return column*(self._item_width+self._relative_spacing[0])

    def compute_y(self, row, column):
        return row*(self._item_height+self._relative_spacing[1])

    # FIXME: the opacity of the last column should be full
    def compute_opacity(self, row, column):
        full_opacity = 255
        shaded_opacity = 150
        invisible_opacity = -80

        # make the transformation symmetrical
        if column <= self._visible_range_size/2.0:
            # beginning of the visible items case
            column = column
            start = self._visible_range_start
        else:
            # end of the visible items case
            column = self._visible_range_size - column - 1
            start = len(self.model) - self._visible_range_size -\
                    self._visible_range_start

        if start <= 0 or column >= 1 or self._visible_range_size <= 1.0:
            opacity = full_opacity
        elif column >= 0:
            opacity = maths.lerp(shaded_opacity, full_opacity, column)
        elif column < 0:
            if start >= 1:
                opacity = maths.lerp(invisible_opacity, shaded_opacity, column+1)
            else:
                opacity = maths.lerp(invisible_opacity, full_opacity, column+1)

        opacity = max(min(255, opacity), 0)
        return opacity

    def handle_input(self, manager, event):
        if event.value == EventValue.KEY_GO_LEFT:
            index = self.selected_item_index - self.rows
            if index >= 0:
                self.selected_item_index = index
                return True
        elif event.value == EventValue.KEY_GO_RIGHT:
            index = self.selected_item_index + self.rows
            if index < len(self.model):
                self.selected_item_index = index
                return True
        elif event.value == EventValue.KEY_GO_UP:
            if self.selected_item_index % self.rows == 0:
                return super(GridHorizontal, self).handle_input(manager, event)
            self.selected_item_index -= 1
            return True
        elif event.value == EventValue.KEY_GO_DOWN:
            if (self.selected_item_index % self.rows == (self.rows - 1)) or \
                (self.selected_item_index == (len(self.model) - 1)):
                return super(GridHorizontal, self).handle_input(manager, event)
            self.selected_item_index += 1
            return True
        elif event.value == EventValue.KEY_PAGE_UP:
            index = self.selected_item_index - \
                    int(self.visible_range_size) * self.rows
            index = max(index, self.selected_item_index % self.rows)
            if index != self.selected_item_index:
                self.selected_item_index = index
                return True
        elif event.value == EventValue.KEY_PAGE_DOWN:
            index = self.selected_item_index + \
                    int(self.visible_range_size) * self.rows
            mod = self.selected_item_index % self.rows
            if mod == ((len(self.model) - 1) % self.rows):
                index = min(index, len(self.model) - 1)
            else:
                index = min(index, len(self.model) - 1 - (self.rows - mod))
            if index != self.selected_item_index:
                self.selected_item_index = index
                return True

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

    # Signals support methods

    def do_scrolled(self, x, y, z, direction, time):
        if direction == pgm.SCROLL_UP:
            index = self.selected_item_index - self.rows
            if index >= 0:
                self.selected_item_index = index
        else:
            index = self.selected_item_index + self.rows
            if index < len(self.model):
                self.selected_item_index = index
        return True

    def _visible_selected_from_position(self, x, y):
        relative_x = (x-self.absolute_x)/self.absolute_width
        visible_selected_x = int(relative_x*self.columns)
        relative_y = (y-self.absolute_y)/self.absolute_height
        visible_selected_y = int(relative_y*self.rows)

        selected_item_index = (visible_selected_x * self.rows) + visible_selected_y

        return selected_item_index

    def do_drag_motion(self, x, y, z, button, time, pressure):
        if not self._dragging:
            return True

        time_since_last = time - self._last_drag_motion
        if time_since_last > self.drag_motion_resolution:
            self._last_drag_motion = time
        else:
            return True

        absolute_item_width = self._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._initial = (x, y, time)
        self._drag_accum += abs(motion)

        return True

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

    grid = GridHorizontal.demo()
    grid.size = (300.0, 220.0)

    def renderer(item, widget):
        widget.label = str(item)
        widget.alignment = pgm.TEXT_ALIGN_CENTER
        widget.bg_a = 0
    grid.set_renderer(renderer)

    try:
        __IPYTHON__
    except NameError:
        pgm.main()
