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

"""
Controller combining an input entry, an on-screen keyboard and a search results
widget to perform generic search queries.
"""

from twisted.internet import reactor

from elisa.core.utils.i18n import install_translation
from elisa.core.media_uri import MediaUri
from elisa.core import common
from elisa.core.utils import defer
from elisa.core.input_event import EventValue

from elisa.plugins.pigment.pigment_controller import PigmentController
from elisa.plugins.pigment.animation import implicit

from elisa.plugins.poblesec.widgets.search_results import SearchResultsWidget
from elisa.plugins.poblesec.widgets.keyboard import Keyboard, TextEntry


_ = install_translation('poblesec')


class SearcherEntry(object):
    """
    A searcher entry that should be used by external plugins that implement
    searchers to describe how they should be displayed.
    """

    def __init__(self, searcher, title, icon_resource):
        """
        Constructor.

        @param searcher:      the searcher's entry point
        @type searcher:       C{str}
        @param title:         the title to display for the search results
        @type title:          C{str}
        @param icon_resource: the icon to display for the search results
        @type icon_resource:  C{str}
        """
        self.searcher = searcher
        self.title = title
        self.icon_resource = icon_resource

class SearchController(PigmentController):
    """
    Controller combining an input entry and an on-screen keyboard to perform
    search queries.
    """

    live_search = True

    def initialize(self, mediatype):
        """
        Initialize the controller.

        @param mediatype: the type of media searched
        @type mediatype:  C{str}
        """
        self.mediatype = mediatype
        # The list of searchers is going to be populated by the decorators of
        # the plugins that provide them.
        self.searchers = []
        self.results = None
        self._live_search_call = None
        return super(SearchController, self).initialize()

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

        animation_settings = {'duration': 300,
                              'transformation': implicit.SMOOTH}

        osk_qwerty = 'elisa.plugins.poblesec.osk_qwerty'
        osk_123 = 'elisa.plugins.poblesec.osk_123'
        osk_symbols = 'elisa.plugins.poblesec.osk_symbols'
        layouts = [osk_qwerty, osk_123, osk_symbols]

        self.keyboard = Keyboard(layouts)
        self.keyboard.set_name("search_keyboard")
        self.keyboard.animated.setup_next_animations(**animation_settings)
        self.keyboard.title.foreground.label = _('Type your search query').upper()

        self._entry = TextEntry()
        self._entry.decorations.set_name('search')
        self._entry.visible = True
        self.keyboard.add_entry(self._entry)

        self.keyboard.visible = True
        self.widget.add(self.keyboard)
        self.widget.set_focus_proxy(self.keyboard)

        self._entry.connect('content-changed', self._on_content_changed)
        self.keyboard.connect('validated', self._on_entry_validated)


    def _add_results_widget(self):
        animation_settings = {'duration': 1000,
                              'transformation': implicit.SMOOTH}

        # FIXME: passing the frontend around to a widget is a hack; an action
        # could be passed instead executed when a result is clicked
        self.results = SearchResultsWidget(self.mediatype, self.frontend)
        self.results.set_name("search_results")
        self.results.opacity = 0
        self.results.animated.setup_next_animations(**animation_settings)
        self.results.title.foreground.label = _('Select a search category').upper()
        self.results.visible = True
        self.widget.add(self.results)
        self.widget.set_focus_proxy(self.results)

        self.widget.add_navigation_rule(self.results, EventValue.KEY_GO_DOWN, self.keyboard)
        self.widget.add_navigation_rule(self.keyboard, EventValue.KEY_GO_UP, self.results)

        # FIXME: set_searchers should be done ASAP that is when decorators
        # have finished their job; the following is a workaround
        self.results.set_searchers(self.searchers)

    def clean(self):
        self._cancel_scheduled_live_search()

        self._entry.disconnect_by_func(self._on_content_changed)
        self.keyboard.disconnect_by_func(self._on_entry_validated)
        self.keyboard = None

        self.searchers = []

        return super(SearchController, self).clean()

    def _search(self):
        if not self.results:
            self._add_results_widget()

        self._cancel_scheduled_live_search()
        search_string = self._entry.content.strip()
        dfrs = []

        def got_search_result(result_model, searcher_entry):
            self.results.set_results(searcher_entry, result_model)

        for searcher_entry in self.searchers:
            # TODO: Cancel the current request if there is one
            searcher = searcher_entry.searcher.lower()
            uri = MediaUri('elisa://search/%s/%s?only_searchers=%s' % \
                           (self.mediatype, search_string, searcher))
            model, dfr = common.application.resource_manager.get(uri)
            dfr.addCallback(got_search_result, searcher_entry)
            dfrs.append(dfr)

        return defer.DeferredList(dfrs)

    def _on_content_changed(self, entry):
        if self.live_search:
            self._schedule_live_search(entry.content)

    def _schedule_live_search(self, content):
        self._cancel_scheduled_live_search()
        if len(content.strip()) >= 3:
            self._live_search_call = reactor.callLater(1.0, self._search)

    def _cancel_scheduled_live_search(self):
        if (self._live_search_call is not None) and \
            not self._live_search_call.called and \
            not self._live_search_call.cancelled:
            self._live_search_call.cancel()

    def _on_entry_validated(self, keyboard):
        self._search()
        self.results.set_focus()
        return True
