# -*- 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.
#
# Author: Florian Boucault <florian@fluendo.com>

from elisa.plugins.pigment.widgets.widget import Widget
from elisa.plugins.pigment.widgets.box import HBox
from elisa.plugins.pigment.widgets.size_enforcer import Square
from elisa.plugins.pigment.widgets.button import Button
from elisa.plugins.pigment.graph.image import Image
from elisa.plugins.pigment.graph.text import Text
from elisa.plugins.pigment.animation import implicit

from elisa.plugins.poblesec.player_video import Player
from elisa.plugins.poblesec.base.list_switcher import ListSwitcherController
from elisa.plugins.poblesec.browser_controller import BrowserController
from elisa.plugins.poblesec.widgets.button import PanelButton
from elisa.core.input_event import EventValue

import gobject

from twisted.internet import task


class TopBarButton(PanelButton):

    def do_activated(self):
        self.activate()

    def activate(self):
        raise NotImplementedError()


class ButtonBar(HBox):
    pass

class Crumb(Button):

    def _create_widgets(self):
        super(Crumb, self)._create_widgets()

        # background
        self.background = Image()
        self.add(self.background)
        self.background.visible = True

        # separator
        self.separator = Text()
        self.add(self.separator)
        self.separator.opacity = 0
        self.separator.visible = True

        settings = {'duration': 500,
                    'transformation': implicit.DECELERATE}
        self.separator.animated.setup_next_animations(**settings)


        # setup crumb animation settings
        self.settings = {'duration': 500,
                         'transformation': implicit.DECELERATE}
        self.animated.setup_next_animations(**self.settings)


class BreadCrumbs(Widget):
    """
    DOCME
    """

    __gsignals__ = {'crumb-clicked': (gobject.SIGNAL_RUN_LAST,
                                      gobject.TYPE_BOOLEAN,
                                      (gobject.TYPE_PYOBJECT,))}

    def __init__(self):
        super(BreadCrumbs, self).__init__()
        self._crumbs = []

        # setup animation settings
        settings = {'duration': 200,
                    'transformation': implicit.DECELERATE}
        self.animated.setup_next_animations(**settings)

        self.update_style_properties(self.style.get_items())

    def push_crumb(self, controller):
        if hasattr(controller, 'display_name'):
            text = controller.display_name
        else:
            text = _('unknown')
        crumb = Crumb()
        crumb.text.label = text
        crumb.text.connect('clicked',
                            lambda *args: self.emit('crumb-clicked', crumb))
        self.add(crumb)
        # loose heuristic to compute the width of the text label based on the
        # number of characters in the text
        width = len(text)*0.039+0.165
        # make sure it never overflows the separator
        crumb.text.width = min(width, 1.0-crumb.separator.width)
        crumb.separator.x = crumb.text.width
        x = 0
        for previous_crumb in self._crumbs:
            x += (previous_crumb.separator.x + previous_crumb.separator.width) * \
                                                            previous_crumb.width
        crumb.x = x
        crumb.visible = True

        # fade in the last crumb's separator
        try:
            last_crumb = self._crumbs[-1]
            last_crumb.separator.animated.opacity = 255
        except IndexError:
            pass

        # fade in the crumb
        crumb.opacity = 0
        crumb.animated.opacity = 255

        # store the crumb
        self._crumbs.append(crumb)

    def pop_crumb(self):
        if len(self._crumbs) == 0:
            return

        crumb = self._crumbs.pop()

        def _remove_crumb(dummy):
            crumb.animated.update_animation_settings(end_callback=None)
            self.remove(crumb)

        crumb.animated.setup_next_animations(end_callback=_remove_crumb,
                                             **crumb.settings)
        crumb.animated.opacity = 0

        # fade out the last crumb's separator
        try:
            last_crumb = self._crumbs[-1]
            last_crumb.separator.animated.opacity = 0
        except IndexError:
            pass

class TopBar(Widget):
    """
    DOCME
    """

    def __init__(self):
        super(TopBar, self).__init__()
        self._create_widgets()
        self.update_style_properties(self.style.get_items())

    def _create_widgets(self):
        # buttons bar
        self.button_bar = ButtonBar()
        self.add(self.button_bar)
        self.button_bar.visible = True
        self.button_bar.navigable = True
        self.set_focus_proxy(self.button_bar)

        # section title
        self.title = Button()
        self.title.set_name("topbar_title")
        self.add(self.title)
        self.title.visible = True

        # bread crumbs
        self.bread_crumbs = BreadCrumbs()
        self.add(self.bread_crumbs)
        self.bread_crumbs.visible = True

        # setup animation settings
        settings = {'duration': 500,
                    'transformation': implicit.DECELERATE}
        self.animated.setup_next_animations(**settings)
        self.button_bar.animated.setup_next_animations(**settings)


class BottomBar(Widget):
    """
    DOCME

    @ivar logo: image visually located at the right end of the bread crumbs
    @type logo: L{elisa.plugins.pigment.graph.image.Image}
    """

    def __init__(self):
        super(BottomBar, self).__init__()
        self._create_widgets()
        self.update_style_properties(self.style.get_items())

    def _create_widgets(self):
        # placeholder for logo, optionally used by plugins
        self.logo = Image()
        self.add(self.logo)
        self.logo.visible = True

        settings = {'duration': 500,
                    'transformation': implicit.DECELERATE}
        self.logo.animated.setup_next_animations(**settings)


class PlayButton(TopBarButton):

    def __init__(self, main_controller):
        super(PlayButton, self).__init__()
        self._main_controller = main_controller

    def activate(self):
        player = self._main_controller.current_player.player
        if len(player.playlist) > 0:
            # switch only to the player if there is something to show/do
            self._main_controller.show_current_player()
            # if the player is an audio/video player, start playback
            if isinstance(player, Player):
                player.play()


class StopButton(TopBarButton):

    def __init__(self, main_controller):
        super(StopButton, self).__init__()
        self._main_controller = main_controller

    def activate(self):
        self._main_controller.stop_all_players()


class ModeSwitchingButton(TopBarButton):

    mode_index = -1

    def __init__(self, browser_controller):
        super(ModeSwitchingButton, self).__init__()
        self._browser_controller = browser_controller

    def activate(self):
        current = self._browser_controller.history.current
        if isinstance(current, ListSwitcherController):
            current.switch_mode(current.modes[self.mode_index])
            # FIXME: focus stealing: the focus should be set to current only if
            # self._browser_controller.widget.focus == True
            current.widget.focus = True

class VerticalListButton(ModeSwitchingButton):
    mode_index = 0

class CoverflowButton(ModeSwitchingButton):
    mode_index = 1

class GridButton(ModeSwitchingButton):
    mode_index = 2

class PoblesecBrowserController(BrowserController):

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

        # create the top bar including title, bread crumbs, playback buttons
        # and visualisation mode switching
        self.topbar = TopBar()
        self.widget.add(self.topbar)
        self._hide_topbar()
        self.topbar.visible = True

        self.topbar.bread_crumbs.connect('crumb-clicked', self._crumb_clicked)
        self.topbar.title.connect('clicked', self._go_back_to_first_controller)

        self.history.connect('push_controller', self._push_controller_signal)
        self.history.connect('pop_controller', self._pop_controller_signal)

        main_controller = self.frontend.retrieve_controllers('/poblesec')[0]

        # gridswitcher button
        button = GridButton(self)
        button.visible = True
        square = Square()
        square.visible = True
        square.add(button)
        self.topbar.button_bar.pack_end(square)

        # coverflow switcher button
        button = CoverflowButton(self)
        button.visible = True
        # FIXME: coverflow is deactivated until it's reimplemented updated with
        # the relook style
        #self.topbar.button_bar.pack_end(button)

        # vertical list switcher button
        button = VerticalListButton(self)
        button.visible = True
        square = Square()
        square.visible = True
        square.add(button)
        self.topbar.button_bar.pack_end(square)

        # empty space
        spacer = Square()
        spacer.visible = True
        self.topbar.button_bar.pack_end(spacer)

        # stop button
        button = StopButton(main_controller)
        button.visible = True
        square = Square()
        square.visible = True
        square.add(button)
        self.topbar.button_bar.pack_end(square)

        # play button
        button = PlayButton(main_controller)
        button.visible = True
        square = Square()
        square.visible = True
        square.add(button)
        self.topbar.button_bar.pack_end(square)

        # empty space
        spacer = Square()
        spacer.visible = True
        self.topbar.button_bar.pack_end(spacer)


        # create the bottom bar including section logo
        self.bottombar = BottomBar()
        self.widget.add(self.bottombar)
        self.bottombar.visible = True

    def _crumb_clicked(self, bread_crumbs, crumb):
        while bread_crumbs._crumbs[-1] != crumb:
            self.history.go_back()

    def _go_back_to_first_controller(self, *args):
        while self.history.index > 0:
            self.history.go_back()

    def set_source_logo(self, source_logo):
        """
        Load L{source_logo} into the placeholder for displaying the source of
        the media.
        If source_logo is None, unload previously loaded resource.

        @param source_logo: resource pointing to the logo
        @type source_logo:  str
        """
        # save the source logo into 'crumbs_logo' instance variable of the
        # current controller so that it will be reused when the controller is
        # displayed again
        self.history.current.crumbs_logo = source_logo
        self._load_logo_in_bottombar(source_logo)

    def _load_logo_in_bottombar(self, logo):
        image = self.bottombar.logo
        if logo != None:
            self.frontend.load_from_theme(logo, image)
            image.animated.opacity = 200
        else:
            image.animated.opacity = 0

    def _push_controller_signal(self, history, previous, current_dfr):
        current_dfr.addCallback(self._new_controller_pushed, previous)

    def _new_controller_pushed(self, current, previous):
        if self.history.index > 0:
            self._update_crumbs_logo(previous, current)
            self._show_topbar()
            self.topbar.bread_crumbs.push_crumb(current)

            self.widget.add_navigation_rule(current.widget,
                                            EventValue.KEY_GO_RIGHT,
                                            self.topbar)
            self.widget.add_navigation_rule(current.widget,
                                            EventValue.KEY_GO_UP,
                                            self.topbar)
            try:
                self.widget.remove_navigation_rule(self.topbar, EventValue.KEY_GO_LEFT)
                self.widget.remove_navigation_rule(self.topbar, EventValue.KEY_GO_DOWN)
            except KeyError:
                pass
            self.widget.add_navigation_rule(self.topbar,
                                            EventValue.KEY_GO_LEFT,
                                            current.widget)
            self.widget.add_navigation_rule(self.topbar,
                                            EventValue.KEY_GO_DOWN,
                                            current.widget)

        return current

    def _update_crumbs_logo(self, previous_controller, current_controller):
        # if the new controller defines a logo attribute it is loaded replacing
        # the currently displayed one
        current_logo = getattr(current_controller, "crumbs_logo", None)
        previous_logo = getattr(previous_controller, "crumbs_logo", None)

        if current_logo != previous_logo:
            self._load_logo_in_bottombar(current_logo)

    def _show_topbar(self):
        self.topbar.animated.opacity = 255

    def _hide_topbar(self):
        self.topbar.animated.opacity = 0

    def _pop_controller_signal(self, history, previous, current):
        if self.history.index < 1:
            self._hide_topbar()

        self.topbar.bread_crumbs.pop_crumb()
        self._update_crumbs_logo(previous, current)

        self.widget.remove_navigation_rule(previous.widget,
                                           EventValue.KEY_GO_RIGHT)
        self.widget.remove_navigation_rule(previous.widget,
                                           EventValue.KEY_GO_UP)
        self.widget.remove_navigation_rule(self.topbar, EventValue.KEY_GO_LEFT)
        self.widget.remove_navigation_rule(self.topbar, EventValue.KEY_GO_DOWN)
        self.widget.add_navigation_rule(self.topbar,
                                        EventValue.KEY_GO_LEFT,
                                        current.widget)
        self.widget.add_navigation_rule(self.topbar,
                                        EventValue.KEY_GO_DOWN,
                                        current.widget)

    def handle_input(self, manager, input_event):
        if self.history.current.has_focus() == True and \
                self.history.current.handle_input(manager, input_event) == True:
            return True

        # if we are not in the first controller, ie. the main menu
        if self.history.index > 0:
            if input_event.value == EventValue.KEY_MENU:
                self.history.go_back()
                return True

        return Widget._focused.handle_input(manager, input_event)

    def navigate(self, paths, force_reload_all=True):
        """
        Navigate through a list of controller paths, appending each of them
        in turn.

        The navigation is done in a clever way: if any of the ancestors of the
        current controller is in the sequence of paths, the navigation won't be
        performed further up.

        If the current path and the last of the sequence of paths have no common
        ancestor, the navigation will go via the main menu, where the correct
        entry will be selected.

        @param paths: a sequence of controller paths
        @type paths:  sequence of (path, display_name, *kwargs) tuple
                      (see the documentation of
                       L{elisa.plugins.poblesec.history.append_controller})

        @param force_reload_all: whether to force reloading all the controllers,
                                 ignoring all parents controllers already open
                                 and forcing passing through the main menu
                                 (C{True} by default)
        @type force_reload_all:  C{bool}

        @return: a deferred fired when the navigation is complete
        @rtype:  L{elisa.core.utils.defer.Deferred}
        """

        # Note: force_reload_all is True by default to overcome some limitations
        # of this navigation system. In most of the current use cases, reloading
        # one ore more controllers is needed, therefore we force going back
        # through the main menu.

        main_menu_path = '/poblesec/sections_menu'

        def go_back(ancestors):
            history = self.history
            go_back = (history.current.path != main_menu_path)
            while go_back:
                if history.current.path == main_menu_path:
                    go_back = False
                    break
                for path, display_name, kwargs in paths:
                    if not force_reload_all and history.current.path == path:
                        ancestors[0] = path
                        go_back = False
                        break
                if go_back:
                    yield history.go_back()

        # FIXME: this ties the generic browser controller to the specificities
        # of the main menu...
        def select_main_menu_item_if_needed(result, ancestors):
            if ancestors[0] is not None:
                # Not needed.
                return

            menu = self.frontend.retrieve_controllers(main_menu_path)[0]
            section_path = paths[0][0]
            entry_path = paths[1][0]

            def iterate_menu():
                for section_index, section in enumerate(menu.model):
                    if section.path == section_path:
                        menu.menu.selected_item_index = section_index
                        # FIXME: using a private method...
                        section_menu = \
                           menu.menu._widget_from_item_index(section_index).menu
                        for entry_index, entry in enumerate(section.model):
                            if entry.controller_path == entry_path:
                                section_menu.selected_item_index = entry_index
                                break
                            else:
                                yield None
                        break
                    else:
                        yield None

            return task.coiterate(iterate_menu())

        def set_topbar_title_if_needed(result, ancestors):
            if ancestors[0] is None:
                self.topbar.title.text.label = paths[0][1]

        def navigate(result, ancestors):
            def iterate_paths(ancestor):
                for path, display_name, kwargs in paths[1:]:
                    if ancestor is not None:
                        if ancestor == path:
                            ancestor = None
                        yield None
                    else:
                        yield self.history.append_controller(path, display_name,
                                                             **kwargs)

            return task.coiterate(iterate_paths(ancestors[0]))

        # Twisted use of a list to pass a mutable argument.
        ancestors = [None]
        dfr = task.coiterate(go_back(ancestors))
        dfr.addCallback(select_main_menu_item_if_needed, ancestors)
        dfr.addCallback(set_topbar_title_if_needed, ancestors)
        dfr.addCallback(navigate, ancestors)
        return dfr
