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

"""
Configuration wizard controller, meant to be executed upon first run of Elisa.
"""

from elisa.core.utils import defer
from elisa.core import common

from elisa.plugins.pigment.pigment_controller import PigmentController
from elisa.plugins.pigment.graph.image import Image

from elisa.plugins.poblesec.transitions import SoftFadeFrontIn, SoftFadeBackOut

import gobject


class ConfigurationWizard(PigmentController):

    """
    Configuration wizard controller.

    A configuration wizard is built from a list of option screens. It handles
    the navigation between those screens, associating each action of a given
    screen to another screen.
    Screens are identified by a name.
    Two special names are reserved: 'quit' and 'done'.
     - 'quit' quits the application
     - 'done' closes the configuration wizard

    The following is a simple example of how to instantiate a configuration
    wizard with two screens:

        welcome = (WelcomeScreen, {'next': 'final', 'quit': 'quit'})
        final = (FinalScreen, {'next': 'done', 'prev': 'welcome'})
        screens = {'welcome': welcome, 'final': final}
        wizard_dfr = ConfigurationWizard.create(None, screens=screens,
                                                start='welcome')
    """

    # The 'closed' signal is emitted when the configuration wizard is closed,
    # after all the steps have been successfully been through, or upon explicit
    # request to close it.
    __gsignals__ = {'closed': (gobject.SIGNAL_RUN_LAST,
                               gobject.TYPE_NONE, ()),
                   }

    def initialize(self, screens, start):
        """
        @param screens:    a dictionary associating screen names to the
                           corresponding classes and the action associations
        @type screens:     C{dict} of C{str}: (C{class},
                           C{dict} of C{str}: C{str})
        @param start:      the name of the first screen of the wizard
        @type start:       C{str}
        @raise ValueError: when screens is empty
        """
        if len(screens) == 0:
            msg = 'A configuration wizard needs at least one screen.'
            return defer.fail(ValueError(msg))

        dfr = super(ConfigurationWizard, self).initialize()

        self._background = Image()
        self.widget.add(self._background)
        self._background.size = (1.0, 1.0)
        self._background.position = (0.0, 0.0, 0.0)
        self._background.bg_color = (0, 0, 0, 220)
        self._background.visible = True

        self._mouse_event_handler_ids = []
        for event in ['double-clicked', 'drag-end', 'released', 'clicked',
                      'pressed', 'drag-begin', 'drag-motion', 'scrolled']:
            event_id = self._background.connect(event, self._mouse_event_cb)
            self._mouse_event_handler_ids.append(event_id)

        self._transition_in = SoftFadeFrontIn()
        self._transition_out = SoftFadeBackOut()

        self._screens = screens
        self._current_name = start
        self._current_screen = None
        self._handler_id = None

        self._showing_dfr = None
        self._hiding_dfr = None

        return dfr

    def clean(self):
        for event_id in self._mouse_event_handler_ids:
            self._background.disconnect(event_id)
        self._background.clean()
        self._background = None

    def _mouse_event_cb(self, *args):
        # Swallow the event
        return True

    def _connect_closed_signal(self, screen):
        self._handler_id = screen.connect('closed', self._closed_cb)
        return screen

    def _closed_cb(self, screen, action):
        # Invoked when one screen is to be closed
        self._current_screen.disconnect(self._handler_id)

        if action == 'quit':
            def delete_config_file(result):
                common.application.config.delete_file()
                return result

            dfr = common.application.stop()
            return dfr.addCallback(delete_config_file)

        elif action == 'done':
            self.emit('closed')
            return defer.succeed(self)

        actions = self._screens[self._current_name][1]
        try:
            new_screen_name = actions[action]
            return self._change_screen(new_screen_name)
        except KeyError:
            msg = "Screen '%s' has no association for action '%s'." % \
                  (self._current_name, action)
            raise KeyError(msg)

    def _create_screen(self, screen_name):
        def set_frontend(screen):
            screen.standalone = False
            screen.set_frontend(self.frontend)
            return screen

        try:
            dfr = self._screens[screen_name][0].create()
            dfr.addCallback(set_frontend)
            return dfr
        except KeyError:
            msg = "No screen associated to name '%s'." % screen_name
            return defer.fail(KeyError(msg))

    def _place_screen(self, screen):
        screen.widget.position = (0.0, 0.0, 0.0)
        screen.widget.size = (1.0, 1.0)

    def _show_screen(self, screen):
        self._current_screen = screen

        screen.widget.visible = False
        self.widget.add(screen.widget)
        self._place_screen(screen)
        screen.prepare()
        screen.widget.regenerate()
        self.widget.set_focus_proxy(screen.widget)

        def set_sensitive(screen):
            screen.sensitive = True

        self._showing_dfr = self._transition_in.apply(screen)
        self._showing_dfr.addCallback(self._connect_closed_signal)
        self._showing_dfr.addCallback(set_sensitive)
        return self._showing_dfr

    def _hide_screen(self, screen):
        def remove_screen(screen):
            self.widget.remove(screen.widget)
            screen.removed()
            return screen.clean()

        screen.sensitive = False
        self._hiding_dfr = self._transition_out.apply(screen)
        self._hiding_dfr.addCallback(remove_screen)
        return self._hiding_dfr

    def _change_screen(self, new_screen_name):
        def transition(new_screen, previous_screen):
            self._show_screen(new_screen)
            new_screen.widget.focus = True
            self._current_name = new_screen_name
            self._current_screen = new_screen
            self._hide_screen(previous_screen)
            return new_screen

        previous_screen = self._current_screen
        dfr = self._create_screen(new_screen_name)
        dfr.addCallback(transition, previous_screen)
        return dfr

    def start(self):
        """
        Create and show the first option screen.

        @return: a deferred fired when the screen is shown
        @rtype:  L{elisa.core.utils.defer.Deferred}
        """
        dfr = self._create_screen(self._current_name)
        dfr.addCallback(self._show_screen)
        dfr.addCallback(lambda x: self)
        return dfr

    def handle_input(self, manager, input_event):
        return self._current_screen.handle_input(manager, input_event)
