#!/usr/bin/python3

import gi

gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, Gdk, Gio, GObject

import os, locale
import subprocess
import re
import sys
import gi
import gettext
import time
import pwd

ICON_SIZE = 32
PATH_TO_USER_DESKTOP_FILES = '~/.local/share/applications/'
PATH_TO_SYS_DESKTOP_FILES = '/usr/share/applications/'
PATH_TO_FLATPAK_DESKTOP_FILES = '/var/lib/flatpak/exports/share/applications/'
SHORTCUT_DESKTOP = 'uncom-shortcut.desktop'
SYS_DESKTOP_FILE = '/usr/share/applications/uncom-shortcut.desktop'
DEFAULT_ICON = '/usr/share/icons/Yaru/256x256/apps/org.uncom.activator.png'

def _image_from_gicon(gicon):
    image = Gtk.Image.new_from_gicon(gicon)
    image.set_pixel_size(ICON_SIZE)
    return image

class ListBoxRowWithData(Gtk.ListBoxRow):
    def __init__(self, data):
        super().__init__()
        self.data = data

class _AppChooser(Gtk.Dialog):
    def __init__(self, main_window):
        uhb = Gtk.Settings.get_default().props.gtk_dialogs_use_header
        Gtk.Dialog.__init__(self, title=_("Applications"), use_header_bar=uhb)

        self._all = {}

        self.entry = Gtk.SearchEntry(
                placeholder_text=_("Search…"))
        self.entry.set_width_chars(30)
        self.entry.props.activates_default=True
        if (Gtk.check_version(3, 22, 20) == None):
            self.entry.set_input_hints(Gtk.InputHints.NO_EMOJI)

        self.searchbar = Gtk.SearchBar()
        self.searchbar.connect_entry(self.entry)
        self.searchbar.set_child(self.entry)
        self.searchbar.props.hexpand = True
        # Translators: This is the accelerator for opening the AppChooser search-bar
#        self._search_key, self._search_mods = Gtk.accelerator_parse(("<primary>f"))
        keyval = Gdk.keyval_from_name("F")
        if keyval != 0:
            self._search_key = keyval
            self._search_mods = Gdk.ModifierType.CONTROL_MASK

        lb = Gtk.ListBox()
        lb.props.activate_on_single_click = False
        lb.set_sort_func(self._sort_apps, None)
#        lb.set_header_func(_list_header_func, None)
###        lb.set_filter_func(self._list_filter_func, None)
        self.entry.connect("search-changed", self._on_search_entry_changed)
        lb.connect("row-activated", lambda b, r: self.response(Gtk.ResponseType.OK) if r.get_mapped() else None)
        lb.connect("row-selected", self._on_row_selected)

        apps = Gio.app_info_get_all()
        for a in apps:
            if a.should_show():
                w = self._build_widget(
                    a,
                    "")
                if w:
                    self._all[w] = a
#                        print("XXX: appending w....")
                    lb.append(w)

        lb.set_filter_func(self._list_filter_func, apps)

        sw = Gtk.ScrolledWindow()
        sw.props.hscrollbar_policy = Gtk.PolicyType.NEVER
        sw.set_child(lb)
        sw.set_vexpand(True)
        sw.set_valign(Gtk.Align.FILL)

        self.add_button(_("Close"), Gtk.ResponseType.CANCEL)
        self.add_button(_("Confirm"), Gtk.ResponseType.OK)
        self.set_default_response(Gtk.ResponseType.OK)

        if self.props.use_header_bar:
            searchbtn = Gtk.ToggleButton()
            searchbtn.props.valign = Gtk.Align.CENTER
            image = Gtk.Image(icon_name = "edit-find-symbolic", icon_size = Gtk.IconSize.NORMAL)
            searchbtn.set_child(image)
            context = searchbtn.get_style_context()
            context.add_class("image-button")
            context.remove_class("text-button")
            self.get_header_bar().pack_end(searchbtn)
            self._binding = searchbtn.bind_property("active", self.searchbar, "search-mode-enabled", GObject.BindingFlags.BIDIRECTIONAL)

        self.get_content_area().append(self.searchbar)
        self.get_content_area().append(sw)
        self.set_modal(True)
        self.set_transient_for(self)
        self.set_size_request(400,800)

        self.listbox = lb


    def _sort_apps(self, a, b, user_data):
        aname = self._all.get(a).get_name()
        bname = self._all.get(b).get_name()

        if aname < bname:
            return -1
        elif aname > bname:
            return 1
        else:
            return 0

    def _build_widget(self, a, extra):
        row = ListBoxRowWithData(a)
        g = Gtk.Grid()
        g.set_margin_start(15)
        g.set_margin_top(15)
        g.set_margin_end(15)
        g.set_margin_bottom(15)

        if not a.get_name():
            return None
        icn = a.get_icon()
        if icn:
            img = _image_from_gicon(icn)
            img.set_margin_end(16)
            g.attach(img, 0, 0, 1, 1)
            img.props.hexpand = False
        else:
             img = None #attach_next_to treats this correctly
        lbl = Gtk.Label(label=a.get_name(), xalign=0)
        g.attach_next_to(lbl,img,Gtk.PositionType.RIGHT,1,1)
        lbl.props.hexpand = True
        lbl.props.halign = Gtk.Align.START
        lbl.props.vexpand = False
        lbl.props.valign = Gtk.Align.CENTER
        if extra:
            g.attach_next_to(
                Gtk.Label(label=extra),
                lbl,Gtk.PositionType.RIGHT,1,1)
        row.set_child(g)
        #row.get_style_context().add_class('tweak-white')
        return row

    def _list_filter_func(self, row, unused):
        txt = self.entry.get_text().lower()
        grid = row.get_child()

#        print("_list_filter_func: txt = ", txt)
#        print("_list_filter_func: row.data.get_name() = ", row.data.get_name())
#        print("_list_filter_func: row.data.get_id() = ", row.data.get_id())

        if txt in row.data.get_name().lower():
            return True
        elif txt in row.data.get_id().lower():
            return True
        else:
            return False

        return True

    def _on_search_entry_changed(self, editable):
        self.listbox.invalidate_filter()
        selected = self.listbox.get_selected_row()
        if selected and selected.get_mapped():
            self.set_response_sensitive(Gtk.ResponseType.OK, True)
        else:
            self.set_response_sensitive(Gtk.ResponseType.OK, False)

    def _on_row_selected(self, box, row):
        if row and row.get_mapped():
            self.set_response_sensitive(Gtk.ResponseType.OK, True)
        else:
            self.set_response_sensitive(Gtk.ResponseType.OK, False)

    def _on_key_press(self, widget, event):
      mods = event.state & Gtk.accelerator_get_default_mod_mask()
      if event.keyval == self._search_key and mods == self._search_mods:
          self.searchbar.set_search_mode(not self.searchbar.get_search_mode())
          return True
      keyname = Gdk.keyval_name(event.keyval)
      if keyname == 'Escape':
          if self.searchbar.get_search_mode():
              self.searchbar.set_search_mode(False)
              return True
      elif keyname not in ['Up', 'Down']:
          if not self.entry.is_focus() and self.searchbar.get_search_mode():
              if self.entry.im_context_filter_keypress(event):
                  self.entry.grab_focus()
                  l = self.entry.get_text_length()
                  self.entry.select_region(l, l)
                  return True

          return self.searchbar.handle_event(event)

      return False

    def get_selected_app(self):
        row = self.listbox.get_selected_row()
        if row:
            return self._all.get(row)
        return None




class MainWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
#        Gtk.Window.__init__(self, title="Настройка ярлыка!")

        translations = {}
        supported_languages = ['en_GB', 'ru']
        for lang in supported_languages:
            translations[lang] = gettext.translation("uncom-shortcut", localedir="/usr/share/locale", languages=[lang])

        if 'ru' in os.environ['LANG']:
            translations['ru'].install()
            _ = translations['ru'].gettext
        else:
            translations['en_GB'].install()
            _ = translations['en_GB'].gettext

        self.set_default_size(400, 250)
        self.set_title(_("Shortcut Settings"))
        self.set_resizable(False)

        self.main_box = Gtk.Box(spacing=0, orientation=Gtk.Orientation.VERTICAL)
        self.set_child(self.main_box)
        self.main_box.set_margin_start(20)
        self.main_box.set_margin_end(20)
        self.main_box.set_margin_top(20)
        self.main_box.set_margin_bottom(20)

        # Create main box container
        self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        self.main_box.append(self.box)

        self.current_icon = self.get_current_icon(passed_string)
        print("Current icon path: " + self.current_icon)
        self.pix = self.get_system_icon_or_image(self.current_icon, "image-x-generic", Gtk.IconSize.LARGE)
        self.pix.set_size_request(128, 128)
        self.box.append(self.pix)

        self.appname = self.get_current_appname(passed_string)
        self.icon_filename = self.get_current_icon(passed_string)
        self.execline = self.get_current_exec(passed_string)

        # Create label
#        label = Gtk.Label(label="Выберите запускаемое приложение")
#        label.set_margin_top(8)
#        self.box.append(label)

        # Additional label
        self.label = Gtk.Label(label=self.appname)
        self.label.set_margin_top(8)
        self.box.append(self.label)

        # chosen app label
        label = Gtk.Label(label="<i>" + _("Session restart is required") + "</i>")
        label.set_margin_top(8)
        label.set_use_markup(True)
        self.box.append(label)

        self.button_change = Gtk.Button(label=_("Select Application"))
        self.box.append(self.button_change)
        self.button_change.set_margin_top(20)
        self.button_change.set_margin_start(50)
        self.button_change.set_margin_end(50)
        self.button_change.connect("clicked", self.on_change_clicked)


        # Create OK button
        self.button_apply = Gtk.Button(label=_("Apply"))
        self.box.append(self.button_apply)
        self.button_apply.set_margin_top(20)
        self.button_apply.set_margin_start(50)
        self.button_apply.set_margin_end(50)
        self.button_apply.connect("clicked", self.on_apply_clicked)

    def get_current_icon(self, desktop_file):
#        print("get_current_icon: desktop_file = ", desktop_file)
        if desktop_file:
            with open(desktop_file, 'r') as file:
                content = file.read()

            # Find the "Icon=" line and replace the entire line with the selected image file
            lines = content.splitlines()
            for i, line in enumerate(lines):
                if line.strip().startswith('Icon='):
                    print("get_current_icon: lines = ", lines[i][5:])
                    return lines[i][5:]

    def get_current_appname(self, filename):
#        print("get_current_appname: filename = ", filename)
        if filename:
            with open(filename, 'r') as file:
                content = file.read()

            # Find the "#App=" line and get it
            lines = content.splitlines()
            for i, line in enumerate(lines):
                if line.strip().startswith('#App='):
#                    print("get_current_appname: lines = ", lines[i][5:])
                    return lines[i][5:]
            return _("None")
        else:
            return ""

    def get_current_exec(self, filename):
#        print("get_current_exec: filename = ", filename)
        if filename:
            with open(filename, 'r') as file:
                content = file.read()

            # Find the "Exec=" line and get it
            lines = content.splitlines()
            for i, line in enumerate(lines):
                if line.strip().startswith('Exec='):
#                    print("get_current_exec: lines = ", lines[i][5:])
                    return lines[i][5:]
            return ""
        else:
            return ""

    def get_system_icon_or_image(self, name, fallback, size):
#        print("get_system_icon_or_image: start, name = ", name)
        if os.path.isfile(name):
#            print ("get_system_icon_or_image: This is an image, using file")
            return Gtk.Image.new_from_file(name)

        theme = Gtk.IconTheme.get_for_display(self.get_display())
        icon = Gtk.IconTheme.lookup_icon(theme, name, fallback, 256, 1, Gtk.TextDirection.NONE, Gtk.IconLookupFlags.FORCE_REGULAR)
#        print("get_system_icon_or_image: icon = ", icon)
        if icon:
#            print("get_system_icon_or_image: This is an icon with next path: " + icon.get_file().get_path())
            return Gtk.Image.new_from_file(icon.get_file().get_path())
        else:
#            print("get_system_icon_or_image: No available icon, show just empty space")
            return Gtk.Image.new()

    def get_system_icon_filename(self, name, fallback, size):
        if os.path.isfile(name):
#            print ("get_system_icon_filename: This is an image file, returning itself")
            return name

        # Check that it is not a missing file
        if "/home/" in name:
#            print("Error: missing file provided, returning default")
            return DEFAULT_ICON

        theme = Gtk.IconTheme.get_for_display(self.get_display())
#        print("get_system_icon_filename: name = ", name)
        icon = Gtk.IconTheme.lookup_icon(theme, name, fallback, 256, 1, Gtk.TextDirection.NONE, Gtk.IconLookupFlags.FORCE_REGULAR)
        if icon:
#            print("get_system_icon_filename: This is an icon with next path: " + icon.get_file().get_path())
            return icon.get_file().get_path()
        else:
#            print("No available icon, return None")
            return None


    def on_response(self, dialog, response):
        if response == Gtk.ResponseType.OK:
            df = dialog.get_selected_app()
            if df:
#                print("on_response: df.get_id() = ", df.get_id())
#                print("on_response: df.get_commandline() = ", df.get_commandline())
                self.label.set_text(df.get_name())
#                user_desktop_file = os.path.join(os.path.expanduser(PATH_TO_USER_DESKTOP_FILES), df.get_id())
#                if os.path.isfile(user_desktop_file):
#                    # There is a desktop file in user dir!
#                    self.current_icon = self.get_current_icon(user_desktop_file)
#                else:
#                    self.current_icon = self.get_current_icon(PATH_TO_SYS_DESKTOP_FILES + df.get_id())
                self.current_icon = self.get_current_icon(df.get_filename())

#                print("on_response: self.current_icon = ", self.current_icon)
                filename = self.get_system_icon_filename(self.current_icon, "image-x-generic", Gtk.IconSize.LARGE)
#                print("on_response: User dropped file: filename = " + filename)
                self.icon_filename = filename
                self.execline = df.get_commandline()
                self.appname = df.get_name()
                self.pix.set_from_file(str(filename))
            print("OK button clicked")
        elif response == Gtk.ResponseType.CANCEL:
            print("Cancel button clicked")
        dialog.destroy()


    def on_change_clicked(self, widget):
#        print("on_change_clicked")
        a = _AppChooser(self)
        a.show()
        a.connect("response", self.on_response)

    def on_apply_clicked(self, widget):
#        print("on_apply_clicked: self.icon_filename = ", self.icon_filename)
#        print("on_apply_clicked: self.execline = ", self.execline)
        self.change_shortcut(self.icon_filename, self.execline, self.appname)

        self.dialog = Gtk.MessageDialog(
            transient_for=self,
            message_type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.OK,
            text=_("Settings will be applied on next login"),
        )

        def close_dialog(self, app):
            self.destroy()

        self.dialog.connect('response', close_dialog)
        self.dialog.set_modal(self)
        self.dialog.show()
#        exit(0)

    def change_shortcut(self, filename, execline, appname):
        if filename:
            # Read content from user or system .desktop file and write to user
            with open(passed_string, 'r') as file:
                content = file.read()

            was_already_modified = False

            # Find the "Icon=" line and replace the entire line with the selected image file
            original_icon = ""
            lines = content.splitlines()
            for i, line in enumerate(lines):
                if line.strip().startswith('Icon='):
                    original_icon = line.strip().split("=")[1]
                    lines[i] = f'Icon={filename}'
                if line.strip().startswith('Exec='):
                    original_exec = line.strip().split("=")[1]
                    lines[i] = f'Exec={execline}'
                if line.strip().startswith('#App='):
                    lines[i] = f'#App={appname}'
                    was_already_modified = True

            new_content = '\n'.join(lines)

            # Save the modified content to the user's home directory
            home_dir = os.path.expanduser(PATH_TO_USER_DESKTOP_FILES)
            new_file_path = os.path.join(home_dir, SHORTCUT_DESKTOP)
            with open(new_file_path, 'w') as new_file:
                new_file.write(new_content)
                if not was_already_modified:
                    new_file.write("\n" + "#App=" + self.appname + "\n")
            return new_file_path


class MyApp(Adw.Application):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.connect('activate', self.on_activate)

    def on_activate(self, app):
        self.win = MainWindow(application=app)
        self.win.present()

print("Running application as: " + os.getlogin())
passed_string = "n/a"

# Given string firl .desktop file of the app
user_desktop_file = os.path.join(os.path.expanduser(PATH_TO_USER_DESKTOP_FILES), SHORTCUT_DESKTOP)
print("user_desktop_file " + user_desktop_file)
if os.path.isfile(user_desktop_file):
    passed_string = user_desktop_file
else:
    passed_string = SYS_DESKTOP_FILE

print("Passed path to .desktop file: " + passed_string)

main_app = MyApp(application_id="tech.uncom.shortcut")
main_app.run()
