from display.Display import Display
from display.Window import Window
from display.Plug import Plug
from factory.DisplayFactory import DisplayFactory
from main import COPYRIGHT, HOME, NAME, USERHOME, VERSION, POSITIONSFILE
from main.AboutDialog import AboutDialog
from main.remotecommands import *
from main.RemoteSocket import RemoteSocket
from main.TrayIcon import TrayIcon
from utils import dialog

import gtk
import shutil
import os
import sys

try:
    import gnomevfs
except ImportError:
    import gnome.vfs as gnomevfs


# This class starts, restarts and kills Displays
class Starter:

    __DISPLAY_TYPE_WINDOW = "window"
    __DISPLAY_TYPE_PLUG = "plug"


    def __init__(self):

        # the communication socket
        self.__socket = RemoteSocket()

        # the About dialog
        self.__about_dialog = AboutDialog(os.path.join(HOME, "data"))

        # the command which gets called when the user removes a display
        self.__remove_command = ""

        # the restart command which gets called when the session restarts
        self.__startup_command = ""

        # the set of open displays as a hashtable "id -> display"
        self.__open_displays = {}
        # the paths of the display files "id -> path"
        self.__display_paths = {}
        # the saved positions of the displays "id -> (x, y)"
        self.__positions = {}

        self.__factory = DisplayFactory()

        # set the message handlers for the socket
        self.__socket.add_message_handler(COMMAND_OPEN_DISPLAY,
                                          self.__handle_open_display)
        self.__socket.add_message_handler(COMMAND_OPEN_DISPLAY_WITH_ID,
                                          self.__handle_open_display_with_id)
        self.__socket.add_message_handler(COMMAND_OPEN_PLUG,
                                          self.__handle_open_plug)
        self.__socket.add_message_handler(COMMAND_CLOSE_DISPLAY,
                                          self.__handle_close_display)
        self.__socket.add_message_handler(COMMAND_SHUTDOWN,
                                          self.__handle_shutdown)
        self.__socket.add_message_handler(COMMAND_DISPLAYS,
                                          self.__handle_displays)
        self.__socket.add_message_handler(COMMAND_DISPLAY_LIST,
                                          self.__handle_display_list)
        self.__socket.add_message_handler(COMMAND_GEOMETRY,
                                          self.__handle_geometry)
        self.__socket.add_message_handler(COMMAND_VERSION,
                                          self.__handle_version)
        self.__socket.add_message_handler(COMMAND_ABOUT,
                                          self.__handle_about)
        self.__socket.add_message_handler(COMMAND_SET_REMOVE_COMMAND,
                                          self.__handle_set_remove_command)
        self.__socket.add_message_handler(COMMAND_SET_STARTUP_COMMAND,
                                          self.__handle_set_startup_command)

        self.__load_positions()

        # socket ready, start handling requests
        self.__socket.start()

        # setup a nice systray icon
        trayicon = TrayIcon()
        trayicon.set_menu([(gtk.STOCK_PROPERTIES, _("_Manage desklets"),
                            self.__handle_manage),
                           (None, _("_View log"),
                           # (gtk.STOCK_EDIT, _("_View log"),
                            self.__handle_show_log),
                           ("gnome-stock-about", _("_About..."), self.__handle_about_dialog),
                           (),
                           (gtk.STOCK_QUIT, _("_Stop daemon"),
                            self.__handle_shutdown)])

        def save_pos():
            self.__save_positions()
            return True

        gtk.timeout_add(3000, save_pos)



    #
    # Reacts on observer messages from the display.
    #
    def __on_display_action(self, src, cmd, *args):

        if (cmd == src.OBS_CLOSE):
            ident = args[0]
            self.__close_display(ident)
            del src

        elif (cmd == src.OBS_RESTART):
            ident = args[0]
            xcoord, ycoord = src.get_geometry()[0:2]
            xcoord = xcoord.as_px()
            ycoord = ycoord.as_px()
            self.__positions[ident] = (xcoord, ycoord)
            path = self.__display_paths[ident]
            self.__remove_display(ident)
            gtk.timeout_add(250, self.__add_display, ident, path,
                            self.__DISPLAY_TYPE_WINDOW)
            del src



    #
    # Adds the given display.
    #
    def __add_display(self, ident, path, displaytype):

        dsp = None
        container = None

        if (ident not in self.__open_displays):
            dsp = self.__create_display(ident, path)

            if (dsp):
                self.__open_displays[ident] = dsp
                self.__display_paths[ident] = path

                print "Info: Adding [%s] with ID '%s' to the list of " \
                      "displays." % (path, ident)
                dsp.add_observer(self.__on_display_action)

                if (displaytype == self.__DISPLAY_TYPE_WINDOW):
                    container = Window()
                    try:
                        xcoord, ycoord = self.__positions[ident]
                        dsp.set_position(xcoord, ycoord)
                    except KeyError:
                        dsp.set_position()

                    dsp.set_container(container)

                elif (displaytype == self.__DISPLAY_TYPE_PLUG):
                    container = Plug()
                    dsp.set_container(container)
                    container.show()

        return container



    #
    # Creates and returns a new display from the given data, or None in case
    # of an error.
    #
    def __create_display(self, ident, path):

        try:
            if (not gnomevfs.exists(path)):
                raise IOError(_("File does not exist."))
            data = gnomevfs.read_entire_file(path)

        except StandardError:
            import traceback
            traceback.print_exc()
            print >> sys.stderr, "Could not open display file [%s]." % (path,)
            dialog.warning(_("Could not open display file '%(path)s'") % vars(),
                           _("The display file could not be opened because "
                             "the file was not readable."), False)
            self.__close_display(ident)
            return


        display = self.__factory.create_display(ident, data, path)

        if (not display):
            dialog.warning(_("Invalid display file '%(path)s'") % vars(),
                           _("The display file contains invalid data and "
                             "could not be loaded."), False)
            self.__close_display(ident)

        return display



    #
    # Removes the given display.
    #
    def __remove_display(self, ident):

        if (ident in self.__open_displays):
            # better use python2.3 dict.pop
            display = self.__open_displays[ident]
            xcoord, ycoord = display.get_geometry()[0:2]
            xcoord = xcoord.as_px()
            ycoord = ycoord.as_px()
            self.__positions[ident] = (xcoord, ycoord)
            if display:
                print "Info: Removing [%s] with ID '%s' from the list of " \
                      "displays." % (display.get_path(), ident)
                display.remove_display()
            del self.__open_displays[ident]
            del self.__display_paths[ident]



    #
    # Closes the given display.
    #
    def __close_display(self, ident):

        if (ident in self.__open_displays):
            display = self.__open_displays[ident]
            display.purge_display()
            
        self.__remove_display(ident)
        if (self.__remove_command):
            os.system("%s %s &" % (self.__remove_command, ident))

        try:
            del self.__positions[ident]
        except StandardError:
            pass



    #
    # Loads the display positions from file.
    #
    def __load_positions(self):

        try:
            lines = open(POSITIONSFILE, "r").readlines()
        except StandardError:
            # no positions
            return

        for line in lines:
            if (not line.strip()):
                continue
            ident, xcoord, ycoord = line.split()
            self.__positions[ident] = (int(xcoord), int(ycoord))



    #
    # Saves the current display positions to a file.
    #
    def __save_positions(self):

        changed = False

        for ident, display in self.__open_displays.items():
            xcoord, ycoord, = display.get_geometry()[0:2]
            xcoord = xcoord.as_px()
            ycoord = ycoord.as_px()
            if (self.__positions.get(ident) != (xcoord, ycoord)):
                changed = True
            self.__positions[ident] = (xcoord, ycoord)

        if (not changed):
            return

        data = ""
        for ident, pos in self.__positions.items():
            xcoord, ycoord = pos
            data += "%s %d %d\n" % (ident, xcoord, ycoord)
        try:
            open(POSITIONSFILE, "w").write(data)
        except IOError:
            import traceback
            traceback.print_exc()
            print >> sys.stderr, "Could not write the position file [%s]." % \
                                                              (POSITIONSFILE,)



    def __handle_open_display(self, path):

        ident = Display.make_id()
        self.__handle_open_display_with_id(path, ident)

        return (ident,)



    def __handle_open_display_with_id(self, path, ident):

        self.__add_display(ident, path, self.__DISPLAY_TYPE_WINDOW)

        return (None,)



    def __handle_open_plug(self, path):

        import time
        ident = str(time.time())
        plug = self.__add_display(ident, path, self.__DISPLAY_TYPE_PLUG)
        xid = plug.get_xembed_id()

        return (ident, xid)



    def __handle_close_display(self, ident):

        self.__remove_display(ident)

        return (None,)



    def __handle_version(self):

        return (NAME, VERSION)



    def __handle_about(self):

        return ("%s %s" % (NAME, VERSION),
                "%s" % (COPYRIGHT.replace(u"\xa9", u"(C)"),),
                "This software is licensed under the terms of the GNU GPL.")



    def __handle_shutdown(self, *args):

        self.__save_positions()

        for ident, display in self.__open_displays.items():
            try:
                display.remove_display()
            except StandardError:
                pass
        gtk.main_quit()
        return (None,)



    def __handle_manage(self, *args):

        cmd = os.path.join(HOME, "gdesklets-shell")
        os.system(cmd + " &")



    def __handle_about_dialog(self, *args):

        self.__about_dialog.show()



    def __handle_show_log(self, *args):

        cmd = os.path.join(HOME, "gdesklets-logview")
        os.system(cmd + " &")


    def __handle_displays(self):

        return self.__open_displays.keys()



    def __handle_display_list(self):

        return self.__open_displays.values()



    def __handle_geometry(self, ident):

        display = self.__open_displays[ident]

        return display.get_geometry()



    def __handle_set_remove_command(self, command):

        # only allow setting the remove command once for security reasons
        if (not self.__remove_command):
            self.__remove_command = command

        return (None,)



    def __handle_set_startup_command(self, command):

        # only allow setting the startup command once for security reasons
        if (not self.__startup_command):
            self.__startup_command = command

            # connect to gnome-session
            try:
                import gnome.ui
                # does the required method exist?
                gnome.ui.Client.set_restart_command
                client = gnome.ui.master_client()
                if (client):
                    client.set_restart_style(gnome.ui.RESTART_IF_RUNNING)
                    cmd = command.split()
                    try:
                        client.set_restart_command(*cmd)
                    except StandardError:
                        client.set_restart_command(len(cmd), cmd)
            except StandardError:
                import traceback
                traceback.print_exc()
                print >> sys.stderr, "Could not connect to gnome-session."
                print _("Could not connect to gnome-session. "
                        "GNOME session management "
                        "will not be available for this application.")

        return (None,)
