#!/usr/bin/python3

'''
@author: raptor

One script to rule them all...

-------
USAGE
-------

If bitfighter is installed on a custom path, include it as an argument.
e.g.
bitfighter_notifier.py C:\my\custom\path\bitfighter.exe


Platform-specific notes:

-------
WINDOWS
-------

You need the following packages to run this:
 - pywin32 - http://sourceforge.net/projects/pywin32/files/pywin32/
   Note that it is important to get the correct version -- see README.txt in the pywin32 downloads section for details

 - systray - http://sourceforge.net/projects/pysystray/files/pysystray/

 This is known to work with Python 2.7


-------
OSX
-------

Works on OSX 10.6+, but better on 10.8+ where we can take advantage of the
built-in notification system.

It may work on OSX 10.5 or 10.4 if PyOBJC is installed


-------
LINUX
-------

The Linux part will attempt to use one of the following as the GUI:
 - QT
 - GTK2 through PyGTK
 - GTK3 (also maybe GTK2) through PyGObject

It will also attempt to use one of the following for the messaging system:
 - Notify2
 - DBus
 - notify-send command

The precedence order of which to check first can be changed


'''


"""
Common Imports
"""
import functools
import inspect
import json
import logging
import os
import subprocess
import sys
import threading
import tempfile   # For our singleton class


"""
Configuration
"""
APPLICATION_NAME        = "Bitfighter Notifier Applet"
GUI_TITLE               = "Bitfighter"
MESSAGE_TITLE           = "Bitfighter"
URL                     = "http://bitfighter.org/bitfighterStatus.json"
REFRESH_INTERVAL        = 20
NOTIFICATION_TIMEOUT    = 5
EXECUTABLE              = "bitfighter"

# Different icon formats for different systems
if sys.platform == 'win32':
    ICON_FILE           = "bitfighter_win_icon_green.ico"

elif sys.platform == 'darwin':
    ICON_FILE           = "redship18.png"

else:
    ICON_FILE           = "bitfighter.png"


# Adjust this variable for Linux distros
ICON_BASE = "/usr/share/pixmaps/"
ICON_PATH = ICON_BASE + ICON_FILE


loggingLevel = logging.ERROR


'''
Developer specific overrides
'''
# For testing and debugging...
devMode = False

if devMode == True:
    REFRESH_INTERVAL = 5
    loggingLevel = logging.DEBUG


"""
Logging
"""
logging.basicConfig(level=loggingLevel)


"""
Singleton class

This prevents multiple notifiers from running.  Taken from:
   https://github.com/pycontribs/tendo/blob/master/tendo/singleton.py
"""
class SingleInstance:
    def __init__(self, flavor_id=""):
        self.initialized = False
        basename = os.path.splitext(os.path.abspath(sys.argv[0]))[0].replace("/", "-").replace(":", "").replace("\\", "-") + '-%s' % flavor_id + '.lock'
        # os.path.splitext(os.path.abspath(sys.modules['__main__'].__file__))[0].replace("/", "-").replace(":", "").replace("\\", "-") + '-%s' % flavor_id + '.lock'
        self.lockfile = os.path.normpath(tempfile.gettempdir() + '/' + basename)

        logging.debug("SingleInstance lockfile: " + self.lockfile)
        if sys.platform == 'win32':
            try:
                # file already exists, we try to remove (in case previous execution was interrupted)
                if os.path.exists(self.lockfile):
                    os.unlink(self.lockfile)
                self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
            except OSError:
                type, e, tb = sys.exc_info()
                if e.errno == 13:
                    logging.error("Another instance is already running, quitting.")
                    sys.exit(-1)
                print(e.errno)
                raise
        else:  # non Windows
            import fcntl
            self.fp = open(self.lockfile, 'w')
            try:
                fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
            except IOError:
                logging.warning("Another instance is already running, quitting.")
                sys.exit(-1)
        self.initialized = True

    def __del__(self):
        import sys
        import os
        if not self.initialized:
            return
        try:
            if sys.platform == 'win32':
                if hasattr(self, 'fd'):
                    os.close(self.fd)
                    os.unlink(self.lockfile)
            else:
                import fcntl
                fcntl.lockf(self.fp, fcntl.LOCK_UN)
                # os.close(self.fp)
                if os.path.isfile(self.lockfile):
                    os.unlink(self.lockfile)
        except Exception as e:
            if logging:
                logging.warning(e)
            else:
                print("Unloggable error: %s" % e)
            sys.exit(-1)

# Use our singleton
me = SingleInstance()


"""
Utility classes
"""
# Adapted from a random pastebin on the web: http://pastebin.com/1VcumfC3
class PeriodicTimer(object):
    def __init__(self, interval, callback):
        self.interval = interval

        @functools.wraps(callback)
        def wrapper(*args, **kwargs):
            # Do the action!
            result = callback(*args, **kwargs)

            if result:
                # Restart a Timer to call this same action again
                self.thread = threading.Timer(self.interval, self.callback)
                self.thread.start()

        self.callback = wrapper

    def start(self):
        self.thread = threading.Timer(self.interval, self.callback)
        self.thread.start()

    def cancel(self):
        self.thread.cancel()


'''
Base Classes

These classes are shared across all platforms
'''

"""
PlayerListReceiver

Downloads and parses the online JSON
"""
class PlayersListReceiver(object):
    def __init__(self, url, messenger, guiApp):
        self.players = set()
        self.url = url
        self.messenger = messenger
        self.guiApp = guiApp
        self.firstRun = True
        self.hasConnectivity = True


    def fetch(self):
        # Python version 3 uses this
        if sys.version_info >= (3, 0):
            import urllib.request
            fp = urllib.request.urlopen(self.url)
            bytesInf = fp.read()
            strInf = bytesInf.decode("utf8")
            fp.close()
            return strInf
        # Python 2.x
        else:
            import urllib2
            response = urllib2.urlopen(self.url)
            return response.read()


    # This method is run in a separate thread and call
    def refresh(self):
        logging.debug("Refreshing JSON")

        try:
            gameInf = json.loads(self.fetch(), strict=False)
            self.hasConnectivity = True
        except:
            logging.debug("Unable to fetch data from {0}".format(self.url))

            # We just lost internet connectivity
            if self.hasConnectivity == True:
                # Empty player set
                self.players = set()
                self.guiApp.refreshToolTip(self.players)


            self.hasConnectivity = False

            # Continue anyways - we may have lost connectivity for only a short while
            return True


        playersNew = set(gameInf["players"])

        if playersNew != self.players:
            # Determine differences
            comein = playersNew.difference(self.players)
            goout = self.players.difference(playersNew)

            # Set our new list
            self.players = playersNew

            # Send a message and update our tooltip
            self.messenger.notify(self.messenger.makeMessage(comein, goout))
            self.guiApp.refreshToolTip(self.players)

        # Guarantee a tooltip if no users are online
        if self.firstRun == True:
            self.guiApp.refreshToolTip(self.players)
            self.firstRun = False


        # Must return true to continue the periodic timer
        return True;


"""
GuiApplicationBase

This handles all GUI (system tray) related functions and set up.
"""
class GuiApplicationBase(object):
    def __init__(self, iconPath):
        self.iconPath = iconPath

        self.title = GUI_TITLE
        self.timer = None


    # Override these methods in sub-classes
    def refreshToolTip(self, players):
        logging.warn("Override me: %s", inspect.stack()[0][3])


    # *args is needed here for some callback calls (like with gtk+)
    def launchExecutable(self, *args):
        logging.warn("Override me: %s", inspect.stack()[0][3])


    # Common methods
    def setTimer(self, timer):
        self.timer = timer


    def formTooltip(self, players):
        if len(players) > 0:
            verb = 'is'
            if len(players) > 1:
                verb = 'are'

            return "{0}\n{1} online".format(", ".join(players), verb)
        else:
            return "Nobody is online"


"""
MessengerBase

This sends a message to the GUI.
"""
class MessengerBase(object):
    def __init__(self, timeout):
        self.timeout = timeout
        self.title = MESSAGE_TITLE

    # Common methods
    def notify(self, message):
        logging.warn("Override me: %s", inspect.stack()[0][3])


    def makeMessage(self, comein, goout):
        verbIn = verbOut = 'has'

        if len(comein) > 1:
            verbIn = 'have'
        if len(goout) > 1:
            verbOut = 'have'

        if len(comein) and len(goout):
            body="{0} {1} joined\n{2} {3} left".format(", ".join(comein),verbIn, ", ".join(goout), verbOut)
        elif len(comein):
            body="{0} {1} joined".format(", ".join(comein), verbIn)
        elif len(goout):
            body="{0} {1} left".format(", ".join(goout), verbOut)
        return body


"""
NotifierBase

This is main entry point for each port (windows, linux, osx, etc.).
"""
class NotifierBase(object):
    def __init__(self):
        self.url = URL
        self.iconPath = ICON_PATH
        self.appName = APPLICATION_NAME
        self.notificationTimeout = NOTIFICATION_TIMEOUT
        self.refreshInterval = REFRESH_INTERVAL
        self.executable = EXECUTABLE

        self.messenger = None
        self.guiApp = None


    def on_start(self):
        if self.messenger == None:
            logging.error("messenger not set up?")
            return

        self.messenger.notify("Bitfighter Notifier Started")


    # Set up timers
    def initialize(self, messenger, guiApp):
        self.messenger = messenger
        self.guiApp = guiApp

        plr = PlayersListReceiver(self.url, self.messenger, self.guiApp)

        timer = PeriodicTimer(self.refreshInterval, plr.refresh)
        timer.start()

        self.guiApp.setTimer(timer)

        # One time timer to notify user we've started up after 1 second
        startTimer = threading.Timer(1, self.on_start)
        startTimer.start()


    def run(self):
        logging.warn("Override me: %s", inspect.stack()[0][3])


'''
Platform-specific implementation classes
'''

# Start Windows
if sys.platform == 'win32':
    from systray import systray


    class MessengerWindows(MessengerBase):
        def __init__(self, timeout, guiApp):
            super(MessengerWindows, self).__init__(timeout)

            self.guiApp = guiApp


        def notify(self, message):
            self.guiApp.trayapp.systray.show_message(message)


    class GuiApplicationWindows(GuiApplicationBase):
        def __init__(self, executable, iconPath):
            super(GuiApplicationWindows, self).__init__(iconPath)

            self.executable = executable
            self.trayapp = None


        def getNotificationIcon(self):
            try:
                return None
            except:
                return None


        def launchExecutable(self, *args):
            try:
                subprocess.Popen(self.executable, shell=False)
                logging.debug("Running Bitfighter with path " + self.executable)
            except:
                logging.warning("Unable to run {0}, trying default path".format(self.executable))
                logging.debug("Attempting default installation path")

                if os.path.exists(os.path.join(os.path.expandvars("%ProgramFiles%"), "bitfighter", "bitfighter.exe")):
                    logging.debug("Attempting " + os.path.join(os.path.expandvars("%ProgramFiles%"), "bitfighter", "bitfighter.exe"))
                    subprocess.Popen(os.path.join(os.path.expandvars("%ProgramFiles%"), "bitfighter", "bitfighter.exe"), shell=True)
                elif os.path.exists(os.path.join(os.path.expandvars("%ProgramFiles(x86)%"), "bitfighter", "bitfighter.exe")):
                    logging.debug("Attempting " + os.path.join(os.path.expandvars("%ProgramFiles(x86)%"), "bitfighter", "bitfighter.exe"))
                    subprocess.Popen(os.path.join(os.path.expandvars("%ProgramFiles(x86)%"), "bitfighter", "bitfighter.exe"), shell=True)
                else:
                    logging.error("Unable to find {0} in default paths".format(self.executable))


        def rightClickEvent(self, icon, button, time):
            pass


        def refreshToolTip(self, players):
            if self.trayapp is None:
                return

            self.trayapp.systray.set_status("\n" + self.formTooltip(players))


        def onQuit(self, _systrayApp):
            if self.timer:
                self.timer.cancel()


        def run(self):
            self.trayapp = systray.App(self.title, self.iconPath)
            self.trayapp.on_quit = self.onQuit
            self.trayapp.on_double_click = self.launchExecutable

            self.trayapp.start()


    class NotifierWindows(NotifierBase):
        def __init__(self):
            super(NotifierWindows, self).__init__()
            logging.debug("Windows!")


        def run(self):
            guiApp = GuiApplicationWindows(self.executable, self.iconPath)
            messenger = MessengerWindows(self.notificationTimeout, guiApp)

            # Set up timers
            self.initialize(messenger, guiApp)

            guiApp.run()

# End Windows


# Start OSX
elif sys.platform == 'darwin':
    import objc
    from Foundation import *
    from AppKit import *
    from PyObjCTools import AppHelper
    import platform


    """
    This uses a thread-blocking NSAlert.  If the alert is up, the application
    does not further update the online status
    """
    class LegacyAlert(object):
        def __init__(self, messageText):
            super(LegacyAlert, self).__init__()
            self.messageText = messageText
            self.informativeText = ""


        def closeAlert(self):
            NSApp.abortModal()


        def displayAlert(self):
            # Create timer to shut down alert after some seconds
            selector = objc.selector(self.closeAlert,signature='v@:')
            timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(NOTIFICATION_TIMEOUT, self, selector, None, False)

            # Add timer to main run loop
            NSRunLoop.mainRunLoop().addTimer_forMode_(timer, NSModalPanelRunLoopMode)

            # Create alert
            alert = NSAlert.alloc().init()
            alert.setMessageText_(self.messageText)
            alert.setInformativeText_(self.informativeText)
            alert.setAlertStyle_(NSInformationalAlertStyle)
            alert.addButtonWithTitle_("OK")
            NSApp.activateIgnoringOtherApps_(True)

            # Run the alert
            alert.runModal()

            timer.invalidate()


    class MessengerOSXLegacy(NSObject, MessengerBase):
        def __init__(self, timeout):
            MessengerBase.__init__(timeout)


        def notify(self, message):
            pool = NSAutoreleasePool.alloc().init()

            ap = LegacyAlert(self.title)
            ap.informativeText = message
            ap.displayAlert()

            del pool

        def setMembers(self, timeout, title):
            self.timeout = timeout
            self.title = title


    class MessengerOSXMountainLion(NSObject, MessengerBase):
        def __init__(self, timeout):
            MessengerBase.__init__(timeout)


        def notify(self, message):
            # We need to set up our own autorelease pool because this happens outside of the main
            # thread where there is no pool
            pool = NSAutoreleasePool.alloc().init()

            NSUserNotification = objc.lookUpClass('NSUserNotification')
            NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
            notification = NSUserNotification.alloc().init()
            notification.setTitle_(self.title)
            notification.setInformativeText_(message)
            NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
            NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)

            del pool


        def setMembers(self, timeout, title):
            self.timeout = timeout
            self.title = title


    class GuiApplicationOSX(NSObject, GuiApplicationBase):
        statusbar = None
        state = 'idle'
        statusitem = None


        def setMembers(self, executable, iconPath):
            self.iconPath = iconPath


        def setTimer(self, timer):
            self.timer = timer


        def applicationDidFinishLaunching_(self, notification):
            statusbar = NSStatusBar.systemStatusBar()
            self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)

            try:
                self.statusitem.setImage_(NSImage.alloc().initByReferencingFile_(self.iconPath))
            except:
                logging.exception("Unable to load icon, use label instead")
                self.statusitem.setTitle_(self.title)

            self.statusitem.setHighlightMode_(1)

            # Build a very simple menu
            self.menu = NSMenu.alloc().init()

            # Default event
            menuitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Exit', 'terminate:', '')
            self.menu.addItem_(menuitem)

            # Bind it to the status item
            self.statusitem.setMenu_(self.menu)

            # FIXME: We need to bind the self.timer.cancel() method to a quit callback
            # OSX seems to kill thread anyways...  so..  maybe not needed?


        def refreshToolTip(self, players):
            if self.statusitem is None:
                return

            pool = NSAutoreleasePool.alloc().init()

            self.statusitem.setToolTip_(self.formTooltip(players))

            del pool


    class NotifierOSX(NotifierBase):
        def __init__(self):
            super(NotifierOSX, self).__init__()
            logging.debug("OSX!")


        def run(self):

            osxVersion, _, _ = platform.mac_ver()
            majorVersion = int(osxVersion.split('.')[0])
            minorVersion = int(osxVersion.split('.')[1])

            logging.debug("major: %s; minor: %s", majorVersion, minorVersion)

            app = NSApplication.sharedApplication()
            guiApp = GuiApplicationOSX.alloc().init()
            app.setDelegate_(guiApp)

            guiApp.setMembers(self.executable, self.iconPath)

            messenger = None

            # OSX 10.8 and greater has a built-in notification system we can take advantage of
            if minorVersion >= 8:
                messenger = MessengerOSXMountainLion.alloc().init()
            else:
                messenger = MessengerOSXLegacy.alloc().init()

            # Multiple inheritance not working with NSObject??
            messenger.setMembers(self.notificationTimeout, MESSAGE_TITLE)


            self.initialize(messenger, guiApp)

            AppHelper.runEventLoop()

# End OSX


# Start Linux
elif sys.platform.startswith('linux'): # or sys.platform.startswith('freebsd'):

    # The hard part. check the runtime environment
    isQtGui = False
    isGIGtk = False
    isPyGtk = False

    isNotify2 = False
    isDBus = False
    isCmdNotifySend = False


    # detect GUI
    def checkQtGui():
        global QApplication, QSystemTrayIcon, QMenu, QIcon, QImage, QObject, SIGNAL, isQtGui
        try:
            from PyQt4.QtGui import QApplication, QSystemTrayIcon, QMenu, QIcon, QImage
            from PyQt4.QtCore import QObject, SIGNAL
            isQtGui = True
        except:
            pass
        return isQtGui


    def checkGIGtk():
        global gtk, gobject, GdkPixbuf, isGIGtk
        try:
            from gi.repository import Gtk as gtk
            from gi.repository import GObject as gobject
            from gi.repository import GdkPixbuf
            isGIGtk = True
        except:
            pass
        return isGIGtk


    def checkPyGtk():
        global gtk, gobject, isPyGtk
        try:
            import pygtk
            import gtk
            import gobject
            isPyGtk = True
        except:
            pass
        return isPyGtk


    # Detect notification engine
    def checkNotify2():
        global notify2, isNotify2
        try:
            import notify2
            isNotify2 = True
        except:
            pass
        return isNotify2


    def checkDBus():
        global dbus, isDBus
        try:
            import dbus
            isDBus = True
        except:
            pass
        return isDBus


    def checkCmdNotifySend():
        global isCmdNotifySend
        try:
            isCmdNotifySend = subprocess.call(["type", "notify-send"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) == 0
        finally:
            return isCmdNotifySend


    # XXX Change order here for precedence
    guiChecks = [checkQtGui, checkPyGtk, checkGIGtk]
    engineChecks = [checkNotify2, checkDBus, checkCmdNotifySend]


    for check in guiChecks:
        if check():
            logging.info("using: %s", check.__name__)
            break
    else:
        logging.error("No suitable GUI toolkit found. Install either PyQt4 or GTK+ bindings for Python")
        exit(1)

    for check in engineChecks:
        if check():
            logging.info("using: %s", check.__name__)
            break
    else:
        logging.error("No suitable notification interface found. Install Python library notify2 or Python bindings to dbus or notify-send command")
        exit(1)


    # =============================================================
    # Set up GUI classes depending on what was detected

    if isQtGui:
        class GuiApplicationLinux(GuiApplicationBase):
            def __init__(self, executable, iconPath, parent=None):
                super(GuiApplicationLinux, self).__init__(iconPath)

                self.eventLoop = 'qt'
                self.app = QApplication(sys.argv) # this should be done before anything else
                self.executable = executable

                if QIcon.hasThemeIcon(iconPath):
                    icon = QIcon.fromTheme(iconPath)
                else:
                    icon = QIcon(iconPath)

                self.statusIcon = QSystemTrayIcon(icon, parent)
                self.menu = QMenu(parent)

                exitAction = self.menu.addAction("Exit")
                exitAction.triggered.connect(self.quit)

                self.statusIcon.setContextMenu(self.menu)

                def activate(reason):
                    if reason == QSystemTrayIcon.Trigger:
                        return self.launchExecutable()

                QObject.connect(self.statusIcon,
                    SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), activate)

                self.statusIcon.show()


            # A portable icon wrapper. Notify2 demands icon class to be compatible with GdkPixbuf so we
            # provide a compatibility layer
            class IconWrapper(object):
                def __init__(self, iconName):
                    if QIcon.hasThemeIcon(iconName):
                        icon = QIcon.fromTheme(iconName)
                    else:
                        icon = QIcon(iconName)
                    size = icon.availableSizes()[0]
                    self.image = icon.pixmap(size).toImage().convertToFormat(QImage.Format_ARGB32)
                    self.image = self.image.rgbSwapped() # otherwise colors are weird :/

                def get_width(self):
                    return self.image.width()

                def get_height(self):
                    return self.image.height()

                def get_rowstride(self):
                    return self.image.bytesPerLine()

                def get_has_alpha(self):
                    return self.image.hasAlphaChannel()

                def get_bits_per_sample(self):
                    return self.image.depth() // self.get_n_channels()

                def get_n_channels(self):
                    if self.image.isGrayscale():
                        return 1
                    elif self.image.hasAlphaChannel():
                        return 4
                    else:
                        return 3;

                def get_pixels(self):
                    return self.image.bits().asstring(self.image.numBytes())
            # end of wrapper class


            def getNotificationIcon(self):
                try:
                    return self.IconWrapper(self.iconPath)
                except:
                    logging.error("Failed to get notification icon")
                    return None


            def refreshToolTip(self, players):
                self.statusIcon.setToolTip(self.formTooltip(players))


            def launchExecutable(self, *args):
                try:
                    subprocess.Popen(self.executable, shell=True)
                except:
                    logging.error("Unable to run {0}".format(self.cmd))


            def run(self):
                sys.exit(self.app.exec_())


            def quit(self):
                self.timer.cancel()
                sys.exit(0)


    elif isGIGtk or isPyGtk:
        class GuiApplicationLinux(GuiApplicationBase):
            def __init__(self, executable, iconPath, parent = None):
                super(GuiApplicationLinux, self).__init__(iconPath)

                self.eventLoop = 'glib'
                self.executable = executable
                self.statusIcon = gtk.StatusIcon()
                self.statusIcon.set_from_file(iconPath)
                self.statusIcon.connect("activate", self.launchExecutable)
                self.statusIcon.connect("popup-menu", self.rightClickEvent)
                self.statusIcon.set_visible(True)


            def getNotificationIcon(self):
                try:
                    if isGIGtk:
                        return GdkPixbuf.Pixbuf.new_from_file(self.iconPath)
                    else:
                        return gtk.gdk.pixbuf_new_from_file(self.iconPath)
                except:
                    return None


            def launchExecutable(self, *args):
                try:
                    subprocess.Popen(self.executable, shell=True)
                except:
                    logging.error("Unable to run {0}".format(self.cmd))


            def rightClickEvent(self, icon, button, time):
                menu = gtk.Menu()

                quitMenu = gtk.MenuItem("Exit")
                quitMenu.connect("activate", gtk.main_quit)

                menu.append(quitMenu)
                menu.show_all()

                if isGIGtk:
                    menuPosition = gtk.StatusIcon.position_menu
                    menu.popup(None, None, menuPosition, self.statusIcon, button, time)
                else:
                    menuPosition = gtk.status_icon_position_menu
                    menu.popup(None, None, menuPosition, button, time, self.statusIcon)


            def refreshToolTip(self, players):
                self.statusIcon.set_tooltip_text(self.formTooltip(players))


            def run(self):
                gobject.threads_init()   # This is needed or our python-based timer will fail
                gtk.quit_add(0, self.quit)
                gtk.main()


            def quit(self):
                self.timer.cancel()
                sys.exit(0)

    # =============================================================
    # Set up messenger classes depending on what was detected

    if isNotify2:
        class MessengerLinux(MessengerBase):
            def __init__(self, appName, timeout, guiApp):
                super(MessengerLinux, self).__init__(timeout)

                notify2.init(appName, guiApp.eventLoop)
                self.notification = None

                if guiApp.iconPath:
                    self.icon = guiApp.getNotificationIcon()


            def notify(self, message):
                if not self.notification:
                    self.notification = notify2.Notification(self.title, message)
                    if self.icon and self.icon.get_width() > 0:
                        self.notification.set_icon_from_pixbuf(self.icon)
                    self.notification.timeout = self.timeout
                else:
                    self.notification.update(self.title, message)
                self.notification.show()


    elif isDBus:
        class MessengerLinux(MessengerBase):
            def __init__(self, appName, timeout, guiApp):
                super(MessengerLinux, self).__init__(timeout)

                self.appName = appName
                # copied from pynotify2
                if guiApp.eventLoop == 'glib':
                    from dbus.mainloop.glib import DBusGMainLoop
                    self.mainloop = DBusGMainLoop()
                elif guiApp.eventLoop == 'qt':
                    from dbus.mainloop.qt import DBusQtMainLoop
                    self.mainloop = DBusQtMainLoop()

                if guiApp.iconPath:
                    self.icon = guiApp.getNotificationIcon()


            def notify(self, message):
                item = 'org.freedesktop.Notifications'
                path = '/org/freedesktop/Notifications'
                interface = 'org.freedesktop.Notifications'
                iconName = ''
                actions = []
                hints = {}
                if self.icon and self.icon.get_width() > 0:
                    struct = (
                        self.icon.get_width(),
                        self.icon.get_height(),
                        self.icon.get_rowstride(),
                        self.icon.get_has_alpha(),
                        self.icon.get_bits_per_sample(),
                        self.icon.get_n_channels(),
                        dbus.ByteArray(self.icon.get_pixels())
                    )
                    hints['icon_data'] = struct

                bus=dbus.SessionBus(self.mainloop)
                nobj = bus.get_object(item, path)
                notify = dbus.Interface(nobj, interface)
                notify.Notify(self.appName, 0, iconName, self.title, message, actions, hints, self.timeout)


    elif isCmdNotifySend:
        class MessengerLinux(MessengerBase):
            def __init__(self, appName, timeout, guiApp):
                super(MessengerLinux, self).__init__(timeout)

                self.appName = appName

                if guiApp.iconPath and os.path.exists(guiApp.iconPath):
                    self.iconPath = os.path.abspath(guiApp.iconPath) # the path must be absolute
                else:
                    self.iconPath = guiApp.iconPath


            def notify(self, message):
                args = ["notify-send", "--app-name", self.appName, "--expire-time", str(self.timeout)]

                if self.iconPath:
                    args.append("--icon")
                    args.append(self.iconPath)
                args.append(self.title)
                args.append(message)

                try:
                    subprocess.call(args,  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                except:
                    logging.exception("notify-send ({0}) call failed".format(args))


    class NotifierLinux(NotifierBase):
        def __init__(self):
            super(NotifierLinux, self).__init__()
            logging.debug("Linux!")

    #        self.guiType = None
    #        self.engineType = None


        def run(self):
    #        self.detectGui()
    #        self.detectEngine()

            guiApp = GuiApplicationLinux(self.executable, self.iconPath)
            messenger = MessengerLinux(self.appName, self.notificationTimeout, guiApp)

            self.initialize(messenger, guiApp)

            guiApp.run()


        """
        # Attempt to simplify the Linux part into classes/methods...  so far failing

        # Try to detect the GUI toolkit to use
        def detectGui(self):
            if self.guiType is None:
                global QApplication, QSystemTrayIcon, QMenu, QIcon, QImage, QTimer, QObject, SIGNAL, isQtGui
                try:
                    from PyQt4.QtGui import QApplication, QSystemTrayIcon, QMenu, QIcon, QImage
                    from PyQt4.QtCore import QTimer, QObject, SIGNAL

                    isQtGui = True
                except:
                    pass

            if self.guiType is None:
                global gtk, gobject, GdkPixbuf, isGIGtk
                try:
                    from gi.repository import Gtk as gtk
                    from gi.repository import GObject as gobject
                    from gi.repository import GdkPixbuf

                    isGIGtk = True
                except:
                    pass

            if self.guiType is None:
                global gtk, gobject, isPyGtk
                try:
                    import pygtk
                    import gtk
                    import gobject

                    isPyGtk = True
                except:
                    pass

            if self.guiType is None:
                logging.error("No GUI found.  Exiting")
                exit(1)
            else:
                logging.info("Detected GUI: %s", self.guiType)


        # Try to detect the notification engine to use
        def detectEngine(self):
            if self.engineType is None:
                global notify2, isNotify2
                try:
                    import notify2

                    isNotify2 = True
                except:
                    pass

            if self.engineType is None:
                global dbus
                try:
                    import dbus

                    self.engineType = 'dbus'
                except:
                    pass

            if self.engineType is None:
                try:
                    isCmdNotifySend = subprocess.call(["type", "notify-send"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) == 0

                    if isCmdNotifySend:
                        self.engineType = 'notify-send'
                except:
                    pass

            if self.engineType is None:
                logging.error("No notification engine found.  Exiting")
                exit(1)
            else:
                logging.info("Detected engine: %s", self.engineType)
        """


# End Linux

else:
    logging.error("This platform is not currently handled.  Join #bitfighter and complain to the developers")



"""
Main!
"""
def main():
    logging.info("starting up!")

    notifier = None
    if sys.platform == 'win32':
        notifier = NotifierWindows()
    elif sys.platform == 'darwin':
        notifier = NotifierOSX()
    elif sys.platform.startswith('linux'):
        notifier = NotifierLinux()
    else:
        logging.error("This platform is not currently handled.  Join #bitfighter on freenode and complain to the developers")

    if len(sys.argv) == 2:
        notifier.executable = sys.argv[1]
        logging.debug("Custom path supplied: " + sys.argv[1])

    if notifier != None:
        notifier.run()


if __name__ == '__main__':
    main()
