#!/usr/bin/python3

import os.path
import shutil
import unidecode

import xdg.DesktopEntry
import xdg.Menu

import gi
gi.require_version("Gtk", "3.0")
gi.require_version("MateDesktop", "2.0")
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, GObject, Pango, MateDesktop

from plugins.execute import Execute
from plugins.filemonitor import monitor as filemonitor


class IconManager(GObject.GObject):

    __gsignals__ = {
        "changed" : (GObject.SignalFlags.RUN_LAST, None, ())
   }

    def __init__(self):
        GObject.GObject.__init__(self)
        self.icons = {}
        self.count = 0

        # Some apps don't put a default icon in the default theme folder, so we will search all themes
        # def createTheme(d):
        #     theme = Gtk.IconTheme()
        #     theme.set_custom_theme(d)
        #     return theme

        # This takes to much time and there are only a very few applications that use icons from different themes
        #self.themes = map( createTheme, [d for d in os.listdir("/usr/share/icons") if os.path.isdir(os.path.join("/usr/share/icons", d))])

        self.defaultTheme = Gtk.IconTheme.get_default()
        #defaultKdeTheme = createTheme("kde.default")

        # Setup and clean up the temp icon dir
        configDir = GLib.get_user_config_dir()
        self.iconDir = os.path.join(configDir, "mintmenu")
        if not os.path.exists(self.iconDir):
            os.makedirs(self.iconDir)
        contents = os.listdir(self.iconDir)
        for fn in contents:
            os.remove(os.path.join(self.iconDir, fn))

        self.defaultTheme.append_search_path(self.iconDir)
        # Themes with the same content as the default them aren't needed
        #self.themes = [theme for theme in self.themes if  theme.list_icons() != defaultTheme.list_icons()]

        #self.themes = [self.defaultTheme, defaultKdeTheme]
        self.themes = [self.defaultTheme]

        # Listen for changes in the themes
        # for theme in self.themes:
        #     theme.connect("changed", self.themeChanged)

    def getIcon(self, iconName, iconSize):
        if not iconName:
            return None

        try:
            iconFileName = ""
            realIconName = ""
            needTempFile = False
            #[iconWidth, iconHeight] = self.getIconSize(iconSize)
            if iconSize <= 0:
                return None

            elif os.path.isabs(iconName):
                iconFileName = iconName
                needTempFile = True
            else:
                if iconName[-4:] in [".png", ".xpm", ".svg", ".gif"]:
                    realIconName = iconName[:-4]
                else:
                    realIconName = iconName

            if iconFileName and needTempFile and os.path.exists(iconFileName):
                tmpIconName = iconFileName.replace("/", "-")
                realIconName = tmpIconName[:-4]
                if not os.path.exists(os.path.join(self.iconDir, tmpIconName)):
                    shutil.copyfile(iconFileName, os.path.join(self.iconDir, tmpIconName))
                    self.defaultTheme.append_search_path(self.iconDir)

            image = Gtk.Image.new_from_icon_name(realIconName, Gtk.IconSize.DND)
            image.set_pixel_size(iconSize)
            return image

        except Exception as e:
            print("Exception %s: %s" % (e.__class__.__name__, e))
            return None

    def themeChanged(self, theme):
        self.emit("changed")

GObject.type_register(IconManager)

class easyButton(Gtk.Button):

    def __init__(self, iconName, iconSize, labels=None, buttonWidth=-1, buttonHeight=-1, ellipsis=True):
        GObject.GObject.__init__(self)
        self.connections = []
        self.iconName = iconName
        self.iconSize = iconSize
        self.showIcon = True
        self.ellipsis = ellipsis

        self.set_relief(Gtk.ReliefStyle.NONE)
        self.set_size_request(buttonWidth, buttonHeight)

        Align1 = Gtk.Alignment.new(0, 0.5, 1.0, 0)
        HBox1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        self.labelBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)

        self.buttonImage = self.getIcon(self.iconSize)
        if not self.buttonImage:
            self.buttonImage = Gtk.Image()
            self.buttonImage.set_size_request(self.iconSize, self.iconSize)

        self.image_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        self.image_box.pack_start(self.buttonImage, False, False, 5)
        self.image_box.show_all()
        HBox1.pack_start(self.image_box, False, False, 0)

        if labels:
            for label in labels:
                if isinstance(label, str):
                    self.addLabel(label)
                elif isinstance(label, list):
                    self.addLabel(label[0], label[1])

        self.labelBox.show()
        HBox1.pack_start(self.labelBox , True, True, 0)
        HBox1.show()
        Align1.add(HBox1)
        Align1.show()
        self.add(Align1)

        self.connectSelf("destroy", self.onDestroy)
        self.connect("released", self.onRelease)
        # Reload icons when the theme changed
        # self.themeChangedHandlerId = iconManager.connect("changed", self.themeChanged)

    def connectSelf(self, event, callback):
        self.connections.append(self.connect(event, callback))

    def onRelease(self, widget):
        widget.get_style_context().set_state(Gtk.StateFlags.NORMAL)

    def onDestroy(self, widget):
        self.buttonImage.clear()
        # iconManager.disconnect(self.themeChangedHandlerId)
        for connection in self.connections:
            self.disconnect(connection)
        del self.connections

    def addLabel(self, text, styles = None):
        label = Gtk.Label()
        if "<b>" in text or "<span" in text:
            label.set_markup(text.replace('&', '&amp;')) # don't remove our pango
        else:
            label.set_text(text)

        if styles:
            labelStyle = Pango.AttrList()
            for attr in styles:
                labelStyle.insert(attr)
            label.set_attributes(labelStyle)

        if self.ellipsis:
            label.set_ellipsize(Pango.EllipsizeMode.END)
        label.set_alignment(0.0, 1.0)
        label.set_max_width_chars(0)
        label.show()
        self.labelBox.pack_start(label , True, True, 0)

    def getIcon(self, iconSize):
        if not self.iconName:
            return None
        icon = iconManager.getIcon(self.iconName, iconSize)
        return icon

    def setIcon(self, iconName):
        self.iconName = iconName
        self.iconChanged()

    # IconTheme changed, setup new button icons
    def themeChanged(self, theme):
        self.iconChanged()

    def iconChanged(self):
        icon = self.getIcon(self.iconSize)
        self.buttonImage.destroy()
        if icon:
            self.buttonImage = icon
            self.image_box.pack_start(self.buttonImage, False, False, 5)
            self.image_box.show_all()
        else:
            #[iW, iH] = iconManager.getIconSize(self.iconSize)
            self.buttonImage.set_size_request(self.iconSize, self.iconSize )

    def setIconSize(self, size):
        self.iconSize = size
        icon = self.getIcon(self.iconSize)
        self.buttonImage.destroy()
        if icon:
            self.buttonImage = icon
            self.image_box.pack_start(self.buttonImage, False, False, 5)
            self.image_box.show_all()
        elif self.iconSize:
            #[iW, iH] = iconManager.getIconSize(self.iconSize)
            self.buttonImage.set_size_request(self.iconSize, self.iconSize )

class ApplicationLauncher(easyButton):

    def __init__(self, desktopFile, iconSize):

        if isinstance(desktopFile, xdg.Menu.MenuEntry):
            desktopItem = desktopFile.DesktopEntry
            desktopFile = desktopItem.filename
            self.appDirs = desktopFile.AppDirs
        elif isinstance(desktopFile, xdg.Menu.DesktopEntry):
            desktopItem = desktopFile
            desktopFile = desktopItem.filename
            self.appDirs = [os.path.dirname(desktopItem.filename)]
        else:
            desktopItem = xdg.DesktopEntry.DesktopEntry(desktopFile)
            self.appDirs = [os.path.dirname(desktopFile)]

        self.desktopFile = desktopFile
        self.startupMonitorId = 0

        self.loadDesktopEntry(desktopItem)

        self.desktopEntryMonitors = []

        base = os.path.basename(self.desktopFile)
        for directory in self.appDirs:
            self.desktopEntryMonitors.append(filemonitor.addMonitor(os.path.join(directory, base) , self.desktopEntryFileChangedCallback))

        easyButton.__init__(self, self.appIconName, iconSize)
        self.setupLabels()

        # Drag and Drop
        self.connectSelf("drag-data-get", self.dragDataGet)

        targets = (Gtk.TargetEntry.new("text/plain", 0, 100), Gtk.TargetEntry.new("text/uri-list", 0, 101))
        self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY)
        self.drag_source_set_icon_name(self.iconName)

        self.connectSelf("focus-in-event", self.onFocusIn)
        self.connectSelf("focus-out-event", self.onFocusOut)
        self.connectSelf("clicked", self.execute)

    def loadDesktopEntry(self, desktopItem):
        try:
            self.appName = desktopItem.getName()
            self.appGenericName = desktopItem.getGenericName()
            self.appComment = desktopItem.getComment()
            self.appExec = desktopItem.getExec().replace('\\\\', '\\')
            self.appIconName = desktopItem.getIcon()
            self.appCategories = desktopItem.getCategories()
            self.appMateDocPath = desktopItem.get("X-MATE-DocPath") or ""
            self.useTerminal = desktopItem.getTerminal()
            self.appPath = desktopItem.getPath()
            self.prefersOffload = desktopItem.get("PrefersNonDefaultGPU", "Desktop Entry", type="boolean")

            if not self.appMateDocPath:
                self.appKdeDocPath      = desktopItem.getDocPath() or ""

            self.appName            = self.appName.strip()
            self.appGenericName     = self.appGenericName.strip()
            self.appComment         = self.appComment.strip()

            basename = os.path.basename(self.desktopFile)
            self.startupFilePath = os.path.join(os.path.expanduser("~"), ".config/autostart", basename)
            if self.startupMonitorId:
                filemonitor.removeMonitor(self.startupMonitorId )
            if os.path.exists(self.startupFilePath):
                self.startupMonitorId = filemonitor.addMonitor(self.startupFilePath, self.startupFileChanged)

        except Exception as e:
            print(e)
            self.appName            = ""
            self.appGenericName     = ""
            self.appComment         = ""
            self.appExec            = ""
            self.appIconName        = ""
            self.appCategories      = ""
            self.appDocPath         = ""
            self.startupMonitorId   = 0

    def onFocusIn(self, widget, event):
        self.set_relief(Gtk.ReliefStyle.HALF)

    def onFocusOut(self, widget, event):
        self.set_relief(Gtk.ReliefStyle.NONE)

    def setupLabels(self):
        self.addLabel(self.appName)

    def filterText(self, text):
        keywords = self.strip_case_and_accents(text).split()
        appName = self.strip_case_and_accents(self.appName)
        appGenericName = self.strip_case_and_accents(self.appGenericName)
        appComment = self.strip_case_and_accents(self.appComment)
        appExec = self.strip_case_and_accents(self.appExec)
        for keyword in keywords:
            if keyword != "" and appName.find(keyword) == -1 and appGenericName.find(keyword) == -1 and appComment.find(keyword) == -1 and appExec.find(keyword) == -1:
                self.hide()
                return False
        self.show()
        return True

    def strip_case_and_accents(self, string):
        if isinstance(string, str):
            try:
                value = unidecode.unidecode(string.lower())
            except:
                pass
        return value

    def getTooltip(self):
        tooltip = self.appName
        if self.appComment != "" and self.appComment != self.appName:
            tooltip = tooltip + "\n" + self.appComment

        return tooltip

    def dragDataGet(self, widget, context, selection, targetType, eventTime):
        if targetType == 100: # text/plain
            selection.set_text("'" + self.desktopFile + "'", -1)
        elif targetType == 101: # text/uri-list
            if self.desktopFile[0:7] == "file://":
                selection.set_uris([self.desktopFile])
            else:
                selection.set_uris(["file://" + self.desktopFile])

    def execute(self, *args, **kwargs):
        if self.appExec:
            if self.useTerminal:
                cmd = "x-terminal-emulator -e \"" + self.appExec + "\""
                if os.path.exists("/usr/bin/mate-terminal"):
                    cmd = "mate-terminal -e \"" + self.appExec + "\""
                Execute(cmd, self.appPath)
            else:
                offload = False

                try:
                    offload = kwargs["offload"]
                except KeyError:
                    pass

                if self.prefersOffload:
                    offload = True

                Execute(None, desktopFile=self.desktopFile, offload=offload)

    def uninstall(self, *args):
        Execute("mint-remove-application " + self.desktopFile)

    # IconTheme changed, setup new icons for button and drag 'n drop
    def iconChanged(self):
        easyButton.iconChanged(self)

        icon = self.getIcon(Gtk.IconSize.DND)
        if icon:
            iconName, size = icon.get_icon_name()
            self.drag_source_set_icon_name(iconName)

    def startupFileChanged(self, *args):
        self.inStartup = os.path.exists(self.startupFilePath)

    def addToStartup(self):
        startupDir = os.path.join(os.path.dirname(self.startupFilePath))
        if not os.path.exists(startupDir):
            os.makedirs(startupDir)

        shutil.copyfile(self.desktopFile, self.startupFilePath)

        # Remove %u, etc. from Exec entry, because MATE will not replace them when it starts the app
        item = MateDesktop.DesktopItem.new_from_uri(self.startupFilePath, MateDesktop.DesktopItemLoadFlags.ONLY_IF_EXISTS)
        if item:
            r = re.compile("%[A-Za-z]")
            tmp = r.sub("", item.get_string(MateDesktop.DESKTOP_ITEM_EXEC)).strip()
            item.set_string(MateDesktop.DESKTOP_ITEM_EXEC, tmp)
            item.save(self.startupFilePath, 0)

    def removeFromStartup(self):
        if os.path.exists(self.startupFilePath):
            os.remove(self.startupFilePath)

    def removeFromFavourites(self):
        if os.path.exists(self.favouritesFilePath):
            os.remove(self.favouritesFilePath)

    def isInStartup(self):
        #return self.inStartup
        return os.path.exists(self.startupFilePath)

    def onDestroy(self, widget):
        easyButton.onDestroy(self, widget)
        if self.startupMonitorId:
            filemonitor.removeMonitor(self.startupMonitorId)
        for id in self.desktopEntryMonitors:
            filemonitor.removeMonitor(id)

    def desktopEntryFileChangedCallback(self):
        GLib.timeout_add(200, self.onDesktopEntryFileChanged)

    def onDesktopEntryFileChanged(self):
        exists = False
        base = os.path.basename(self.desktopFile)
        for directory in self.appDirs:
            if os.path.exists(os.path.join(directory, base)):
                # print os.path.join(dir, base), self.desktopFile
                self.loadDesktopEntry(xdg.DesktopEntry.DesktopEntry(os.path.join(directory, base)))
                for child in self.labelBox:
                    child.destroy()

                self.iconName = self.appIconName

                self.setupLabels()
                self.iconChanged()
                exists = True
                break

        if not exists:
            # FIXME: What to do in this case?
            self.destroy()
        return False

class MenuApplicationLauncher(ApplicationLauncher):

    def __init__(self, desktopFile, iconSize, category, showComment, highlight=False):

        self.showComment = showComment
        self.appCategory = category
        self.highlight = highlight

        ApplicationLauncher.__init__(self, desktopFile, iconSize)

    def filterCategory(self, category):
        if self.appCategory == category or category == "":
            self.show()
        else:
            self.hide()

    def setupLabels(self):
        appName = self.appName
        appComment = self.appComment
        if self.highlight:
            try:
                appName = "<b>%s</b>" % appName
                appComment = "<b>%s</b>" % appComment
            except Exception as e:
                print(e)

        if self.showComment and self.appComment != "":
            if self.iconSize <= 2:
                self.addLabel('<span size="small">%s</span>\n<span size="x-small">%s</span>' %
                    (appName, appComment))
            else:
                self.addLabel('%s\n<span size="small">%s</span>' %
                    (appName, appComment))
        else:
            self.addLabel(appName)

    def execute(self, *args, **kwargs):
        self.highlight = False
        for child in self.labelBox:
            child.destroy()
        self.setupLabels()
        return super(MenuApplicationLauncher, self).execute(*args, **kwargs)

    def setShowComment(self, showComment):
        self.showComment = showComment
        for child in self.labelBox:
            child.destroy()
        self.setupLabels()

class FavApplicationLauncher(ApplicationLauncher):

    def __init__(self, desktopFile, iconSize, swapGeneric = False):

        self.swapGeneric = swapGeneric

        ApplicationLauncher.__init__(self, desktopFile, iconSize)

    def setupLabels(self):
        if self.appGenericName:
            if self.swapGeneric:
                self.addLabel('<span weight="bold">%s</span>' % self.appName)
                self.addLabel(self.appGenericName)
            else:
                self.addLabel('<span weight="bold">%s</span>' % self.appGenericName)
                self.addLabel(self.appName)
        else:
            self.addLabel('<span weight="bold">%s</span>' % self.appName)
            if self.appComment != "":
                self.addLabel(self.appComment)
            else:
                self.addLabel("")

    def setSwapGeneric(self, swapGeneric):
        self.swapGeneric = swapGeneric
        for child in self.labelBox:
            child.destroy()

        self.setupLabels()

class CategoryButton(easyButton):

    def __init__(self, iconName, iconSize, labels , f):
        easyButton.__init__(self, iconName, iconSize, labels, ellipsis=False)
        self.filter = f

iconManager = IconManager()
