class LookupController(BaseController):
    """The central connection between look-up back-ends and the rest of Virtaal."""

    __gtype_name__ = 'LookupController'
    __gsignals__ = {
        'lookup-query': (gobject.SIGNAL_RUN_FIRST, None, (object,))
    }

    # INITIALIZERS #
    def __init__(self, plugin, config):
        GObjectWrapper.__init__(self)

        self.config = config
        self.main_controller = plugin.main_controller
        self.plugin = plugin

        self.disabled_model_names = ['baselookupmodel'] + self.config.get('disabled_models', [])
        self._signal_ids = {}
        self.view = LookupView(self)
        self._load_models()

    def _load_models(self):
        self.plugin_controller = PluginController(self)
        self.plugin_controller.PLUGIN_CLASSNAME = 'LookupModel'
        self.plugin_controller.PLUGIN_CLASS_INFO_ATTRIBS = ['display_name', 'description']
        new_dirs = []
        for dir in self.plugin_controller.PLUGIN_DIRS:
           new_dirs.append(os.path.join(dir, 'lookup', 'models'))
        self.plugin_controller.PLUGIN_DIRS = new_dirs

        self.plugin_controller.PLUGIN_INTERFACE = BaseLookupModel
        self.plugin_controller.PLUGIN_MODULES = ['virtaal_plugins.lookup.models', 'virtaal.plugins.lookup.models']
        self.plugin_controller.get_disabled_plugins = lambda *args: self.disabled_model_names

        self.plugin_controller.load_plugins()


    # METHODS #
    def destroy(self):
        self.view.destroy()
        self.plugin_controller.shutdown()
class TMController(BaseController):
    """The logic-filled glue between the TM view and -model."""

    __gtype_name__ = 'TMController'
    __gsignals__ = {
        'start-query': (gobject.SIGNAL_RUN_FIRST, None, (object,))
    }

    QUERY_DELAY = 300
    """The delay after a unit is selected (C{Cursor}'s "cursor-changed" event)
        before the TM is queried."""

    # INITIALIZERS #
    def __init__(self, main_controller, config={}):
        GObjectWrapper.__init__(self)

        self.config = config
        self.main_controller = main_controller
        self.disabled_model_names = ['basetmmodel'] + self.config.get('disabled_models', [])
        self.max_matches = self.config.get('max_matches', 5)
        self.min_quality = self.config.get('min_quality', 75)

        self._signal_ids = {}
        self.view = TMView(self, self.max_matches)
        self._load_models()

        self._connect_plugin()

    def _connect_plugin(self):
        self._store_loaded_id = self.main_controller.store_controller.connect('store-loaded', self._on_store_loaded)
        self._store_closed_id = self.main_controller.store_controller.connect('store-closed', self._on_store_closed)
        if self.main_controller.store_controller.get_store() is not None:
            self._on_store_loaded(self.main_controller.store_controller)
            self.view._should_show_tmwindow = True

        if self.main_controller.mode_controller is not None:
            self._mode_selected_id = self.main_controller.mode_controller.connect('mode-selected', self._on_mode_selected)

    def _load_models(self):
        self.plugin_controller = PluginController(self)
        self.plugin_controller.PLUGIN_CLASSNAME = 'TMModel'
        self.plugin_controller.PLUGIN_CLASS_INFO_ATTRIBS = ['display_name', 'description']
        new_dirs = []
        for dir in self.plugin_controller.PLUGIN_DIRS:
           new_dirs.append(os.path.join(dir, 'tm', 'models'))
        self.plugin_controller.PLUGIN_DIRS = new_dirs

        self.plugin_controller.PLUGIN_INTERFACE = BaseTMModel
        self.plugin_controller.PLUGIN_MODULES = ['virtaal_plugins.tm.models', 'virtaal.plugins.tm.models']
        self.plugin_controller.get_disabled_plugins = lambda *args: self.disabled_model_names

        self._model_signal_ids = {}
        def on_plugin_enabled(plugin_ctrlr, plugin):
            self._model_signal_ids[plugin] = plugin.connect('match-found', self.accept_response)
        def on_plugin_disabled(plugin_ctrlr, plugin):
            plugin.disconnect(self._model_signal_ids[plugin])
        self._signal_ids['plugin-enabled']  = self.plugin_controller.connect('plugin-enabled',  on_plugin_enabled)
        self._signal_ids['plugin-disabled'] = self.plugin_controller.connect('plugin-disabled', on_plugin_disabled)

        self.plugin_controller.load_plugins()


    # METHODS #
    def accept_response(self, tmmodel, query_str, matches):
        """Accept a query-response from the model.
            (This method is used as Model-Controller communications)"""
        query_str = forceunicode(query_str)
        if query_str != self.current_query or not matches:
            return
        # Perform some sanity checks on matches first
        for match in matches:
            if not isinstance(match.get('quality', 0), int):
                match['quality'] = int(match['quality'])
            if 'tmsource' not in match or match['tmsource'] is None:
                match['tmsource'] = tmmodel.display_name
            match['query_str'] = query_str

        anything_new = False
        for match in matches:
            curr_targets = [normalize(m['target']) for m in self.matches]
            if normalize(match['target']) not in curr_targets:
                # Let's insert at the end to prioritise existing matches over
                # new ones. We rely on the guarantee of sort stability. This
                # way an existing 100% will be above a new 100%.
                self.matches.append(match)
                anything_new = True
            else:
                norm_match_target = normalize(match['target'])
                prevmatch = [m for m in self.matches if normalize(m['target']) == norm_match_target][0]
                if 'quality' not in prevmatch or not prevmatch['quality']:
                    # Matches without quality are assumed to be less appropriate
                    # (ie. MT matches) than matches with an associated quality.
                    self.matches.remove(prevmatch)
                    self.matches.append(match)
                    anything_new = True
        if not anything_new:
            return
        self.matches.sort(key=lambda x: 'quality' in x and x['quality'] or 0, reverse=True)
        self.matches = self.matches[:self.max_matches]

        # Only call display_matches if necessary:
        if self.matches:
            self.view.display_matches(self.matches)

    def destroy(self):
        # Destroy TMView
        self.view.hide()
        self.view.destroy()

        # Disconnect signals
        self.main_controller.store_controller.disconnect(self._store_loaded_id)
        if getattr(self, '_cursor_changed_id', None):
            self.main_controller.store_controller.cursor.disconnect(self._cursor_changed_id)
        if getattr(self, '_mode_selected_id', None):
            self.main_controller.mode_controller.disconnect(self._mode_selected_id)
        if getattr(self, '_target_focused_id', None):
            self.main_controller.unit_controller.view.disconnect(self._target_focused_id)

        self.plugin_controller.shutdown()

    def select_match(self, match_data):
        """Handle a match-selection event.
            (This method is used as View-Controller communications)"""
        unit_controller = self.main_controller.unit_controller
        target_n = unit_controller.view.focused_target_n
        old_text = unit_controller.view.get_target_n(target_n)
        textbox =  unit_controller.view.targets[target_n]
        self.main_controller.undo_controller.push_current_text(textbox)
        unit_controller.set_unit_target(target_n, forceunicode(match_data['target']))

    def send_tm_query(self, unit=None):
        """Send a new query to the TM engine.
            (This method is used as Controller-Model communications)"""
        if unit is not None:
            self.unit = unit

        self.current_query = self.unit.source
        self.matches = []
        self.view.clear()
        self.emit('start-query', self.unit)

    def start_query(self):
        """Start a TM query after C{self.QUERY_DELAY} milliseconds."""
        if not hasattr(self, 'storecursor'):
            return False

        if not hasattr(self, 'unit'):
            self.unit = self.storecursor.deref()

        self.unit_view = self.main_controller.unit_controller.view
        if getattr(self, '_target_focused_id', None) and getattr(self, 'unit_view', None):
            self.unit_view.disconnect(self._target_focused_id)
        self._target_focused_id = self.unit_view.connect('target-focused', self._on_target_focused)
        self.view.hide()

        def start_query():
            self.send_tm_query()
            return False
        if getattr(self, '_delay_id', 0):
            gobject.source_remove(self._delay_id)
        self._delay_id = gobject.timeout_add(self.QUERY_DELAY, start_query)


    # EVENT HANDLERS #
    def _on_cursor_changed(self, cursor):
        self.storecursor = cursor
        self.unit = cursor.deref()

        if self.unit is None:
            return

        if self.view.active and self.unit.istranslated():
            self.view.mnu_suggestions.set_active(False)
        elif not self.view.active and not self.unit.istranslated():
            self.view.mnu_suggestions.set_active(True)

        return self.start_query()

    def _on_mode_selected(self, modecontroller, mode):
        self.view.update_geometry()

    def _on_store_closed(self, storecontroller):
        if hasattr(self, '_cursor_changed_id') and self.storecursor:
            self.storecursor.disconnect(self._cursor_changed_id)
        self.storecursor = None
        self._cursor_changed_id = 0
        self.view.hide()

    def _on_store_loaded(self, storecontroller):
        """Disconnect from the previous store's cursor and connect to the new one."""
        if getattr(self, '_cursor_changed_id', None) and self.storecursor:
            self.storecursor.disconnect(self._cursor_changed_id)
        self.storecursor = storecontroller.cursor
        self._cursor_changed_id = self.storecursor.connect('cursor-changed', self._on_cursor_changed)

        def handle_first_unit():
            self._on_cursor_changed(self.storecursor)
            return False
        gobject.idle_add(handle_first_unit)

    def _on_target_focused(self, unitcontroller, target_n):
        #logging.debug('target_n: %d' % (target_n))
        self.view.update_geometry()
class TerminologyController(BaseController):
    """The logic-filled glue between the terminology view and -model."""

    __gtype_name__ = 'TerminologyController'
    __gsignals__ = {
        'start-query': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING,))
    }

    # INITIALIZERS #
    def __init__(self, main_controller, config={}):
        GObjectWrapper.__init__(self)

        self.config = config
        self.main_controller = main_controller
        self.placeables_controller = main_controller.placeables_controller

        self.disabled_model_names = ['basetermmodel'] + self.config.get('disabled_models', [])
        self.placeables_controller.add_parsers(*terminology.parsers)
        self.placeables_controller.non_target_placeables.append(terminology.TerminologyPlaceable)
        self.placeables_controller.connect('parsers-changed', self._on_placeables_changed)
        main_controller.view.main_window.connect('style-set', self._on_style_set)

        if not (terminology.TerminologyPlaceable, TerminologyGUIInfo) in placeablesguiinfo.element_gui_map:
            placeablesguiinfo.element_gui_map.insert(0, (terminology.TerminologyPlaceable, TerminologyGUIInfo))

        self.view = TerminologyView(self)
        self._connect_signals()
        self._load_models()

    def _connect_signals(self):
        def lang_changed(ctrlr, lang):
            for src in self.main_controller.unit_controller.view.sources:
                src.elem.remove_type(terminology.TerminologyPlaceable)
                src.refresh()

        lang_controller = self.main_controller.lang_controller
        lang_controller.connect('source-lang-changed', lang_changed)
        lang_controller.connect('target-lang-changed', lang_changed)

    def _load_models(self):
        self.plugin_controller = PluginController(self)
        self.plugin_controller.PLUGIN_CLASSNAME = 'TerminologyModel'
        self.plugin_controller.PLUGIN_CLASS_INFO_ATTRIBS = ['description', 'display_name']
        new_dirs = []
        for dir in self.plugin_controller.PLUGIN_DIRS:
           new_dirs.append(os.path.join(dir, 'terminology', 'models'))
        self.plugin_controller.PLUGIN_DIRS = new_dirs

        self.plugin_controller.PLUGIN_INTERFACE = BaseTerminologyModel
        self.plugin_controller.PLUGIN_MODULES = ['virtaal_plugins.terminology.models', 'virtaal.plugins.terminology.models']
        self.plugin_controller.get_disabled_plugins = lambda *args: self.disabled_model_names
        self.plugin_controller.load_plugins()


    # METHODS #
    def destroy(self):
        self.view.destroy()
        self.plugin_controller.shutdown()
        self.placeables_controller.remove_parsers(terminology.parsers)


    # EVENT HANDLERS #
    def _on_placeables_changed(self, placeables_controller):
        for term_parser in terminology.parsers:
            if term_parser not in placeables_controller.parsers:
                placeables_controller.add_parsers(term_parser)

    def _on_style_set(self, widget, prev_style):
        TerminologyGUIInfo.update_style(widget)