#!/usr/local/bin/python3
# Copyright 2020 gi-lom
# Copyright 2020 Nikita Kravets
# Copyright 2020-2022 Rafael Mardojai CM
# Copyright 2021-2022 Mufeed Ali
# SPDX-License-Identifier: GPL-3.0-or-later

import logging
import locale
import gettext

import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop

import gi

gi.require_version('Soup', '3.0')
from gi.repository import GLib

from dialect.session import Session
from dialect.settings import Settings
from dialect.languages import get_lang_name
from dialect.providers import TRANSLATORS
from dialect.providers.base import ApiKeyRequired, InvalidApiKey

SEARCH_BUS_NAME = 'org.gnome.Shell.SearchProvider2'
SBN = dict(dbus_interface=SEARCH_BUS_NAME)

CLIPBOARD_PREFIX = 'copy-to-clipboard'
ERROR_PREFIX = 'translation-error'

localedir = '/usr/local/share/locale'
langs_trans = gettext.translation('dialect-cldr-langs', localedir, fallback=True)
ui_trans = gettext.translation('dialect', localedir, fallback=True)
ui_trans.add_fallback(langs_trans)
ui_trans.install(names=['gettext'])

locale.bindtextdomain('dialect', localedir)
locale.textdomain('dialect')


class TranslateService(dbus.service.Object):
    bus_name = 'app.drey.Dialect.SearchProvider'
    _object_path = '/app/drey/Dialect/SearchProvider'

    def __init__(self):
        # init dbus
        self.session_bus = dbus.SessionBus()
        bus_name = dbus.service.BusName(self.bus_name, bus=self.session_bus)
        dbus.service.Object.__init__(self, bus_name, self._object_path)

        self.loaded = False
        self.load_failed = False

        # Live translation enabled
        self.live_enabled = self.is_live_enabled()

        # Translations store
        self.translations = {}
        self.src_language = 'auto'
        self.dest_language = None

        # Translator
        self._load_translator()
        Settings.get().connect('changed', self._on_settings_changed)
        Settings.get().connect('translator-changed', self._on_translator_changed)

    @dbus.service.method(in_signature='as', out_signature='as', **SBN)
    def GetInitialResultSet(self, terms):
        """
        Join separate terms in one ID line, start translation and send this line back
        on start of input
        """
        text = ' '.join(terms)
        provider = Settings.get().active_translator

        if self.live_enabled:
            if not self.loaded:
                return self.GetInitialResultSet(terms)
            translation_id = self.translation(text)
            results = [translation_id]
            if not translation_id.startswith(ERROR_PREFIX):
                results.append(CLIPBOARD_PREFIX + text)
            return results

        return [
            _('Translate “{text}” with {provider_name}').format(
                text=text, provider_name=TRANSLATORS[provider].prettyname
            )
        ]

    @dbus.service.method(in_signature='asas', out_signature='as', **SBN)
    def GetSubsearchResultSet(self, _previous_results, new_terms):
        return self.GetInitialResultSet(new_terms)

    @dbus.service.method(in_signature='as', out_signature='aa{sv}', **SBN)
    def GetResultMetas(self, ids):
        """Send translated text"""

        translate_id = ids[0]

        if len(ids) == 1:
            text = translate_id
            if translate_id in self.translations:
                text = self.translations[translate_id]
            return [
                {
                    'id': translate_id,
                    'name': text,
                }
            ]

        elif len(ids) == 2 and translate_id in self.translations and ids[1] == CLIPBOARD_PREFIX + ids[0]:
            text = self.translations[translate_id]
            lang = get_lang_name(self.dest_language)
            provider = Settings.get().active_translator
            description = f'{lang} — {TRANSLATORS[provider].prettyname}' if self.live_enabled else ''

            self.translations.clear()

            return [
                {
                    'id': translate_id,
                    'name': text,
                    'description': description,
                },
                {
                    'id': ids[1],
                    'name': _('Copy'),
                    'description': _('Copy translation to clipboard'),
                    'clipboardText': text,
                },
            ]

        else:
            # Probably never needed, just in case
            return [
                dict(
                    id=id,
                    name=id,
                )
                for id in ids
            ]

    @dbus.service.method(in_signature='sasu', **SBN)
    def ActivateResult(self, result_id, terms, timestamp):
        if not result_id.startswith(CLIPBOARD_PREFIX):
            self.LaunchSearch(terms, timestamp)

    @dbus.service.method(in_signature='asu', terms='as', timestamp='u', **SBN)
    def LaunchSearch(self, terms, _timestamp):
        text = ' '.join(terms)
        GLib.spawn_async_with_pipes(None, ['/usr/local/bin/dialect', '--text', text], None, GLib.SpawnFlags.SEARCH_PATH, None)

    def translation(self, src_text=None):
        """Start a new translation"""

        # If the two languages are the same, nothing is done
        if self.src_language != self.dest_language and src_text != '':
            error_id = ERROR_PREFIX + src_text

            message = self.translator.format_translation(src_text, self.src_language, self.dest_language)

            response = Session.get().send_and_read(message, None)

            if response:
                try:
                    data = response.get_data()
                    (translation, _lang) = self.translator.get_translation(data)

                    self.translations[src_text] = translation.text
                    return src_text
                except InvalidApiKey as exc:
                    logging.error(exc)
                    self.translations[error_id] = _('The provided API key is invalid')
                except ApiKeyRequired as exc:
                    logging.error(exc)
                    self.translations[error_id] = _('API key is required to use the service')
                except Exception as exc:
                    logging.error(exc)
            else:
                self.translations[error_id] = _('Translation failed, check for network issues')

            return error_id

    def is_live_enabled(self):
        return Settings.get().live_translation and Settings.get().sp_translation

    def _load_translator(self):
        self.loaded = False
        provider = Settings.get().active_translator
        self.translator = TRANSLATORS[provider]()

        if self.translator.trans_init_requests:
            requests = []
            for name in self.translator.trans_init_requests:
                message = getattr(self.translator, f'format_{name}_init')()
                callback = getattr(self.translator, f'{name}_init')
                requests.append([message, callback])
            Session.get().multiple(requests, self._on_loaded)
        else:
            self._on_loaded('')

    def _on_loaded(self, errors):
        if errors or self.translator.error:
            self.loaded = False
            self.load_failed = True
            self.dest_language = None
        else:
            self.loaded = True
            self.load_failed = False
            self.dest_language = self.translator.dest_langs[0]

            self.translator.settings.connect('changed', self._on_translator_settings_changed)

    def _on_settings_changed(self, _settings, key):
        if key.startswith('translator-'):
            self._load_translator()
        if key == 'live-translation' or key == 'sp-translation':
            self.live_enabled = self.is_live_enabled()

    def _on_translator_changed(self, *args):
        self._load_translator()

    def _on_translator_settings_changed(self, _settings, key):
        if key == 'src-langs' or key == 'dest-langs':
            self.dest_language = self.translator.dest_langs[0]
        else:
            self._load_translator()

if __name__ == "__main__":
    DBusGMainLoop(set_as_default=True)
    TranslateService()
    GLib.MainLoop().run()
