#!/usr/bin/python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
import os
from os.path import expanduser
import re
import resource
import shutil
import subprocess
import glob


class MenuAdd:

    def __init__(self):
        '''Activate the SysInfo class'''
        # this chunk of code initializes everything

        '''Create the GUI'''
        builder = Gtk.Builder()
        builder.add_from_file("/usr/share/ubuntustudio-menu-add/ubuntustudio-menu-add.glade")
        '''Get windows'''
        self.window_main = builder.get_object('window_main')
        self.about_dial = builder.get_object('about_dial')
        self.file_bad_dial = builder.get_object('file_bad_dial')
        self.help_dial = builder.get_object('help_dial')
        '''Get buttons and entries'''
        self.name_entry = builder.get_object('name_entry')
        self.visible_ck = builder.get_object('visible_ck')
        self.active_ck = builder.get_object('active_ck')
        self.use_system_ck = builder.get_object('use_system_ck')
        self.menufolder_combo = builder.get_object('menufolder_combo')
        self.submenu_combo = builder.get_object('submenu_combo')
        self.user_combo = builder.get_object('user_combo')
        self.system_combo = builder.get_object('system_combo')
        self.but_edit = builder.get_object('but_edit')
        '''dialog transient settings'''
        self.about_dial.set_transient_for(self.window_main)
        self.help_dial.set_transient_for(self.window_main)
        self.file_bad_dial.set_transient_for(self.window_main)

        handlers = {
            "on_window_main_delete_event": self.on_window_main_delete_event,
            "about_cb": self.about_cb,
            "edit_cb": self.edit_cb,
            "exit_cb": self.exit_cb,
            "visible_cb": self.visible_cb,
            "active_cb": self.active_cb,
            "menu_cb": self.menu_cb,
            "submenu_cb": self.submenu_cb,
            "filename_cb": self.filename_cb,
            "local_cb": self.local_cb,
            "system_cb": self.local_cb,
            "use_system_cb": self.use_system_cb,
            "help_cb": self.help_cb,
        }
        builder.connect_signals(handlers)

        self.time = 0
        self.timeout_id = GLib.timeout_add(500, self.file_timeout, None)

        self.visible_ck.set_active(True)
        self.tempfile = os.path.expanduser("~/.config/ubuntustudio_menu_temp.desktop")
        # string from user input for filename
        self.inputstr = ""
        # string with .desktop
        self.deskfile = ""
        # string with the file to write to if active
        self.writefile = ""
        # string with path to local directory
        self.deskpath = os.path.expanduser("~/.local/share/applications/")
        # full file name to write to if inactive
        self.inactive = ""
        # categorie to add
        self.categories = "none"
        self.cat_change = False
        # file is available in system directories
        self.in_system = False
        # file has already been added to local
        self.in_local = False
        # item has name, exec lines (will work)
        self.item_ok = False
        # item was active as found on disk
        self.item_active = False
        # name of inputstring temp file is valid for
        self.temp_copied = ""

        # fill local items
        loc_list = glob.glob(self.deskpath+'*.desktop')
        for item in loc_list:
            filen = os.path.basename(item)
            file_id = filen.split('.')[0]
            with open (item, 'rt') as ifile:
                for nextline in ifile:
                    if "Name" == nextline.split('=')[0]:
                        self.user_combo.append(file_id, (nextline.strip().split('=')[1]))

        loc_list = glob.glob(self.deskpath+'*.inactive')
        for item in loc_list:
            filen = os.path.basename(item)
            file_id = filen.split('.')[0]
            with open (item, 'rt') as ifile:
                for nextline in ifile:
                    if "Name" == nextline.split('=')[0]:
                        self.user_combo.append(file_id, (nextline.strip().split('=')[1]))

        # fill system items
        loc_list = glob.glob('/usr/share/applications/*.desktop')
        for item in loc_list:
            filen = os.path.basename(item)
            file_id = os.path.splitext(filen)[0]
            name = ""
            bad = False
            with open (item, 'rt') as ifile:
                for nextline in ifile:
                    if "Name" == nextline.split('=')[0]:
                        name = nextline.strip().split('=')[1]
                    if nextline.strip() == "NoDisplay=true":
                        bad = True
            if not bad:
                self.system_combo.append(file_id, name)
        loc_list = []

    def file_timeout(self, data):
        ''' instead of checking the filename for validity each key stroke
        leave some time between keystrokes'''
        if self.time:
            self.time = self.time - 1
            if not self.time:
                ''' order of things here:
                    - check filename
                    - check existing
                    - check if valid
                '''
                self.active_ck.set_active(True)
                self.make_filenames()
                self.find_existing()
                # at this point we could actually edit
                self.but_edit.set_sensitive(True)
                self.check_valid()

        return True # keeps timer happy

    '''the next calls are for checking out files'''
    def make_filenames(self):
        '''Makes up a few file names:
            parses the user input for legality
            adds .desktop
            creates the path of the user's directory
            creates the full path for the output file
        '''
        badset = "\'\"~` /|\\[]{}()?&*@!" # need to allow dots so check for ".desktop another way
        self.inputstr = str(self.name_entry.get_text()).lower()
        self.inputstr = self.inputstr.split(".desktop")[0]
        if len(self.inputstr) < 1:
            self.file_bad_dial.format_secondary_text("file name too small, try typing a file name")
            self.file_bad_dial.run()
            self.file_bad_dial.hide()
            return 1
        if 1 in [c in self.inputstr for c in badset]:
            self.file_bad_dial.format_secondary_text("file name contains illegal characters {}()[\"  \'~`/|\\?&*@!]")
            self.file_bad_dial.run()
            self.file_bad_dial.hide()
            self.name_entry.set_text(self.inputstr)
            self.deskfile = ""
            self.writefile = ""
            return 1
        self.deskfile = self.inputstr+".desktop"
        self.deskpath = os.path.expanduser("~/.local/share/applications/")
        self.writefile = self.deskpath+self.deskfile
        self.inactive = self.deskpath+self.inputstr+".inactive"
        return 0

    def find_existing(self):
        ''' check if the file we are working with already exists.
            if so set if it is system, local or active
            /usr/local/share/:/usr/share/ or $XDG_DATA_DIRS
            start with .local/share/applications/
            add /usr/share/applications and usr/local/...
            even if we find the name in .local check the rest
            so that we know if there is a system copy.
        '''
        found = False
        self.in_local = False
        self.in_system = False
        if os.path.isfile(self.writefile):
            print("this is an existing active file")
            shutil.copyfile(self.writefile, self.tempfile)
            self.active_ck.set_active(True)
            self.in_local = True
            found = True
        elif os.path.isfile(self.inactive):
            print("this is an existing inactive file")
            shutil.copyfile(self.inactive, self.tempfile)
            self.active_ck.set_active(False)
            self.in_local = True
            found = True

        if os.path.isfile("/usr/share/applications/"+str(self.deskfile)):
            self.in_system = True
            if not found:
                shutil.copyfile(f'/usr/share/applications/{self.deskfile}', self.tempfile)
                found = True

        if os.path.isfile("/usr/local/share/applications/"+str(self.deskfile)):
            self.in_system = True
            if not found:
                shutil.copyfile(f'/usr/local/share/applications/{self.deskfile}', self.tempfile)
                found = True

        if found:
            self.temp_copied = self.inputstr
            if self.in_system:
                self.use_system_ck.set_sensitive(True)
                self.use_system_ck.set_active(False)
            else:
                self.use_system_ck.set_sensitive(False)
                self.use_system_ck.set_active(False)

    def check_valid(self):
        ''' check if the file has a Name field with some text
            check if the file has an Exec field with some text
            check if the file has a URL field and should be rejected
        '''
        self.item_ok = False
        good = 0
        if os.path.isfile(self.tempfile):
            with open (self.tempfile, 'rt') as ifile:
                for nextline in ifile:
                    if "Name" == nextline.split('=')[0]:
                        if len(nextline.strip()) > 5:
                            good = good + 1
                    if "Exec" == nextline.split('=')[0]:
                        if len(nextline.strip()) > 5:
                            good = good + 1
                    if "URL" == nextline.split('=')[0]:
                        if len(nextline.strip()) > 4:
                            good = good - 1
                            self.file_bad_dial.format_secondary_text(
                                "The current item is being used as a Link to a URL these don't work in menus")
                            self.file_bad_dial.run()
                            self.file_bad_dial.hide()
                            self.name_entry.set_text("")
                    if nextline.strip() == "NoDisplay=true":
                        self.visible_ck.set_active(False)

        if good > 1:
            self.item_ok = True
            self.menufolder_combo.set_sensitive(True)
            self.visible_ck.set_sensitive(True)
            self.active_ck.set_sensitive(True)
            self.but_edit.set_sensitive(True)
            return 0
        else:
            self.menufolder_combo.set_sensitive(False)
            self.submenu_combo.set_sensitive(False)
            self.visible_ck.set_sensitive(False)
            self.active_ck.set_sensitive(False)
            self.but_edit.set_sensitive(True)
            return 1
        '''end of file checking'''

    def save(self):
        '''save edited file and quite'''
        if self.make_filenames() == 1:
            return  # invalid file name
        if self.active_ck.get_active():
            outfile = self.writefile
            if os.path.isfile(self.inactive):
                os.unlink(self.inactive)

        else:
            outfile = self.inactive
            if os.path.isfile(self.writefile):
                os.unlink(self.writefile)

        outf = open (outfile, 'w')
        with open (self.tempfile, 'rt') as ifile:
            for nextline in ifile:
                if "Categories" == nextline.split('=')[0]:
                    if not self.cat_change:
                        outf.write(nextline)
                elif "NoDisplay" == nextline.split('=')[0]:
                    # remove this line
                    continue
                else:
                    outf.write(nextline)

        if self.cat_change:
            outf.write("Categories="+str(self.categories)+"\n")  # for some reason strings need an end line
        if not self.visible_ck.get_active():
            outf.write("NoDisplay=true\n")
        outf.write("")  # file needs a blank line at the bottom
        outf.close()

    '''Functions for all the gui controls'''
    def filename_cb(self, widget):
        '''deal with a changed/entered file name
            reset the world...
        '''
        if os.path.isfile(self.tempfile):
            os.unlink(self.tempfile)

        self.inputstr = ""
        self.deskfile = ""
        self.writefile = ""
        self.inactive = ""
        self.categories = "none"
        self.cat_change = False
        self.in_system = False
        self.in_local = False
        self.item_ok = False
        self.item_active = False
        self.temp_copied = ""
        self.menufolder_combo.set_sensitive(False)
        self.menufolder_combo.set_active_id("none")
        self.submenu_combo.set_sensitive(False)
        self.submenu_combo.set_active_id("none")
        self.visible_ck.set_sensitive(False)
        self.active_ck.set_sensitive(False)
        self.but_edit.set_sensitive(False)
        self.time = self.time + 1

    def local_cb(self, widget):
        ''' the user has chosen a file on the system'''
        newname = widget.get_active_id()
        widget.set_active_id("none")
        self.name_entry.set_text(newname)

    ''' next calls are extra funtionality '''
    def visible_cb(self, button):
        '''someone has changed the visible check box'''
        if self.item_ok:
            self.save()

    def active_cb(self, button):
        '''someone has changed the active check box'''
        if self.item_ok:
            self.save()

    def use_system_cb(self, widget):
        ''' should we use the system file as a starting point?'''
        return

    '''The next calls deal with catagories'''
    def menu_cb(self, widget):
        '''The menu folder has been chosen.
            the submenu needs to be setup.
        '''
        self.menufolder = str(widget.get_active_id())
        self.categories = self.menufolder
        self.cat_change = True
        self.make_submenu()
        self.submenu_combo.set_sensitive(True)
        if self.item_ok:
            self.save()

    def submenu_cb(self, widget):
        '''A submenu has been chosen.
            change the catagory to match.
        '''
        this_id = str(widget.get_active_id())
        if this_id != "None":
            self.categories = this_id
            self.cat_change = True
        if self.item_ok:
            self.save()

    def make_submenu(self):
        '''once the user has chosen a menu folder, we need to add the
        right submenus to display'''
        self.submenu_combo.get_model().clear()
        self.submenu_combo.append("None", "None")
        self.submenu_combo.set_active_id("None")
        if self.menufolder == "AudioVideoEditing;Audio;":
            self.submenu_combo.append("Audio;Utilities;", "Audio Utilities")
            self.submenu_combo.append("Audio;Effects;", "Audio Effects")
            self.submenu_combo.append("Synth;", "Instruments")
            self.submenu_combo.append("Midi;", "Midi Utilities")
            self.submenu_combo.append("Mixer;", "Mixers and Card Control")
        if self.menufolder == "Graphics;":
            self.submenu_combo.append("Graphics;Utilities;", "Graphics Utilities")
            self.submenu_combo.append("Photography;", "Photography")
    '''end categories'''

    def about_cb(self, button):
        self.about_dial.run()
        self.about_dial.hide()

    def help_cb(self, button):
        self.help_dial.run()
        self.help_dial.hide()

    def edit_cb(self, button):
        '''save a template file unless it already exists
        and start editor. Wait for editor to quit. Check if
        desktop file has changed'''
        if self.make_filenames() == 1:
            return
        if self.in_system and self.use_system_ck.get_active():
            use_system = False
            if os.path.isfile("/usr/share/applications/"+str(self.deskfile)):
                shutil.copyfile(f'/usr/share/applications/{self.deskfile}', self.tempfile)
                use_system = True

            if os.path.isfile("/usr/local/share/applications/"+str(self.deskfile)):
                if not use_system:
                    shutil.copyfile(f'/usr/local/share/applications/{self.deskfile}', self.tempfile)

        # check if file name exists in .local/share/applications
        if not self.item_ok:
            #We didn't find an existing file to use so we make a stub
            with open(self.tempfile, 'w') as f:
                f.write("[Desktop Entry]\nVersion=1.0\nType=Application\n")
        # now edit it externally
        subprocess.run(["/usr/bin/exo-desktop-item-edit", str(self.tempfile)], shell=False)
        # we should check it first to make sure it is valid
        self.check_valid()
        if self.item_ok:
            self.save()

    def exit_cb(self, button):
        '''save as window delete quit the application, cleaning up
        any template file created or file changes'''
        if os.path.isfile(self.tempfile):
            os.unlink(self.tempfile)
        Gtk.main_quit()

    def on_window_main_delete_event(self, *args):
        if os.path.isfile(self.tempfile):
            os.unlink(self.tempfile)
        Gtk.main_quit(*args)


ma = MenuAdd()
ma.window_main.show_all()

Gtk.main()
