# -*- 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.plugins.pigment.pigment_controller import PigmentMainController
from elisa.plugins.pigment.pigment_frontend import ControllerNotFound
from elisa.plugins.pigment.graph.image import Image
from elisa.plugins.pigment.widgets.widget import Widget
from elisa.plugins.pigment.widgets.theme import Theme
from elisa.plugins.poblesec.transitions import Transition, FadeIn, FadeOut
from elisa.plugins.poblesec.configuration_wizard import ConfigurationWizard
from elisa.plugins.poblesec.widgets.swapping_image import SwappingImage
from elisa.plugins.poblesec import modal_popup

from elisa.core.media_uri import MediaUri
from elisa.plugins.gstreamer.amp_master import GstMetadataAmpClient

from elisa.plugins.base.models.media import PlayableModel
from elisa.plugins.base.models.image import ImageModel
from elisa.plugins.base.messages.device import NewDeviceDetected, \
                                               DeviceRemoved

from elisa.plugins.pigment.message import PigmentFrontendLoadedMessage

from elisa.core.input_event import *
from elisa.core.utils import defer
from elisa.core.default_config import CONFIG_DIR
from elisa.core.application import NewElisaVersionMessage
from elisa.core.utils.update_checker import AvailablePluginUpdatesMessage

from elisa.core import common
from elisa.core.utils import typefinding
from elisa.core.utils.i18n import install_translation

_, _n = install_translation('poblesec', True)

import platform
import os
import sys

from twisted.internet import task

import pgm


try:
    import dbus
    from elisa.plugins.poblesec.dbus_player import Player as DBusPlayer
except ImportError:
    dbus = None


class PartialFadeOut(Transition):
    def apply(self, controller):
        def cancel(dfr):
            self._stop(dfr, controller)
        dfr = defer.Deferred(canceller=cancel)

        def end_animation(timer):
            if not dfr.called:
                dfr.callback(controller)

        controller.widget.animated.update_animation_settings(duration=400,
                                                    end_callback=end_animation)
        controller.widget.animated.opacity = 150

        return dfr


class ContextualBackground(Widget):
    """
    DOCME
    """

    def __init__(self):
        super(ContextualBackground, self).__init__()
        # store the background previously loaded to avoid reloading
        self._loaded_resource = None

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

    def _create_widgets(self):
        # background content
        self.content = SwappingImage()
        self.content.visible = True
        self.add(self.content)

        # mask layered on top of the background content; typically a gradient
        self.mask = Image()
        self.mask.visible = True
        self.add(self.mask)

        theme = Theme.get_default()
        mask_file = theme.get_resource("elisa.plugins.poblesec.backgrounds.mask")
        # FIXME: scale pictures on the fly to be a maximum of 1024 pixels
        # wide and high: it should take into account the size of the viewport
        self.mask.set_from_file(mask_file, 1024)

    def load_resource(self, resource):
        """
        DOCME
        """
        theme = Theme.get_default()
        image_path = theme.get_resource(resource)
        self.load_file(image_path)

    def load_file(self, image_path):
        """
        DOCME
        """
        if image_path != self._loaded_resource:
            self._loaded_resource = image_path
            # FIXME: scale pictures on the fly to be a maximum of 1024 pixels
            # wide and high: it should take into account the size of the viewport
            self.content.set_from_file(image_path, 1024)


class PoblesecController(PigmentMainController):

    default_config = {'enable_remove': '0'}
    config_doc = {'enable_remove': 'Whether or not to allow the user to ' \
                  'remove/hide videos/tracks/photos'}

    players = {}

    def __init__(self):
        super(PoblesecController, self).__init__()
        self._overlay_in = FadeIn()
        self._overlay_out = FadeOut()
        self._background_in = FadeIn()
        self._background_out = PartialFadeOut()

        self.current_player = None

        self._popup_visible = False
        self._popups_queue = []
        self._popups_enabled = True

    def initialize(self):
        bus = common.application.bus
        bus.register(self._got_new_version, NewElisaVersionMessage)
        bus.register(self._frontend_loaded, PigmentFrontendLoadedMessage)
        bus.register(self._plugin_updates_available,
                     AvailablePluginUpdatesMessage)
        return super(PoblesecController, self).initialize()

    def prepare(self):
        self.browser.prepare()

    def _got_new_version(self, message, sender):
        if platform.system().lower() == 'windows' and message.installer_url:
            self._download_installer(message.installer_url, message.version)

    def _download_installer(self, url, version):
        uri = MediaUri(url)
        installer_path = os.path.join(CONFIG_DIR, uri.filename)

        # avoid downloading twice the same file
        if not os.path.exists(installer_path):

            def got_installer(model):
                fd = open(installer_path, 'wb')
                fd.write(model.data)
                fd.close()
                return self._show_installer_popup(installer_path, version)

            model, dfr = common.application.resource_manager.get(uri)
            dfr.addCallback(got_installer)
        else:
            self._show_installer_popup(installer_path, version)

    def _new_core_update_texts(self, version):
        title = _('MOOVIDA UPDATE AVAILABLE')
        subtitle = _('Update Moovida To Version %(version)s') % {'version': version}
        now = _('Update Now')
        later = _('Later')
        text = _("A new update to Moovida is available which brings with it many new "\
                 "bug fixes, general improvements and possible new features. To "\
                 "update Movida click on '%(now)s'. If you do not wish to update Moovida "\
                 "at this time click '%(later)s'. The next time you restart Moovida you "\
                 "will again be offered the chance to update again.") % {'now': now, 'later': later}
        return title, subtitle, text, now, later


    def _core_and_plugins_updates_texts(self, version, updates_number):
        title = _('UPDATES AVAILABLE')
        now = _('Update All')
        later = _('Later')
        subtitle = _n('Moovida & a New Plugin Update Available',
                      'Moovida & %(count)d New Plugin Updates Available',
                      updates_number) % {'count': updates_number}
        text = _n("There is one plugin update available",
                  "There are plugin updates available",
                  updates_number) % {'count': updates_number}

        text += _(" as well as a new version of Movida (version %(version)s). These updates "\
                  "bring many new bug fixes, general improvements and possible new "\
                  "features. To update both Moovida and Plugins, click on '%(now)s'. "\
                  "Alternatively click on '%(later)s' to close this message. You can "\
                  "perform your updates at a later date.") % {'version': version,
                                                              'now': now,
                                                              'later': later}

        return title, subtitle, text, now, later

    def _plugins_updates_texts(self, names):
        title = _('PLUGIN UPDATES AVAILABLE')
        later = _('Later')

        # This can't be done through _n() itself, because all msgstr[]
        # strings are compared to msgid_plural in terms of
        # placeholders, not msgid. Thus, any placeholder used in
        # msgstr[] needs to be defined in msgid_plural.
        if len(names) == 1:
            subtitle = _('Update Available for %(name)s Plugin') % \
                       {'name': names[0]}
        else:
            subtitle = _n('Update Available for a Plugin',
                          '%(count)d New Plugin Updates Available',
                          len(names)) % {'count': len(names)}
        now = _n('Update Now',
                 'Update All',
                 len(names)) % {'count': len(names)}
        text = _n("Moovida has detected that there is one available plugin "\
                  "update. To update your plugin click on '%(now)s'.",
                  "Moovida has detected that there are %(count)d available plugin "\
                  "updates. To update your plugins click on '%(now)s'.",
                  len(names)) % {'count': len(names), 'now': now}

        text += _(" Alternatively click on '%(later)s' to close this message. You can "\
                  "perform your updates at a later date.") % {'later': later}

        return title, subtitle, text, now, later

    def _new_plugins_texts(self, names):
        title = _('NEW PLUGINS AVAILABLE')
        later = _('Later')

        now = _n('Install',
                 'Install All',
                 len(names)) % {'count': len(names)}
        subtitle = _n('One New Recommended Plugin Available',
                      '%(count)d New Recommended Plugins Available',
                      len(names)) % {'count': len(names)}
        text = _n('Moovida has detected that there is one recommended plugin.',
                  'Moovida has detected that there are %(count)d recommended plugins.',
                  len(names)) % {'count': len(names)}
        rec = _n('plugin',
                 'plugins',
                 len(names))

        text += _(" From time to time Moovida will recommend some of the best new "\
                  "plugins as they are released. To install our recommended %(rec)s "\
                  "click on '%(now)s'. Alternatively click on '%(later)s' to close this "\
                  "message.\nYou can download and install recommended plugins "\
                  "at a later date via the 'Available Plugins' subsection within "\
                  "the 'Plugins' section of the Main Menu.") % {'rec': rec,
                                                                'now': now,
                                                                'later': later}
        return title, subtitle, text, now, later

    def _new_plugins_and_updates(self, new_names, updates_names):
        title = _('PLUGIN UPDATES AVAILABLE')
        now = _('Install All')
        later = _('Later')

        subtitle = _n("One New Recommended Plugin",
                      "%(count)d New Recommended Plugins",
                      len(new_names)) % {'count': len(new_names)}
        subtitle += _n(" & One Update Available",
                       " & %(count)d Updates Available",
                       len(updates_names)) % {'count': len(updates_names)}

        text = _n("Moovida has detected that there are both "\
                  "one recommended plugin",
                  "Moovida has detected that there are both "\
                  "%(count)d recommended plugins",
                  len(new_names)) % {'count': len(new_names)}
        text += _n(" and one update available.\n",
                   " and %(count)d updates available.\n",
                   len(updates_names)) % {'count': len(updates_names)}   
        text += _("From time to time Moovida will recommend some of "\
                  "the best new plugins as they are released.")
        text += _n(" There is also one update to previously installed "\
                   "plugins available to install.\nTo install both the plugin update",
                   " There are also %(count)d updates to previously installed "\
                   "plugins available to install.\nTo install both plugins updates",
                   len(updates_names)) % {'count': len(updates_names)}
        text += _n(" and the recommended plugin",
                   " and the recommended plugins",
                   len(new_names)) % {'count': len(new_names)}
        text += _(" click on '%(now)s'. Alternatively click on '%(later)s' "\
                  "to close this message.") % {'now': now,
                                               'later': later}

        return title, subtitle, text, now, later

    def _show_installer_popup(self, installer_path, version):
        title, subtitle, text, now, later = self._new_core_update_texts(version)

        def now_clicked():
            # execute installer in background and stop Moovida
            from win32api import ShellExecute
            import pywintypes

            try:
                ShellExecute(0, 'open', installer_path, '', None, 1)
            except pywintypes.error, error:
                self.warning(error)
                self.hide_popup()
            else:
                common.application.stop()

        buttons = [(now, now_clicked), (later, self.hide_popup)]
        self.enqueue_popup(title, subtitle, text, buttons)

    def _handle_win32_copydata(self, frontend):
        import platform

        if platform.system() == 'Windows':
            from win32con import WM_COPYDATA

            def on_copydata(viewport, event):
                import ctypes
                from ctypes import Structure, c_char, sizeof, addressof, memmove
                from ctypes.wintypes import ULONG, DWORD
                from elisa.core.utils.mswin32.structures import COPYDATASTRUCT

                copydata = COPYDATASTRUCT()
                memmove(addressof(copydata), event.lparam, sizeof(copydata))

                class COMMANDLINEDATA(Structure):
                    _fields_ = [("data", c_char * copydata.cbData),]

                commandlinedata = COMMANDLINEDATA()
                memmove(addressof(commandlinedata), copydata.lpData,
                        sizeof(commandlinedata))
                # We use '|' as a file separator
                files = commandlinedata.data.split('|')
                if len(files) > 0:
                    self.play_files(files)

                return True

            frontend.windows_msg_handler.add_wndproc(WM_COPYDATA, on_copydata)

    def _set_focused_controller(self, controller):
        self.focused_controller = controller
        self.widget.set_focus_proxy(controller.widget)
        if self.widget.focus:
            controller.widget.set_focus()

    def _frontend_loaded(self, message, sender):
        self._handle_win32_copydata(sender)
        if not common.application.first_run:
            return
        self.debug('First run, showing the configuration wizard')

        # While the wizard is visible, no popups can be shown
        #self.enable_popups(False)

        def start_wizard(wizard):
            self._wizard_close_handler_id = wizard.connect('closed',
                                                           self._close_wizard_cb)
            wizard.set_frontend(self.frontend)
            return wizard.start()

        def show_wizard(wizard):
            self.current_controller = self.focused_controller
            wizard.widget.visible = False
            self.widget.add(wizard.widget, layer=pgm.DRAWABLE_NEAR)
            wizard.widget.regenerate()
            self._background_in.apply(wizard)
            self._set_focused_controller(wizard)
            return wizard

        # This is a sample configuration wizard.
        #from elisa.plugins.poblesec.configuration.welcome import WelcomeScreen
        #from elisa.plugins.poblesec.configuration.finished import FinalScreen
        #welcome = (WelcomeScreen, {'next': 'final', 'quit': 'quit'})
        #final = (FinalScreen, {'next': 'done', 'prev': 'welcome'})
        #screens = {'welcome': welcome, 'final': final}
        #dfr = ConfigurationWizard.create(None, screens=screens,
        #                                 start='welcome')
        #dfr.addCallback(start_wizard)
        #dfr.addCallback(show_wizard)
        dfr = defer.succeed(self)

        return dfr

    def _close_wizard_cb(self, wizard):
        def faded(controller):
            self._set_focused_controller(self.current_controller)
            self.widget.remove(controller.widget)
            # Now popups are allowed to show up
            self.enable_popups(True)
            return controller.clean()

        return self._background_out.apply(wizard).addCallback(faded)

    def toplevel_window_shown(self):
        # Overridden from mother class.
        player = self.current_player
        if self.focused_controller == player:
            # The player is in the foreground, show the OSD
            # (see http://bugs.launchpad.net/elisa/+bug/276268).
            player.player_osd.show()
            player.player_osd.control_ribbon.show()
            player.player_osd.mouse_osd.show()

    def toplevel_window_hidden(self):
        # Overridden from mother class.
        pass # do nothing

    def _get_core(self, plugins):
        for plugin_dict in plugins:
            if plugin_dict['name'] == u'elisa':
                return plugin_dict
        return None

    def _get_plugin_name(self, plugin_dict):
        name = plugin_dict['name']
        if name.startswith('elisa-plugin'):
            name = name[13:].title()
        return name

    def show_restart_popup(self, result, updated):
        if len(updated) == 0:
            # Restart is needed only for plugin updates
            return result

        title = _('RESTART REQUIRED')
        subtitle = _('Please Restart Moovida')
        now = _('OK')
        later = _('Later')
        what = ''

        core = self._get_core(updated)
        if core:
            total_updates = len(updated) - 1
            # NOTE: This will be added to "Moovida has finished updating"
            what = _('to version %(version)s ') % {'version': core['version']}
            if total_updates > 0:
                # NOTE: This will be added to "Moovida has finished updating to version %(version)s"
                what += _n(' and has also updated one plugin',
                           ' and has also updated %(count)d plugins',
                           total_updates) % {'count': total_updates}
        else:
            # NOTE: This will be added to "Moovida has finished updating"
            what = _n('one plugin',
                      '%(count)d plugins',
                      len(updated)) % {'count': len(updated)}    

        text = _("Moovida has finished updating %(what)s.") % {'what': what}
        text += _n(" We require that you restart Moovida "\
                   "for this update to take effect.",
                   " We require that you restart Moovida "\
                   "for these updates to take effect.",
                   len(updated))

        if platform.system() == 'Windows' and hasattr(sys, 'frozen'):
            # Running an installed Elisa on Windows: we are able to restart
            # Elisa automatically.
            now = _('Restart')
            text += _(" To restart Moovida, click on '%(now)s'. You may close this "\
                      "message and restart Moovida at a later date by clicking "\
                      "on '%(later)s'. ") % {'now': now, 'later': later}
            from elisa.core.utils.mswin32 import tools
            buttons = [(now, lambda: tools.exit(1, True)),
                       (later, self.hide_popup)]
        else:
            buttons = [(now, self.hide_popup)]

        self.enqueue_popup(title, subtitle, text, buttons)
        return result

    def _plugin_updates_available(self, message, sender):
        # Plugin updates and/or new recommended plugins are available for
        # download from the plugin repository.

        def iterate_plugins(available_updates, new_recommended, updated):
            plugin_registry = common.application.plugin_registry

            def _plugin_updated(result, plugin_dict):
                self.debug('Updated %s to version %s.' % \
                           (plugin_dict['name'], plugin_dict['version']))
                updated.append(plugin_dict)
                return result

            def _update_failed(failure, plugin_dict):
                self.debug('Failed to update %s to version %s.' % \
                           (plugin_dict['name'], plugin_dict['version']))
                # Swallow the failure, to go on with the remaining updates.
                return None

            for plugin_dict in available_updates:
                dfr = plugin_registry.update_plugin(plugin_dict)
                dfr.addCallback(_plugin_updated, plugin_dict)
                dfr.addErrback(_update_failed, plugin_dict)
                yield dfr

            def _plugin_installed(result, plugin_dict):
                self.debug('Installed %s %s.' % \
                           (plugin_dict['name'], plugin_dict['version']))
                return result

            def _install_failed(failure, plugin_dict):
                self.debug('Failed to install %s %s.' % \
                           (plugin_dict['name'], plugin_dict['version']))
                # Swallow the failure, to go on with the remaining plugins.
                return None

            def _install_plugin(egg_file, plugin_dict):
                install_dfr = \
                    plugin_registry.install_plugin(egg_file,
                                                   plugin_dict['name'])
                install_dfr.addErrback(_install_failed)
                return install_dfr

            for plugin_dict in new_recommended:
                dfr = plugin_registry.download_plugin(plugin_dict)
                dfr.addCallback(_install_plugin, plugin_dict)
                dfr.addCallback(_plugin_installed, plugin_dict)
                yield dfr

        def process_plugins(result, updates, new, updated):
            return task.coiterate(iterate_plugins(updates, new, updated))

        def do_process_plugins(updates, new):
            if self._popup_visible:
                dfr = self.hide_popup()
            else:
                dfr = defer.succeed(None)
            updated = []
            dfr.addCallback(process_plugins, updates, new, updated)
            dfr.addCallback(self.show_restart_popup, updated)
            return dfr

        config = common.application.config
        silent_update = \
            config.get_option('auto_update_plugins', section='plugin_registry')
        silent_install = \
            config.get_option('auto_install_new_recommended_plugins',
                              section='plugin_registry')

        if silent_update and silent_install:
            # Do not ask the user's confirmation
            return do_process_plugins(message.available_updates,
                                      message.new_recommended)

        elif silent_update:
            if len(message.new_recommended) == 0:
                return do_process_plugins(message.available_updates, [])

            # Ask the user's confirmation to install new recommended plugins
            names = [self._get_plugin_name(p) for p in message.new_recommended]
            title, subtitle, text, now, later = self._new_plugins_texts(names)
            buttons = [(now,
                        lambda: do_process_plugins(message.available_updates,
                                                   message.new_recommended)),
                       (later,
                        lambda: do_process_plugins(message.available_updates,
                                                   []))]
            return self.enqueue_popup(title, subtitle, text, buttons)

        elif silent_install:
            if len(message.available_updates) == 0:
                return do_process_plugins([], message.new_recommended)

            # Ask the user's confirmation to update plugins
            core_plugin = self._get_core(message.available_updates)
            if len(message.available_updates) == 1:
                if core_plugin:
                    version = core_plugin['version']
                    texts = self._new_core_update_texts(version)
                else:
                    name = self._get_plugin_name(message.available_updates[0])
                    texts = self._plugins_updates_texts([name,])
            else:
                updates_number = len(message.available_updates)
                if core_plugin:
                    version = core_plugin['version']
                    texts = self._core_and_plugins_updates_texts(version,
                                                                 updates_number-1)
                else:
                    names = [self._get_plugin_name(p)
                             for p in message.available_updates]
                    texts = self._plugins_updates_texts(names)
            title, subtitle, text, now, later = texts
            buttons = [(now,
                        lambda: do_process_plugins(message.available_updates,
                                                   message.new_recommended)),
                       (later,
                        lambda: do_process_plugins([],
                                                   message.new_recommended))]
            return self.enqueue_popup(title, subtitle, text, buttons)

        if len(message.new_recommended) == 0:
            core_plugin = self._get_core(message.available_updates)
            if len(message.available_updates) == 1:
                if core_plugin:
                    version = core_plugin['version']
                    texts = self._new_core_update_texts(version)
                else:
                    name = self._get_plugin_name(message.available_updates[0])
                    texts = self._plugins_updates_texts([name,])
            else:
                updates_number = len(message.available_updates)
                if core_plugin:
                    version = core_plugin['version']
                    texts = self._core_and_plugins_updates_texts(version,
                                                                 updates_number-1)
                else:
                    names = [self._get_plugin_name(p)
                             for p in message.available_updates]
                    texts = self._plugins_updates_texts(names)
        elif len(message.available_updates) == 0:
            names = [self._get_plugin_name(p) for p in message.new_recommended]
            texts = self._new_plugins_texts(names)
        else:
            new_names = [self._get_plugin_name(p) for p in message.new_recommended]
            updates_names = [self._get_plugin_name(p)
                             for p in message.available_updates]
            texts = self._new_plugins_and_updates(new_names, updates_names)

        title, subtitle, text, now, later = texts
        buttons = [(now,
                    lambda: do_process_plugins(message.available_updates,
                                               message.new_recommended)),
                   (later, self.hide_popup)]
        self.enqueue_popup(title, subtitle, text, buttons)

    def add_player(self, name, path, init_dbus=False):
        if name in self.players:
            raise ValueError()

        def player_created(controller, name):
            setattr(self, name, controller)
            self.current_player = controller
            self.widget.add(self.current_player.widget, layer=pgm.DRAWABLE_FAR)
            self.current_player.widget.visible = True
            self._background_out.apply(self.current_player)
            self.players[name]= controller

        try:
            dfr = self.frontend.create_controller(path)
            dfr.addCallback(player_created, name=name)
            if init_dbus:
                dfr.addCallback(self._initialize_dbus)
            return dfr
        except ControllerNotFound:
            self.debug("Not loading %s because its " % path  +
                       "controller could not be found")
            setattr(self, name, None)

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

        deferreds = []
        
        core_players=[
            ('video_player', '/poblesec/video_player', False),
            ('dvd_player', '/poblesec/dvd_player', False),
            ('music_player', '/poblesec/music_player', True),
            ('slideshow_player','/poblesec/slideshow_player', False),
        ]

        def browser_created(controller):
            self.browser = controller
            self.widget.add(self.browser.widget, layer=pgm.DRAWABLE_NEAR)

            self._create_background(self.browser.widget)
            self._load_startup_background()

            if not self._popup_visible:
                self._set_focused_controller(controller)

            self._new_controller_pushed(controller.history.current, None)
            controller.history.connect('push_controller',
                                       self._on_push_controller)
            controller.history.connect('pop_controller',
                                       self._on_pop_controller)

        def setup_hotplug_detection(result):
            bus = common.application.bus
            bus.register(self._new_device_detected_cb, NewDeviceDetected)
            bus.register(self._device_removed_cb, DeviceRemoved)

        dfr = self.frontend.create_controller('/poblesec/browser',
                                              home_path=None)
        dfr.addCallback(browser_created)
        dfr.addCallback(setup_hotplug_detection)
        deferreds.append(dfr)

        for (name, path, init_dbus) in core_players:
            d = self.add_player(name, path, init_dbus)
            if d:
                deferreds.append(d)

        dfr = self._create_gst_metadata()
        deferreds.append(dfr)
        
        dfr_list = defer.DeferredList(deferreds)
        dfr_list.addCallback(self._process_command_line_files)
        return dfr_list

    def _on_pop_controller(self, history, previous, current):
        self._update_background(previous, current)

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

    def _new_controller_pushed(self, current, previous):
        self._update_background(previous, current)

    def _update_background(self, previous_controller, current_controller):
        # if the new controller defines a background attribute it is loaded
        # replacing the currently displayed one
        new_background_resource = getattr(current_controller, "background", None)

        if new_background_resource != None:
            self.background.load_resource(new_background_resource)

    def _create_background(self, parent_widget):
        self.background = ContextualBackground()
        parent_widget.add(self.background, layer=pgm.DRAWABLE_MIDDLE)
        self.background.visible = True

    def _load_startup_background(self):
        default_background = 'elisa.plugins.poblesec.backgrounds.main'
        self.background.load_resource(default_background)

    def _process_command_line_files(self, result):
        files = common.application.options['files']
        if len(files) > 0:
            self.play_files(files)

    def _new_device_detected_cb(self, message, sender):
        # A new device was hotplugged
        volume = message.model
        protocol = volume.protocol
        if protocol in ('file', 'ipod'):
            # That includes data CD/DVD
            self._connected_peripheral_popup(volume)
        elif protocol == 'cdda':
            self._connected_audiocd_popup(volume)

    def _connected_peripheral_popup(self, volume):
        title = _('PERIPHERAL DEVICE CONNECTED')
        subtitle = _("The Connected Device '%(name)s' Is Available") % {'name': volume.name}
        text = _("A peripheral device has been connected to this computer. " \
                 "You may browse this device and any file or folders it " \
                 "contains by selecting 'Browse'. Alternatively select " \
                 "'Cancel' to close this message.\n\n" \
                 "To access this device at a later date you can find it " \
                 "listed via the 'Attached Devices' subsection within the " \
                 "'Devices &amp; Shares' section of the Main Menu.")
        buttons = ((_('Cancel'), self.hide_popup),
                   (_('Browse'), lambda: self._browse_volume(volume)))
        self.enqueue_popup(title, subtitle, text, buttons)

    def _connected_audiocd_popup(self, volume):
        title = _('AUDIO CD INSERTED')
        subtitle = _("The CD Entitled '%(name)s' Is Available") % {'name': volume.name}
        text = _("A CD (Compact Disc) has been inserted into this computer. " \
                 "You may play this CD now by selecting 'Play'. To view and " \
                 "listen to the tracks available on this disc select " \
                 "'Browse'. Alternatively select 'Cancel' to close this " \
                 "message.\n\n" \
                 "To access this device at a later date you can find it " \
                 "listed via the 'Attached Devices' subsection within the " \
                 "'Devices &amp; Shares' section of the Main Menu.")
        buttons = ((_('Cancel'), self.hide_popup),
                   (_('Play'), lambda: None), # TODO: implement me!
                   (_('Browse'), lambda: self._browse_volume(volume)))
        self.enqueue_popup(title, subtitle, text, buttons)

    def _device_removed_cb(self, message, sender):
        # TODO: implement me!
        pass

    def _browse_volume(self, volume):
        def navigate(result):
            uri = MediaUri({'scheme': volume.protocol, 'host': '',
                            'path': volume.mount_point})
            browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
            paths = (('/poblesec/devices_and_shares_menu', _('Devices & Shares').upper(), {}),
                     ('/poblesec/devices', _('Attached Devices'), {}),
                     ('/poblesec/directory_settings', volume.name, {'uri': uri}))
            return browser.navigate(paths)

        def hide_player(result):
            if self.current_player is not None:
                self.hide_current_player()
            return result

        dfr = self.hide_popup()
        dfr.addCallback(navigate)
        dfr.addCallback(hide_player)
        return dfr

    def _create_gst_metadata(self):
        def gst_metadata_created(gst_metadata):
            self.gst_metadata = gst_metadata

        dfr = GstMetadataAmpClient.create()
        dfr.addCallback(gst_metadata_created)
        return dfr

    def _initialize_dbus(self, result=None):
        if dbus is None:
            # no dbus support
            return

        bus = dbus.SessionBus()
        self.bus_name = dbus.service.BusName('com.fluendo.Elisa', bus)

        self.dbus_player = DBusPlayer(self, bus,
                '/com/fluendo/Elisa/Plugins/Poblesec/AudioPlayer', self.bus_name)
        
        from elisa.plugins.poblesec.dbus_player import FilePlayer
        self.dbus_fileplayer = FilePlayer(self, bus,
                '/com/fluendo/Elisa/Plugins/Poblesec/FilePlayer', self.bus_name)

    def clean(self):
        self._clean_dbus()
        bus = common.application.bus
        bus.unregister(self._plugin_updates_available)
        bus.unregister(self._frontend_loaded)
        bus.unregister(self._got_new_version)

        def gst_metadata_cleaned(result):
            parent_dfr = super(PoblesecController, self).clean()
            return parent_dfr

        dfr = self.gst_metadata.clean()
        dfr.addCallback(gst_metadata_cleaned)
        return dfr

    def _clean_dbus(self):
        if dbus is None:
            # no dbus support
            return

        bus = dbus.SessionBus()
        self.dbus_player.remove_from_connection(bus,
                '/com/fluendo/Elisa/Plugins/Poblesec/AudioPlayer')
        self.dbus_fileplayer.remove_from_connection(bus,
                '/com/fluendo/Elisa/Plugins/Poblesec/FilePlayer')
        # BusName implements __del__, eew
        del self.bus_name

        # remove the reference cycle
        self.dbus_player = None
        self.dbus_fileplayer = None

    def _hide_players(self, leave=None):
        for p in self.players.keys():
            if self.players[p] == leave:
                continue
            if not self.players[p].is_compatible(leave):
                self.players[p].player.stop()
            FadeOut().apply(self.players[p])

    def show_player(self, name):
        if name not in self.players:
            return
        self._hide_players(self.players[name])
        self.current_player=self.players[name]
        self.show_current_player()

    def show_video_player(self):
        self.show_player('video_player')

    def show_dvd_player(self):
        self.show_player('dvd_player')

    def show_music_player(self):
        self.show_player('music_player')

    def show_slideshow_player(self):
        self.show_player('slideshow_player')

    def show_current_player(self):
        self._overlay_out.apply(self.browser)
        self._background_in.apply(self.current_player)
        self._set_focused_controller(self.current_player)

    def hide_current_player(self):
        self._overlay_in.apply(self.browser)
        self._background_out.apply(self.current_player)
        self._set_focused_controller(self.browser)

    def enable_popups(self, enabled):
        """
        Enable or disable popups. When popups are disabled, they will not show
        up but can still be enqueued, and will start being dequeued when they
        are enabled again.
        
        @warning: disabling popups will not hide the current popup if there is
                  one.

        @param enabled: whether popups should be enabled
        @type enabled:  C{bool}
        """
        self._popups_enabled = enabled
        if enabled and len(self._popups_queue) > 0:
            self._show_popup()

    def enqueue_popup(self, title, subtitle, text, buttons, 
                      popup_type=modal_popup.InformationPopup):
        """
        Enqueue a popup for later display.
        If the popup can be displayed right away, it will be.

        @param title:      the title of the popup
        @type title:       C{unicode}
        @param subtitle:   the second-level title of the popup
        @type subtitle:    C{unicode}
        @param text:       the text of the popup
        @type text:        C{unicode}
        @param buttons:    a list of buttons for the popup
        @type buttons:     C{list} of (C{str}, C{callable})
        @param popup_type: type of the popup widget
        @type popup_type:  C{elisa.plugins.poblesec.widgets.modal_popup.Popup}
        """
        
        if not issubclass(popup_type, modal_popup.Popup):
            raise TypeError('popup_type is not '
                'elisa.plugins.poblesec.widgets.modal_popup.Popup descendant') 

        self._popups_queue.append((title, subtitle, text, buttons, popup_type))
        if self._popups_enabled and not self._popup_visible:
            self._show_popup()

    def _show_popup(self):
        # Dequeue one popup and show it
        try:
            title, subtitle, text, buttons, popup_type = self._popups_queue.pop(0)
        except IndexError:
            return defer.fail(AssertionError('No popup in queue.'))

        def popup_created(controller):
            self._popup_visible = True
            self.current_controller = self.focused_controller
            self.popup_controller = controller
            controller.widget.visible = False
            self.widget.add(controller.widget, layer=pgm.DRAWABLE_NEAR)
            controller.widget.regenerate()
            self._background_in.apply(controller)
            self._set_focused_controller(controller)
            return controller

        dfr = self.frontend.create_controller('/poblesec/modal_popup',
                                              popup_cls=popup_type,
                                              title=title, subtitle=subtitle,
                                              text=text, buttons=buttons)
        dfr.addCallback(popup_created)
        return dfr

    def hide_popup(self):
        """
        Hide the current popup.

        @return: a deferred fired when the popup is hidden, or when the next
                 popup in the queue, if there is one, is shown
        @rtype:  L{elisa.core.utils.defer.Deferred}

        @raise AssertionError: if there is no popup currently visible
        """
        if not self._popup_visible:
            # No popup to hide.
            return defer.fail(AssertionError('No popup to hide.'))

        def faded(controller):
            self._set_focused_controller(self.current_controller)
            self.widget.remove(controller.widget)
            self._popup_visible = False
            return controller

        def show_next_popup(controller):
            if len(self._popups_queue) > 0:
                return self._show_popup()
            return controller

        hide_dfr = self._background_out.apply(self.popup_controller)
        hide_dfr.addCallback(faded)
        hide_dfr.addCallback(show_next_popup)
        return hide_dfr

    def start_current_player(self):
        if self.current_player == self.slideshow_player:
            # slideshow player
            try:
                self.slideshow_player.player.get_current_picture()
            except IndexError:
                # no content to be displayed
                return
            self.show_current_player()
            self.slideshow_player.player.start_slideshow()
        else:
            # all other players based on video player
            try:
                self.current_player.player.get_current_model()
            except IndexError:
                # no content to be displayed
                return
            self.show_current_player()
            self.current_player.player.play()

    def stop_all_players(self):
        self.slideshow_player.player.clear_playlist()
        self.music_player.player.stop()
        self.video_player.player.stop()
        self.dvd_player.player.stop()

    def handle_input(self, manager, input_event):
        if input_event.value == EventValue.KEY_OK and \
                input_event.modifier == EventModifier.ALT:
            self.frontend.toggle_fullscreen()
            return True
        
        if self.focused_controller.handle_input(manager, input_event):
            return True
        
        if input_event.value == EventValue.KEY_ESCAPE:
            self.frontend.toggle_fullscreen()
            return True
        elif input_event.value == EventValue.KEY_F11:
            self.frontend.toggle_fullscreen()
            return True
        elif input_event.value == EventValue.KEY_f:
            self.frontend.toggle_fullscreen()
            return True
        elif input_event.value == EventValue.KEY_MENU:
            if self.focused_controller == self.browser:
                # show the current player if it displaying anything interesting,
                # at least one picture for the slideshow player, one video for the
                # video player, one track for the music player
                if self.current_player == self.slideshow_player:
                    if self.slideshow_player.player.get_playlist_size() != 0:
                        self.show_current_player()
                else:
                    # all other players based on video player
                    if self.current_player.player.status != \
                                                self.current_player.player.STOPPED:
                        self.show_current_player()
            elif self.focused_controller == self.current_player:
                self.hide_current_player()
            elif self.focused_controller == self.popup_controller:
                # swallow the event
                pass
            return True
        elif input_event.value == EventValue.KEY_SPACE and self.current_player:
            if self.current_player.player.status != \
                                            self.current_player.player.STOPPED:
                    self.current_player.toggle_play_pause()
            return True

        return super(PoblesecController, self).handle_input(manager, input_event)
    
    def get_playable_model(self, file_path):
        file_uri = MediaUri("file://%s" % file_path)
        playable_model = PlayableModel()
        playable_model.uri = file_uri
        playable_model.title = os.path.basename(file_path)
        return playable_model
    
    def get_image_model(self, file_path):
        file_uri = MediaUri("file://%s" % file_path)
        image = ImageModel()
        image.references.append(file_uri)
        image.title = file_path
        return image
    
    def play_files(self, files):
        # Overridden from mother class.
        try:
            file_path = files[0]
        except IndexError:
            return
        try:
            file_uri = MediaUri("file://%s" % file_path)
            media_type = typefinding.typefind(file_uri)
        except typefinding.UnknownFileExtension, e:
            self.warning("Can't retrieve file type: %s" % file_path)
            return

        playable_model = PlayableModel()
        playable_model.uri = file_uri
        playable_model.title = os.path.basename(file_path)

        if "video" in media_type:
            self.video_player.player.play_model(playable_model)
            self.show_video_player()
            for file in files[1:]:
                playable_model = self.get_playable_model(file)
                self.video_player.player.enqueue_to_playlist(playable_model)
        elif "audio" in media_type:
            self.music_player.player.play_model(playable_model)
            self.show_music_player()
            for file in files[1:]:
                playable_model = self.get_playable_model(file)
                self.music_player.player.enqueue_to_playlist(playable_model)
        elif "image" in media_type:
            playlist = []
            for file_path in files:
                playlist.append(self.get_image_model(file_path))
            self.slideshow_player.player.set_playlist(playlist)
            self.show_slideshow_player()
        else:
            self.warning("Unknown media type: %s" % file_path)
