# -*- 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: Olivier Tilloy <olivier@fluendo.com>

"""
Custom button widgets.
"""

from elisa.plugins.pigment.widgets.widget import Widget
from elisa.plugins.pigment.widgets.theme import Theme
from elisa.plugins.pigment.widgets.box import HBox

from elisa.plugins.pigment.graph.image import Image
from elisa.plugins.pigment.graph.text import Text
from elisa.plugins.pigment.graph.group import NodeNotInGroup

import pgm

import logging


class TextButton(Widget):

    """
    A simple button widget with only a text label.
    """

    def __init__(self):
        super(TextButton, self).__init__()

        # Invisible background to receive mouse events
        self._background = Image()
        self.add(self._background)
        self._background.size = (1.0, 1.0)
        self._background.position = (0.0, 0.0, 0.0)
        self._background.bg_a = 0
        self._background.visible = True

        self.text = Text()
        self.add(self.text)
        self.text.visible = True

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

    def clean(self):
        self.text.clean()
        self.text = None
        self._background.clean()
        self._background = None
        return super(TextButton, self).clean()

    def label__get(self):
        return self.text.label

    def label__set(self, label):
        self.text.label = label

    label = property(label__get, label__set)


class StateButton(Widget):

    """
    A button widget with a background image and a text that has two states:
    when focused, the background image is different than when unfocused.
    Hovering over it with the mouse pointer will change its state.
    """

    def __init__(self, image_resource_unfocused, image_resource_focused, text=''):
        """
        Constructor.

        @param image_resource_unfocused: the image resource to display as
                                         background when unfocused
        @type image_resource_unfocused:  C{str}
        @param image_resource_focused:   the image resource to display as
                                         background when focused
        @type image_resource_focused:    C{str}
        @param text:                     the text label of the button
        @type text:                      C{str}
        """
        super(StateButton, self).__init__()

        theme = Theme.get_default()

        self.bg_unfocused = Image()
        self.add(self.bg_unfocused, forward_signals=False)
        self.bg_unfocused.layout = pgm.IMAGE_FILLED
        image_file = theme.get_resource(image_resource_unfocused)
        self.bg_unfocused.set_from_file(image_file)
        self.bg_unfocused.size = self.size
        self.bg_unfocused.position = (0.0, 0.0, 0.0)
        self._unfocused_entered_id = self.bg_unfocused.connect('entered',
                                                               self._hover_cb,
                                                               True)
        self._unfocused_left_id = self.bg_unfocused.connect('left',
                                                            self._hover_cb,
                                                            False)

        self.bg_focused = Image()
        self.add(self.bg_focused, forward_signals=False)
        self.bg_focused.layout = pgm.IMAGE_FILLED
        image_file = theme.get_resource(image_resource_focused)
        self.bg_focused.set_from_file(image_file)
        self.bg_focused.size = self.size
        self.bg_focused.position = (0.0, 0.0, 0.0)
        self._focused_entered_id = self.bg_focused.connect('entered',
                                                           self._hover_cb,
                                                           True)
        self._focused_left_id = self.bg_focused.connect('left',
                                                        self._hover_cb,
                                                        False)

        self.text = Text()
        self.add(self.text, forward_signals=False)
        self.text.ellipsize = pgm.TEXT_ELLIPSIZE_MIDDLE
        self.text.alignment = pgm.TEXT_ALIGN_CENTER
        self.text.bg_a = 0
        self.text.label = text
        self.text.size = (self.width, self.height * 0.6)
        self.text.position = (0.0, self.height * 0.2, 0.0)
        self.text.visible = True

        self.mouse_overlay = Image()
        self.add(self.mouse_overlay)
        self.mouse_overlay.size = self.size
        self.mouse_overlay.position = (0, 0, 1)
        self.mouse_overlay.bg_a = 0
        self.mouse_overlay.visible = True

        self.highlight(False)

    def clean(self):
        self.bg_unfocused.disconnect(self._unfocused_entered_id)
        self.bg_unfocused.disconnect(self._unfocused_left_id)
        self.bg_unfocused.clean()
        self.bg_unfocused = None

        self.bg_focused.disconnect(self._focused_entered_id)
        self.bg_focused.disconnect(self._focused_left_id)
        self.bg_focused.clean()
        self.bg_focused = None

        self.text.clean()
        self.text = None

        self.mouse_overlay.clean()
        self.mouse_overlay = None

        return super(StateButton, self).clean()

    def label__get(self):
        return self.text.label

    def label__set(self, value):
        self.text.label = value

    label = property(label__get, label__set)

    def highlight(self, highlight):
        """
        Change the background image depending on the focus.

        @param highlight: whether the button should appear highlighted
        @type highlight:  C{bool}
        """
        self.bg_unfocused.visible = not highlight
        self.bg_focused.visible = highlight

    def do_focus(self, focus):
        self.highlight(focus)

    def _hover_cb(self, button, x, y, z, time, hover):
        if not self.focus:
            self.highlight(hover)


class IconButton(Widget):

    """
    A button widget with an icon and an optional text.
    """

    def __init__(self):
        super(IconButton, self).__init__()

        # Invisible background to receive mouse events
        self._background = Image()
        self.add(self._background)
        self._background.size = (1.0, 1.0)
        self._background.position = (0.0, 0.0, 0.0)
        self._background.bg_a = 0
        self._background.visible = True

        self.container = HBox()
        self.add(self.container, forward_signals=False)
        self.container.visible = True

        self.icon = Image()
        self.container.pack_start(self.icon)
        self.icon.layout = pgm.IMAGE_SCALED
        self.icon.bg_a = 0
        self.icon.visible = True

        self.text = Text()
        self.text.alignment = pgm.TEXT_ALIGN_LEFT
        self.text.ellipsize = pgm.TEXT_ELLIPSIZE_END
        self.text.bg_a = 0
        self.text.visible = True

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

    def clean(self):
        try:
            self.container.remove(self.text)
        except NodeNotInGroup:
            pass
        self.container.remove(self.icon)
        self.text.clean()
        self.text = None
        self.icon.clean()
        self.icon = None
        self.container.clean()
        self.container = None
        self._background.clean()
        self._background = None
        return super(IconButton, self).clean()

    def update_style_properties(self, props=None):
        if props is None:
            return

        remaining_props = props.copy()
        custom_props = ('icon_only-width', 'icon_with_text-width')
        for prop in custom_props:
            try:
                remaining_props.pop(prop)
            except KeyError:
                continue

        if len(remaining_props) > 0:
            return super(IconButton, self).update_style_properties(remaining_props)

    def _layout_text(self):
        style = self.style.get_items()
        if len(self.label) == 0:
            try:
                self.container.remove(self.text)
            except NodeNotInGroup:
                pass
            self.icon.width = style['icon_only-width']
        else:
            if self.text not in self.container:
                self.container.pack_start(self.text)
            self.icon.width = style['icon_with_text-width']
            self.text.width = style['text-width']

    def label__get(self):
        return self.text.label

    def label__set(self, label):
        self.text.label = label
        self._layout_text()

    label = property(label__get, label__set)

    @classmethod
    def _demo_widget(cls, *args, **kwargs):
        theme = Theme.load_from_module('elisa.plugins.poblesec')

        widget = cls()

        resource = theme.get_resource('elisa.plugins.poblesec.directory_settings_add')
        widget.icon.set_from_file(resource)

        widget.label = 'Add'

        widget.size = (0.8, 0.4)
        widget.position = (0.1, 0.1, 0.0)
        widget.visible = True

        def _button_clicked_cb(button, x, y, z, mbutton, time, data):
            logging.debug('button clicked')
            if len(button.label) == 0:
                label = 'Add'
                resource = theme.get_resource('elisa.plugins.poblesec.directory_settings_add')
            else:
                label = ''
                resource = theme.get_resource('elisa.plugins.poblesec.directory_settings_added')
            button.label = label
            button.icon.set_from_file(resource)

        button_clicked_id = widget.connect('clicked', _button_clicked_cb)

        return widget


from elisa.plugins.pigment.widgets.const import STATE_NORMAL
from elisa.plugins.pigment.animation import implicit

class SmoothStatesWidget(Widget):
    """
    DOCME
    """

    def __init__(self, state_widgets):
        """
        DOCME
        """
        super(SmoothStatesWidget, self).__init__()

        self._transitions = {}
        self._state_widgets = state_widgets

        # fade in the appropriate widget
        widget = self._get_widget_for_state(STATE_NORMAL)
        self._current_widget = widget
        self.add(widget, forward_signals=False)

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

    def clean(self):
        self._state_widgets.clear()
        self._current_widget = None
        return super(SmoothStatesWidget, self).clean()


    def setup_transition(self, end_state, start_state=None,
                         mode=implicit.REPLACE, duration=200,
                         transformation=implicit.SMOOTH):
        """
        DOCME
        """
        key = (start_state, end_state)
        self._transitions[key] = (mode, {'duration': duration,
                                         'transformation': transformation})

    # Protected API
    def update_style_properties(self, props=None):
        if isinstance(self._current_widget, Widget):
            self._current_widget.update_style_properties(props)
            return

        # case when self._current_widget is not a widget
        # (elisa.plugins.pigment.widgets.widget.Widget) but any other node
        # (elisa.plugins.pigment.graph.node.Node)
        # in that case forward values to self._current_widget's attributes
        logstr = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)

        for key, value in props.iteritems():
            widget = self._current_widget
            attribute = key
            try:
                value = \
                    self._parse_style_value(value, widget, attribute, logstr)
            except AttributeError:
                # Invalid value, skip it
                continue

            setattr(widget, attribute, value)

    def set_name(self, name):
        if isinstance(self._current_widget, Widget):
            self._current_widget.set_name(name)
        super(SmoothStatesWidget, self).set_name(name)

    def do_state_changed(self, previous_state):
        have_new_widget = \
            self._perform_states_transition(previous_state, self.state)
        if have_new_widget:
            # no style property is applied on that widget yet, we reflect that
            # in _committed_style
            self._committed_style = {}
        super(SmoothStatesWidget, self).do_state_changed(previous_state) 

    def _get_widget_for_state(self, state):
        widget_cls = self._state_widgets[state]
        widget = widget_cls()
        if isinstance(widget, Widget):
            widget.set_name(self.name)
            widget.state = state
        widget.visible = True
        return widget

    def _perform_states_transition(self, start_state, end_state):
        """
        DOCME

        @return: True if a new widget has been created, False if we stick with
        the old one.
        """
        if start_state == end_state:
            return False

        try:
            new_state_widget = self._get_widget_for_state(end_state)
        except KeyError:
            # no widget defined for that state, go back to normal state
            end_state = STATE_NORMAL
            if start_state == end_state:
                return False
            new_state_widget = self._get_widget_for_state(end_state)

        previous_state_widget = self._current_widget
        self._current_widget = new_state_widget

        # fade in the appropriate widget 'new_state_widget'
        if new_state_widget not in self:
            new_state_widget.opacity = 0
            self.add(new_state_widget, forward_signals=False)

        self._setup_animation(new_state_widget.animated, start_state, end_state)
        new_state_widget.animated.opacity = 255
        new_state_widget.animated.passthrough = False

        # fade out the previously shown widget 'previous_state_widget'
        if previous_state_widget != None:
            def remove_new_state_widget(dummy):
                animated = previous_state_widget.animated
                animated.update_animation_settings(end_callback=None)
                self.remove(previous_state_widget)

            self._setup_animation(previous_state_widget.animated, start_state,
                                  end_state, remove_new_state_widget)
            previous_state_widget.animated.opacity = 0
            previous_state_widget.animated.passthrough = False

        return True

    def _setup_animation(self, animated, start_state, end_state,
                         end_callback=None):
        animated.update_animation_settings(end_callback=None)

        try:
            key = (start_state, end_state)
            mode, parameters = self._transitions[key]
        except KeyError:
            try:
                key = (None, end_state)
                mode, parameters = self._transitions[key]
            except KeyError:
                # instantaneous transition
                animated.stop_animations()
                animated.passthrough = True
                if end_callback != None:
                    end_callback(None)
                return

        animated.passthrough = False
        animated.mode = mode
        animated.setup_next_animations(end_callback=end_callback, **parameters)


from elisa.plugins.pigment.widgets.const import STATE_NORMAL, \
                                                STATE_PRESSED, \
                                                STATE_SELECTED, \
                                                STATE_LOADING

class RelookSmoothStatesWidget(SmoothStatesWidget):
    """
    DOCME
    """

    def __init__(self, state_widgets):
        SmoothStatesWidget.__init__(self, state_widgets=state_widgets)

        self.setup_transition(end_state=STATE_NORMAL,
                              mode=implicit.REPLACE,
                              duration=330,
                              transformation=implicit.DECELERATE)
        self.setup_transition(end_state=STATE_NORMAL,
                              start_state=STATE_SELECTED,
                              mode=implicit.APPEND,
                              duration=400,
                              transformation=implicit.DECELERATE)
        self.setup_transition(end_state=STATE_SELECTED,
                              mode=implicit.REPLACE,
                              duration=150,
                              transformation=implicit.ACCELERATE)
        self.setup_transition(end_state=STATE_LOADING,
                              mode=implicit.REPLACE,
                              duration=330,
                              transformation=implicit.DECELERATE)


from elisa.plugins.pigment.widgets.button import Button
from elisa.plugins.pigment.widgets.panel import PiecePanel

class RelookSmoothPanel(RelookSmoothStatesWidget):
    """
    DOCME
    """

    def __init__(self):
        state_widgets = {STATE_NORMAL: Image,
                         STATE_PRESSED: PiecePanel,
                         STATE_SELECTED: PiecePanel,
                         STATE_LOADING: Image}

        RelookSmoothStatesWidget.__init__(self, state_widgets=state_widgets)

    def do_state_changed(self, previous_state):
        if previous_state == STATE_LOADING:
            # stop the animation created (below) when going into STATE_LOADING
            animated = self._current_widget.animated
            animated.stop_animation_for_attribute('bg_color')

        super(RelookSmoothPanel, self).do_state_changed(previous_state)
        if self.state == STATE_LOADING:
            animated = self._current_widget.animated
            animated.setup_next_animations(duration=900,
                                           repeat_behavior=implicit.REVERSE,
                                           repeat_count=implicit.INFINITE,
                                           transformation=implicit.SMOOTH)
            animated.bg_color = (178, 193, 200, 191)
            # we need to put repeat_count back to default so that we don't
            # have to specify repeat_count for the other transitions
            animated.setup_next_animations(repeat_count=1)



class PanelButton(Button):
    """
    DOCME
    """

    def _create_widgets(self):
        super(PanelButton, self)._create_widgets()
        self.panel = RelookSmoothPanel()
        self.panel.visible = True
        self.add(self.panel, forward_signals=False)

    def do_state_changed(self, previous_state):
        self.panel.state = self.state
        super(PanelButton, self).do_state_changed(previous_state)

if __name__ == '__main__':
    import pgm
    from elisa.plugins.poblesec.widgets.button import PanelButton
    button = PanelButton.demo()
    try:
        __IPYTHON__
    except NameError:
        pgm.main()
