#!/usr/bin/python3
import gi, os, re, jwmkit_utils
import xml.etree.ElementTree as ET
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf

# JWM Kit - A set of Graphical Apps to simplify use of JWM (Joe's Window Manager) <https://codeberg.org/JWMKit/JWM_Kit>
# Copyright © 2020-2022 Calvin Kent McNabb <apps.jwmkit@gmail.com>
#
# This file is part of JWM Kit.
#
# JWM Kit is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# JWM Kit 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 JWM Kit.  If not, see <https://www.gnu.org/licenses/>.


def terminal_fix(data, term):
    # work around for poorly made .desktop files with Terminal=True, but still adds the terminal in the exec= line.
    terms = ['xfce4-terminal', 'i3-sensible-terminal', 'lxterminal', 'gnome-terminal', 'roxterm', 'defaultterminal',
             'mate-terminal', 'Eterm', 'terminology', 'sakura', 'terminator', 'gnome-terminal', 'qterminal',
             'deepin-terminal', 'xterm', 'rxvt', 'uxterm', 'urxvt', 'x-terminal-emulator', term]

    if ' -e ' in data:
        for terminal in terms:
            if terminal in data:
                return False
    return True


def read_desktop(filename, term):

    def get_appdata():
        try:
            with open(filename) as f:
                data = f.read()
        except FileNotFoundError:
            data = ''
        return data

    def get_values():
        settings_cats = ['Settings', 'X-Desktop', 'X-Desktop-appearance', 'DesktopSettings', 'Accessibility',
                         'X-Desktop-windows', 'X-Desktop-sleep', 'Screensaver', 'X-Desktop-applet', 'X-DesktopApplets',
                         'Clock', 'X-Setup', 'X-Setup-puppy', 'X-SetupEntry', 'X-DesktopCountry',
                         'X-Setup-wizard', 'X-SetupWizard', 'X-Setup-installation', 'X-SetupUtility',
                         'X-Desktop-settings', 'X-antiX']
        mv_settings = ['urxvtcontrol', 'bootmanager', 'ipinfo', 'wcpufreq', 'pup-sysinfo', 'hardinfo', 'loginmanager',
                       '/usr/local/bin/pschedule', 'pup-kview', 'sysinfo', '/usr/bin/hardinfo', 'video-info', 'gpptp',
                       'eventmanager', 'firewall_ng', 'firewallstatus', '/usr/sbin/firewallstatusrestart', 'janky_BT',
                       'samba.sh', 'pup-advert-blocker', 'clipit']
        keys = ['Name', 'Icon', 'Exec',  'Categories']
        values = []

        hidden = re.findall('NoDisplay=true', appdata)
        if len(hidden) != 0:
            return None

        is_term = False
        if re.findall('Terminal=true', appdata): is_term = True
        # create cat_test var to test if app belongs in settings
        try:
            cat_test = re.findall('\nCategories=(.*)', appdata)[0]
        except IndexError:
            cat_test = ''
        # Set cat_test to settings if the app is in the mv_settings list
        try:
            if re.findall('\nExec=(.*)', appdata)[0] in mv_settings:
                cat_test = 'Settings'
        except IndexError:
            pass

        for key in keys:
            if any(item in settings_cats for item in cat_test.split(';')):
                try:
                    values.append(re.findall('\n%s=(.*)' % key, appdata)[0])
                except IndexError:
                    values.append('')
        # Change the command to use the terminal when needed
        if is_term:
            if len(values) > 3:
                if terminal_fix(values[2], term):
                    # Fix for improperly made desktop files
                    if values[2] .startswith('TERM='):
                        values[2] = re.findall('TERM=[^ ]+ (.+)', values[2])[0]
                    values[2] = '{} -T "{}" -e "{}"'.format(term, values[0], values[2])
        if ''.join(values) != '':
            return values

    appdata = get_appdata()
    # Prevent errors with an attempting to verify the file is a Desktop file before processing
    try:
        appdata = re.findall('(?s)(\[Desktop Entry\].+?(?=\[Desktop|\Z))', appdata, re.MULTILINE)[0]
    except IndexError:
        appdata = ''

    return get_values()


def sort_apps(set_apps):
    # blacklist is for items that conflict with JWM Kit. Also includes installers for  browsers and Libreoffice
    software_blocklist = ['dl_chrome_opera.sh', 'install_brave_gui.sh', 'install_vivaldi_gui.sh', 'gpkgdialog',
                          'xpkgdialog', 'defaultterminal -e pkgdialog', '/usr/local/petget/pkg_chooser.sh', 'getflash'
                          'wmstartups', 'wizardwizard', 'pdesktop', 'pupclockset', 'get_libreoffice']
    personal_cats = ['X-Desktop', 'X-Desktop-appearance', 'X-Desktop-settings', 'DesktopSettings', 'Accessibility',
                     'X-Desktop-windows', 'X-Desktop-sleep', 'Screensaver', 'X-Desktop-applet', 'X-DesktopApplets']
    hardware_cats = ['HardwareSettings', 'Printing', 'X-XFCE-HardwareSettings']
    antix_cats = ['X-antiX']
    system_cats = ['X-Setup', 'X-Setup-puppy', 'X-SetupEntry', 'X-Setup-wizard', 'X-SetupWizard',
                   'X-Setup-installation', 'X-SetupUtility', 'X-DesktopCountry', 'Clock']
    jwmkit_apps, personal_apps, hardware_apps, system_apps, general_apps, antix_apps = [], [], [], [], [], []

    for app in set_apps:
        app_cats = app[3].split(';')
        if not app[2].lower() in software_blocklist:
            if app[0].startswith('JWM Kit'):
                app[3] = 'JWM Kit'
                app[0] = app[0][8:]
                jwmkit_apps.append(app)
            elif any(cat in antix_cats for cat in app_cats):
                app[3] = 'antiX'
                antix_apps.append(app)
            elif any(cat in hardware_cats for cat in app_cats):
                app[3] = 'Hardware'
                hardware_apps.append(app)
            elif any(cat in system_cats for cat in app_cats):
                if 'PackageManager' not in app_cats:
                    app[3] = 'System'
                    system_apps.append(app)
            elif any(cat in personal_cats for cat in app_cats):
                app[3] = 'Personal'
                personal_apps.append(app)
            else:
                app[3] = 'General'
                general_apps.append(app)

    all_apps = []
    if jwmkit_apps:
        all_apps.append(jwmkit_apps)
    if antix_apps:
        all_apps.append(antix_apps)
    if general_apps:
        all_apps.append(general_apps)
    if hardware_apps:
        all_apps.append(hardware_apps)
    if personal_apps:
        all_apps.append(personal_apps)
    if system_apps:
        all_apps.append(system_apps)

    return all_apps


def get_icon_paths(status_message):
    # read settings file to find user defined icons file
    home = os.path.expanduser('~')
    settings = '{}/.config/jwmkit/settings'.format(home)
    if os.path.isfile(settings):
        with open(settings) as f:
            f = f.read()
        f = '\n{}'.format(f)
        try:
            icon_path = re.findall('\nicons.*=(.+)', f)[0]
            if icon_path.startswith('$HOME'):
                icon_path = '{}{}'.format(home, icon_path[5:])
        except IndexError:
            # No fallback for now. In future I will search all includes in .jwmrc for IconPath tags.
            print('Icon path not found in settings')
            print('Use JWM Config to create or configure path to Groups xml')
            print('or manually edit the files')
            icon_path = ''
            status_message.set_markup('<b>Error :</b> Icon config not in Settings file. Unable to find icons.\n'
                                      'Use JWM Kit Repair &amp; Restore to repair the settings file')

    # make a list of the icons paths defined in the JWM xml icon file
    else:
        icon_path = ''
        status_message.set_markup(
            '<b>Error :</b> Can not read Settings file. Unable to find icons.'
            '\nUse JWM Kit Repair &amp; Restore to repair the settings file')
    try:
        tree = ET.parse(icon_path)
        root = tree.getroot()
        icon_path = []
        for node in root:
            node = node.text
            if node.startswith('$HOME'):
                node = '{}{}'.format(home, node[5:])
            # if the file exist add it to the list
            if os.path.isdir(node):
                icon_path.append(node)
    except (ET.ParseError, FileNotFoundError):
        # file is missing or corrupt.
        print('The file either contains no icons paths, is missing or corrupt.\n')
        icon_path = ''
    return icon_path


def get_set_apps(term):
    home = os.path.expanduser('~')
    sys_appdir = '/usr/share/applications/'
    home_appdir = os.path.join(home, '.local/share/applications/')
    applications = os.listdir(sys_appdir)
    sys_applications = re.findall("'([^']+?desktop)'", str(applications))
    if os.path.isdir(home_appdir):
        applications = os.listdir(home_appdir)
        home_applications = re.findall("'([^']+?desktop)'", str(applications))
    else:
        home_applications = []
    sys_applications = [i for i in sys_applications if i not in home_applications]
    sys_applications = ['{}{}'.format(sys_appdir, i) for i in sys_applications]
    home_applications = ['{}{}'.format(home_appdir, i) for i in home_applications]
    applications = sys_applications + home_applications
    app_list = []
    for filename in applications:
        app_data = read_desktop(filename, term)
        if app_data is not None:
            app_list.append(app_data)
    return app_list


class MainWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="JWM Kit Settings")
        self.set_border_width(10)
        try:
            self.set_icon_from_file('/usr/share/pixmaps/jwmkit/config.svg')
        except gi.repository.GLib.Error:
            self.set_icon_name('preferences-system')
        term = jwmkit_utils.find_terminal()
        set_apps = get_set_apps(term)
        set_apps = sort_apps(set_apps)
        status_message = Gtk.Label()
        self.jwm_icon_paths = get_icon_paths(status_message)

        mainbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        self.grid = Gtk.Grid(column_homogeneous=True, column_spacing=10, row_spacing=10)
        hbox_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        scrolledwindow = Gtk.ScrolledWindow()
        scrolledwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        scrolledwindow.set_min_content_height(370)

        close_button = Gtk.Button(label="Close", image=Gtk.Image(stock=Gtk.STOCK_CANCEL))
        close_button.set_property("width-request", 110)
        close_button.set_always_show_image(True)
        close_button.connect("clicked", Gtk.main_quit)

        about_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_ABOUT))
        about_button.set_always_show_image(True)
        about_button.set_property("width-request", 40)
        about_button.connect('clicked', jwmkit_utils.get_about, self)

        hbox_bottom.pack_start(about_button, False, False, 0)
        hbox_bottom.pack_start(status_message, True, True, 0)
        hbox_bottom.pack_end(close_button, False, False, 0)

        scrolledwindow.add(self.grid )
        mainbox.pack_start(scrolledwindow, True, True, 0)
        mainbox.add(hbox_bottom)
        self.set_border_width(20)
        self.add(mainbox)
        self.populate(set_apps)
        self.show_all()

    def populate(self, set_apps):
        # use gather app info to populate the window with icons/shortcuts to various settings tool

        def start_app(button, selected_app):
            # start the selected app when the button is pressed
            os.system('{} &'.format(selected_app))

        row_count = 0
        for app_list in set_apps:
            row_count += 1
            column_count = 1
            label = Gtk.Label()
            label.set_markup('<b>{}</b>'.format(app_list[0][3]))
            hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
            hbox.pack_start(label, False, True, 5)
            self.grid.attach(hbox, column_count, row_count, 3, 5)
            row_count += 5
            for app in app_list:
                app_icon = Gtk.Image()
                app_icon.set_from_pixbuf(self.update_icon(app[1], 48))
                app_button = Gtk.Button(image=app_icon, label=app[0], xalign=0)
                app_button.set_always_show_image(True)
                app_button.set_relief(Gtk.ReliefStyle.NONE)
                app_button.connect('clicked', start_app, app[2])
                self.grid.attach(app_button, column_count, row_count, 1, 1)
                column_count += 1
                if column_count == 4:
                    column_count = 1
                    row_count += 1

    def update_icon(self, icon, size):
        if not os.path.isfile(icon):
            if '/' in icon:
                icon = ''
            try:
                icon = self.get_icon_dir(icon)
                icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon, size, size, preserve_aspect_ratio=False)
            except IndexError:
                icon = Gtk.IconTheme.get_default().load_icon('image-missing', size, Gtk.IconLookupFlags.FORCE_SIZE)
        else:
            try:
                icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon, size, size, preserve_aspect_ratio=False)
            except gi.repository.GLib.Error:
                icon = Gtk.IconTheme.get_default().load_icon('image-missing', size, Gtk.IconLookupFlags.FORCE_SIZE)
        return icon

    def get_icon_dir(self, icon_name):
        # search the icon directories to find the icon
        icons = []
        icon_name = icon_name.replace(' ', '\ ')
        for icon_dir in self.jwm_icon_paths:
            icon_list = os.listdir(icon_dir)
            for icon in ['{}.png'.format(icon_name), '{}.svg'.format(icon_name), '{}.xpm'.format(icon_name)]:
                if icon in icon_list:
                    icons.append(os.path.join(icon_dir, icon))
                    while '' in icons: icons.remove('')
                    if icons:
                        break
        return icons[0]


win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.set_position(Gtk.WindowPosition.CENTER)
Gtk.main()
