#!/usr/bin/python3
#
# Copyright 2023 Ideve Core
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This file was inspired by the search_provider.in file from the dialect-app project
# developed by dialect-team (available at https://github.com/dialect-app/dialect).
# This file is not a direct copy of the original file, but rather an original work based on it.
# This file is licensed under the GNU General Public License (GPL) version 3 or later.
# For more information about the GPL license, please refer to:
# https://www.gnu.org/licenses/gpl-3.0.en.html
#
# The complete code for the application is available at https://github.com/dialect-app/dialect.
#
# SPDX-License-Identifier: GPL-3.0-or-later

import sys
import logging
import locale
import gettext

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

from gi.repository import GLib, Gio
from valuta.define import CODES
from valuta.utils import Convertion, Utils

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

localedir = '/usr/share/locale'
ui_trans = gettext.translation('valuta', localedir, fallback=True)
ui_trans.install(names=['gettext'])

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

dbus_interface_description = '''
<!DOCTYPE node PUBLIC
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
<node>
  <interface name="org.gnome.Shell.SearchProvider2">
    <method name="GetInitialResultSet">
      <arg type="as" name="terms" direction="in" />
      <arg type="as" name="results" direction="out" />
    </method>
    <method name="GetSubsearchResultSet">
      <arg type="as" name="previous_results" direction="in" />
      <arg type="as" name="terms" direction="in" />
      <arg type="as" name="results" direction="out" />
    </method>
    <method name="GetResultMetas">
      <arg type="as" name="identifiers" direction="in" />
      <arg type="aa{sv}" name="metas" direction="out" />
    </method>
    <method name="ActivateResult">
      <arg type="s" name="identifier" direction="in" />
      <arg type="as" name="terms" direction="in" />
      <arg type="u" name="timestamp" direction="in" />
    </method>
    <method name="LaunchSearch">
      <arg type="as" name="terms" direction="in" />
      <arg type="u" name="timestamp" direction="in" />
    </method>
  </interface>
</node>
'''

class ConvertionService:
  def __init__(self):
    self.currency_data = {
      "from_currency_value": 1,
      "to_currency_value": None,
      "currency_value": None,
    }
    self.utils = Utils('io.github.idevecore.Valuta')
    self.dest_currencies = {}
    self.from_currency = self.utils.settings.get_string('src-currency')
    self.to_currency = self.utils.settings.get_string('dest-currency')
    self.provider = self.utils.settings.get_enum('providers')
    self.utils.settings.connect("changed::src-currency", self.on_currencies_changed);
    self.utils.settings.connect("changed::dest-currency", self.on_currencies_changed);

  def GetInitialResultSet(self, terms, callback):
    value = ' '.join(terms)

    if self.utils.parse_number(value):
      self.currency_data['from_currency_value'] = float(self.utils.parse_number(value))
      converter_id = self.convertion(value)
      results = [converter_id]
      if not converter_id.startswith(ERROR_PREFIX):
        results.append(CLIPBOARD_PREFIX + value)
      callback(results)

    value_splited = value.upper().split(' ')
    if len(value_splited) == 4:
      if value_splited[1] in CODES and value_splited[3] in CODES:
        if self.utils.parse_number(value_splited[0]):
          if value_splited[1] != self.from_currency or value_splited[3] != self.to_currency:
            self.utils.settings.set_string('src-currency', value_splited[1])
            self.utils.settings.set_string('dest-currency', value_splited[3])
            self.on_currencies_changed('', '')
            self.currency_data['from_currency_value'] = float(self.utils.parse_number(value_splited[0]))
            converter_id = self.convertion(value)
            results = [converter_id]
            if not converter_id.startswith(ERROR_PREFIX):
              results.append(CLIPBOARD_PREFIX + value)
            callback(results)
          else:
            self.currency_data['from_currency_value'] = float(self.utils.parse_number(value_splited[0]))
            converter_id = self.convertion(value)
            results = [converter_id]
            if not converter_id.startswith(ERROR_PREFIX):
              results.append(CLIPBOARD_PREFIX + value)
            callback(results)

  def GetSubsearchResultSet(self, _previous_results, new_terms, callback):
    return self.GetInitialResultSet(new_terms, callback)

  def GetResultMetas(self, ids, callback):
    """Send destination currency value"""

    converter_id = ids[0]

    if len(ids) == 1:
      value = converter_id
      if converter_id in self.dest_currencies:
        value = self.dest_currencies[converter_id]
      callback(
        [
          {
            'id': GLib.Variant("s", converter_id),
            'name': GLib.Variant("s", value),
          }
        ]
      )

    elif (
      len(ids) == 2 and converter_id in self.dest_currencies
      and ids[1] == CLIPBOARD_PREFIX + ids[0]
    ):
      value = self.dest_currencies[converter_id]
      description = f'{_("According to")} {self.utils.settings.get_string("providers").upper()}'

      self.dest_currencies.clear()

      callback(
        [
          {
            'id': GLib.Variant("s", converter_id),
            'name': GLib.Variant("s", f'{self.utils.format_number(str(self.currency_data["from_currency_value"]))} {self.from_currency} = {self.utils.format_number(str(value))} {self.to_currency}'),
            'description': GLib.Variant("s", description),
          },
          {
            'id': GLib.Variant("s", ids[1]),
            'name': GLib.Variant("s", _('Copy')),
            'description': GLib.Variant("s", _('Copy convertion to clipboard')),
            'clipboardText': GLib.Variant("s", self.utils.format_number(str(value)))
          }
        ]
      )

    else:
      callback(
        [
          dict(
            id=GLib.Variant("s", id),
            name=GLib.Variant("s", id),
          )
          for id in ids
        ]
      )

  def ActivateResult(self, result_id, terms, timestamp, callback):
    if not result_id.startswith(CLIPBOARD_PREFIX):
      self.LaunchSearch(terms, timestamp)
    callback((None,))

  def LaunchSearch(self, terms, _timestamp):
    value = ' '.join(terms)
    splited_value = value.split(' ')
    if len(splited_value) == 4:
      GLib.spawn_async_with_pipes(
          None, ['/usr/bin/valuta', '--src-currency-value', splited_value[0]], None,
          GLib.SpawnFlags.SEARCH_PATH, None
      )
    else:
      GLib.spawn_async_with_pipes(
        None, ['/usr/bin/valuta', '--src-currency-value', value], None,
        GLib.SpawnFlags.SEARCH_PATH, None
      )

  def convertion(self, from_currency_value=None):
   if not self.from_currency == self.to_currency:
    error_id = ERROR_PREFIX + from_currency_value
    if not self.currency_data['to_currency_value'] or self.provider != self.utils.settings.get_enum("providers"):
      self.currency_data['to_currency_value'] = Convertion(self.utils.settings).convert_raw(1, self.from_currency, self.to_currency, self.utils.settings.get_enum("providers"))

    if self.currency_data['to_currency_value']:
      try:
        to_currency_value = float(self.currency_data["from_currency_value"]) * float(self.currency_data["to_currency_value"])
        self.dest_currencies[from_currency_value] = str(to_currency_value)
        return from_currency_value
      except Exception as exc:
        logging.error(exc)

    return error_id

  def on_currencies_changed(self, widget, state):
    self.from_currency = self.utils.settings.get_string('src-currency')
    self.to_currency = self.utils.settings.get_string('dest-currency')
    self.currency_data['to_currency_value'] = None

class ConvertionServiceApplication(Gio.Application):
  def __init__(self):
    Gio.Application.__init__(self,
                             application_id='io.github.idevecore.Valuta.SearchProvider',
                             flags=Gio.ApplicationFlags.IS_SERVICE,
                             inactivity_timeout=10000)
    self.service_object = ConvertionService()
    self.search_interface = Gio.DBusNodeInfo.new_for_xml(dbus_interface_description).interfaces[0]

  def do_dbus_register(self, connection, object_path):
    try:
      connection.register_object(
        object_path=object_path,
        interface_info=self.search_interface,
        method_call_closure=self.on_dbus_method_call
      )
    except:
      self.quit()
      return False
    finally:
      return True

  def on_dbus_method_call(self, connection, sender, object_path, interface_name, method_name, parameters, invocation):
    def return_value(results):
      results = (results,)
      if results == (None,):
        results = ()
      results_type = (
        "("
        + "".join(
          map(
            lambda argument_info: argument_info.signature,
            self.search_interface.lookup_method(method_name).out_args,
          )
        )
        + ")"
      )
      wrapped_results = GLib.Variant(results_type, results)
      invocation.return_value(wrapped_results)
      self.release()

    self.hold()

    method = getattr(self.service_object, method_name)
    arguments = list(parameters.unpack())
    arguments.append(return_value)

    method(*arguments)

if __name__ == "__main__":
    app = ConvertionServiceApplication()
    sys.exit(app.run())
