from config import settings
from config.DaemonConfigger import DaemonConfigger
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
from main.AboutDialog import AboutDialog
from main.remotecommands import *
from main.RemoteSocket import RemoteSocket
from utils import dialog
from utils import vfs
from utils.error import Error

import gobject
import gtk
import os
import sys


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

    __DISPLAY_TYPE_WINDOW = "window"
    __DISPLAY_TYPE_PLUG = "plug"


    def __init__(self):

        # feedback dialog
        self.__feedback = LoadingFeedback()

        # the communication socket
        self.__socket = RemoteSocket()

        # the factory which reads the display's XML
        self.__factory = DisplayFactory()

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

        # the configuration dialog
        self.__configger = DaemonConfigger()

        # the command which gets called when the user removes a display
        self.__remove_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 = {}

        # 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_CONFIGURE,
                                          self.__handle_configger)
        self.__socket.add_message_handler(COMMAND_SET_REMOVE_COMMAND,
                                          self.__handle_set_remove_command)


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

        # setup a nice systray icon
        if (not HAVE_WIN32 and settings.show_tray_icon):
            from main.TrayIcon import TrayIcon
            trayicon = TrayIcon()
            trayicon.set_menu([(None, _("_Manage desklets"),
                                self.__handle_manage),
                               (),
                               (gtk.STOCK_PROPERTIES, _("_Configuration"),
                                 self.__handle_configger),
                               (None, _("_View log"),
                                self.__handle_show_log),
                                (),
                               (None, _("_About..."),
                                self.__handle_about_dialog),
                               (),
                               (gtk.STOCK_QUIT, _("_Stop daemon"),
                                self.__handle_shutdown)])



    #
    # 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()
            path = self.__display_paths[ident]
            self.__remove_display(ident)
            def f(*args): pass
            gobject.timeout_add(250, self.__handle_open_display_with_id,
                                f, path, ident)
            del src



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

        dsp = None
        container = None

        # can this happen?
        if (ident in self.__open_displays): return None

        
        Error().register(ident, path)
        
        # create display
        try:
            dsp = self.__create_display(ident, path)
        except:
            Error().handle(ident)
            #log("Warning: Couldn't add desklet \"%s\".\n%s"  % (path, exc))
            self.__close_display(ident)
            return None
        
        self.__open_displays[ident] = dsp
        self.__display_paths[ident] = path

        log("Adding \"%s\" with ID \"%s\" to the desklet list."
            % (path, ident))
        dsp.add_observer(self.__on_display_action)

        # create container widget
        if (displaytype == self.__DISPLAY_TYPE_WINDOW):
            container = Window(dsp)

        #elif (displaytype == self.__DISPLAY_TYPE_PLUG):
        #    container = Plug()
        #    dsp.set_container(container)
        #    container.show()
            
        # initialize display
        try:
            dsp.initialize()
        except:
            Error().handle(ident)
            self.__close_display(ident)
            return None

        return container



    #
    # Creates and returns a new display from the given data.
    #
    def __create_display(self, ident, path):

        try:
            data = vfs.read_entire_file(path)

        except:
            log("Could not open desklet file \"%s\"." % (path,))
            raise UserError(_("Could not open desklet file \"%s\"") % (path,),
                           _("The desklet file could not be opened because "
                             "the file was not readable."),
                            show_details = False)

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

        return display



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

        try:
            display = self.__open_displays[ident]
        except KeyError, exc:
            log("Warning: Couldn't remove desklet with ID \"%s\".\n%s" \
                % (ident, exc))
            return

        log("Removing \"%s\" with ID \"%s\" from the desklet list."
            % (self.__display_paths[ident], ident))
        display.remove_display()
        Error().forget(ident)

        try:
            del self.__open_displays[ident]
            del self.__display_paths[ident]
        except StandardError:
            pass

        import gc
        gc.collect()


    #
    # 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))



    def __handle_open_display(self, callback, path):

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

        #return (ident,)
        #callback(ident)



    def __handle_open_display_with_id(self, callback, path, ident):

        self.__feedback.set_loading(path)

        def open_window():
            self.__feedback.show()

        def close_window(*args):
            self.__feedback.hide()

        def f(*args):
            return (ident, path, self.__DISPLAY_TYPE_WINDOW)

        def g(*args):
            return ident

        self.__pipeline([open_window, f, self.__add_display, close_window,
                         g, callback])
        #gobject.timeout_add(100, self.__add_display, callback, ident, path,
        #                    self.__DISPLAY_TYPE_WINDOW)
        #callback()



    def __handle_open_plug(self, callback, 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)
        callback(ident, xid)



    def __handle_close_display(self, callback, ident):

        self.__remove_display(ident)
        callback()


    def __handle_version(self, callback):

        #return (NAME, VERSION)
        callback(NAME, VERSION)



    def __handle_about(self, callback):

        callback("%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, callback, *args):

        #self.__save_positions()

        for ident, display in self.__open_displays.items():
            try:
                display.remove_display()
            # what kind of exception are we expecting here?
            except:
               log("Could not remove desklet \"%s\"!" % (display,))
        # FIXME circumvent: 'gtk.ImageMenuItem' object is not callable
        try:
            callback()
        except:
            pass
        gtk.main_quit()



    def __handle_manage(self, *args):

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


    def __handle_configger(self, *args):

        self.__configger.show()



    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, callback):

        #return self.__open_displays.keys()
        callback(self.__open_displays.keys())



    def __handle_display_list(self, callback):

        #return self.__open_displays.values()
        callback(*self.__open_displays.values())


    def __handle_geometry(self, callback, ident):

        display = self.__open_displays[ident]

        #return display.get_geometry()
        callback(*display.get_geometry())


    def __handle_set_remove_command(self, callback, command):

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

        callback()



    #
    # Executes the given commands in a pipeline. A command takes the return
    # values of the previous command as its arguments.
    #
    def __pipeline(self, line, *args):

        next = line.pop(0)
        try:
            retval = next(*args)
        except:
            from utils.ErrorFormatter import ErrorFormatter
            log(ErrorFormatter().format(sys.exc_info()))
            retval = ()

        if (line):
            if (not type(retval) in (type(()), type([]))):
                retval = (retval,)
            gobject.idle_add(self.__pipeline, line, *retval)




#
# Class for giving feedback while loading a display.
#
# TODO: move into its own file
#
class LoadingFeedback(gtk.Window):

    def __init__(self):

        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
        self.set_position(gtk.WIN_POS_CENTER)

        frm = gtk.Frame()
        frm.set_shadow_type(gtk.SHADOW_OUT)
        frm.show()
        self.add(frm)
        
        box = gtk.HBox(spacing = 12)
        box.set_border_width(12)
        box.show()
        frm.add(box)

        self.__icon = gtk.Image()
        box.pack_start(self.__icon)

        self.__label = gtk.Label("")
        self.__label.show()
        box.pack_end(self.__label)



    def set_loading(self, path):

        from MetaData import MetaData
        try:
            meta = MetaData(path)
            preview = meta.get(meta.KEY_PREVIEW)
        except:
            preview = ""

        dirname = os.path.dirname(path)
        icon = os.path.join(dirname, preview)

        self.__label.set_markup("<big><b>Loading:</b>\n%s</big>" % path)

        self.__icon.hide()
        if (preview):
            try:
                # for him who loves small try blocks: you don't gain anything
                # from splitting up this block except for badly readable code ;)
                from utils import vfs
                data = vfs.read_entire_file(icon)
                loader = gtk.gdk.PixbufLoader()
                loader.write(data, len(data))
                loader.close()
                pbuf = loader.get_pixbuf()

                # scale icon down while preserving aspect ratio
                width = pbuf.get_width()
                height = pbuf.get_height()
                scale = 48 / float(height)
                width *= scale
                if (width > 96): width = 96
                
                if (abs(scale - 1.0) > 0.001):
                    pbuf = pbuf.scale_simple(int(width), 48, 3)

                self.__icon.set_from_pixbuf(pbuf)
                self.__icon.show()

            except:
                pass

        #end if

        self.resize(10, 10)
        self.set_size_request(-1, -1)
