#!/usr/bin/env python

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('WebKit', '3.0')
from gi.repository import Gio, Gtk, Gdk, GObject, WebKit
import json
import time
import datetime
import sys

class MenuToolButton(Gtk.ToolButton):
    def __init__(self, text):
        Gtk.ToolButton.__init__(self, Gtk.STOCK_EXECUTE)
        self.set_label(text)
        self.connect("clicked", self.onClicked)

    def set_popup(self, menu):
        self.menu = menu

    def onClicked(self, widget):
        x, y, w, h = self.getScreenCoordinates()
        self.menu.popup(None, None, lambda menu, data: (x, y+h, True), None, 1, 0)

    def getScreenCoordinates(self):
        parent = self.get_parent_window()
        x, y = parent.get_root_origin()
        w = parent.get_width()
        h = parent.get_height()
        extents = parent.get_frame_extents()
        allocation = self.get_allocation()
        return (x + (extents.width-w)/2 + allocation.x,
                y + (extents.height-h)-(extents.width-w)/2 + allocation.y,
                allocation.width,
                allocation.height)

class ThemeWebView(WebKit.WebView):
    def __init__(self):
        WebKit.WebView.__init__ (self)
        
        # Disable context-menu
        settings = self.get_settings()
        settings.set_property('enable-default-context-menu', False)
        settings.set_property("enable-scripts", True)
        settings.set_property("enable-webgl", True)
        settings.set_property("enable-universal-access-from-file-uris", True)
        settings.set_property("enable-developer-extras", True)
        self.set_settings(settings)

        #  Intercept navigation and resource requests (allow only "file:" uris)
        self.connect("navigation-requested", self.onNavigationRequest);
        self.connect("resource-request-starting", self.onResourceRequestStarting);

    def onNavigationRequest(self, view, frame, networkRequest):
        uri = networkRequest.get_uri()
        if not uri.startswith("file:"):
            return 1
        return 0

    def onResourceRequestStarting(self, view, frame, resource, networkRequest, userdata):
        uri = networkRequest.get_uri()
        if not uri.startswith("file:"):
            networkRequest.set_uri("about:blank")

class ThemeToolbar(Gtk.Toolbar):
    def __init__(self, emulator):
        Gtk.Toolbar.__init__ (self)
        self.set_style(Gtk.ToolbarStyle.TEXT)

        self.sensitiveItems = []
        
        # Open
        button = Gtk.ToolButton(Gtk.STOCK_OPEN)
        button.connect('clicked', emulator.onOpenClicked)
        self.add(button)

        # Refresh
        button = Gtk.ToolButton(Gtk.STOCK_REFRESH)
        button.connect('clicked', emulator.onRefreshClicked)
        self.sensitiveItems.append(button)
        self.add(button)

        # En/Disable
        self.disableButton = Gtk.ToolButton(Gtk.STOCK_YES)
        self.disableButton.set_label("Disable")
        self.disableButton.connect('clicked', emulator.onEnableDisableClicked)
        self.sensitiveItems.append(self.disableButton)
        self.add(self.disableButton)

        self.add(Gtk.SeparatorToolItem())

        # Resolution
        name_store = Gtk.ListStore(str)
        name_store.append(["1920x1080"])
        name_store.append(["1024x768"])
        name_store.append(["800x600"])
        name_store.append(["640x480"])

        self.resolutionCombo = Gtk.ComboBox.new_with_model_and_entry(name_store)
        self.resolutionCombo.set_entry_text_column(0)
        self.resolutionCombo.connect('changed', emulator.onResolutionChanged)
        entry = self.resolutionCombo.get_child()
        entry.connect("key-press-event", emulator.onResolutionKeyPress)
        entry.set_text("800x600")
        self.sensitiveItems.append(self.resolutionCombo)
        item = Gtk.ToolItem()
        item.add(self.resolutionCombo)
        self.add(item)

        self.add(Gtk.SeparatorToolItem())

        # Add menu button
        button = self.createAddMenuButton(emulator)
        self.sensitiveItems.append(button)
        self.add(button)

        # Set menu button
        button = self.createSetMenuButton(emulator)
        self.sensitiveItems.append(button)
        self.add(button)

        # Hide menu button
        button = self.createHideMenuButton(emulator)
        self.sensitiveItems.append(button)
        self.add(button)


    def createMenuItem(self, text, callback, *params):
        item = Gtk.MenuItem(text)
        item.connect("activate", callback, *params)
        return item
        
    def createAddMenuButton(self, emulator):
        menu = Gtk.Menu()
        menu.append(self.createMenuItem('Dummies', emulator.onAddDummiesClicked))
        menu.append(self.createMenuItem('User', emulator.onAddUserClicked))
        menu.append(self.createMenuItem('Session', emulator.onAddSessionClicked))
        menu.show_all()

        button = MenuToolButton(u"Add \u25BE")
        button.set_popup(menu)

        return button

    def createSetMenuButton(self, emulator):
        menu = Gtk.Menu()
        menu.append(self.createMenuItem('Welcome Message', emulator.onMenuJavascriptCallPrompt, "set_welcome_message"))
        menu.append(self.createMenuItem('Message', emulator.onMenuJavascriptCallPrompt, "mdm_msg"))
        menu.append(self.createMenuItem('Error Message', emulator.onMenuJavascriptCallPrompt, "mdm_error"))
        menu.append(self.createMenuItem('Timed Message', emulator.onMenuJavascriptCallPrompt, "mdm_timed"))
        menu.show_all()

        button = MenuToolButton(u"Set \u25BE")
        button.set_popup(menu)

        return button

    def createHideMenuButton(self, emulator):
        menu = Gtk.Menu()
        menu.append(self.createMenuItem('Shutdown', emulator.onMenuJavascriptCall, "mdm_hide_shutdown"))
        menu.append(self.createMenuItem('Suspend', emulator.onMenuJavascriptCall, "mdm_hide_suspend"))
        menu.append(self.createMenuItem('Restart', emulator.onMenuJavascriptCall, "mdm_hide_restart"))
        menu.append(self.createMenuItem('Quit', emulator.onMenuJavascriptCall, "mdm_hide_quit"))
        menu.append(self.createMenuItem('XDMCP', emulator.onMenuJavascriptCall, "mdm_hide_xdmcp"))
        menu.show_all()

        button = MenuToolButton(u"Hide \u25BE")
        button.set_popup(menu)

        return button

class MultiPromptDialog(Gtk.Dialog):
    def __init__(self, parent, title, okLabel, *labels):
        Gtk.Dialog.__init__ (self, title, parent,
                              Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT)
                              
        self.add_button(okLabel, Gtk.ResponseType.OK)
        self.add_button("Cancel", Gtk.ResponseType.CANCEL)
        self.set_title(title)

        dialogBox = self.get_content_area()

        vbox = Gtk.VBox()

        self.entries = []
        for label in labels:
            entry = Gtk.Entry()
            label = Gtk.Label(label)
            label.set_alignment(0, 0)
            vbox.add(label)
            vbox.add(entry)
            self.entries.append(entry)

        dialogBox.pack_end(vbox, False, False, 0)

        self.connect("key-press-event", self.onKeyPress)
        self.show_all()
        
    def onKeyPress(self, widget, event):
        if event.keyval == Gdk.KEY_Return:
            self.response(Gtk.ResponseType.OK)

    def getTexts(self):
        result = []
        for entry in self.entries:
            result.append(entry.get_text())

        return result
        
class ThemeEmulator:
    def closeApplication(self, widget):
        Gtk.main_quit()

    def __init__(self):
        self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        self.window.connect("destroy", self.closeApplication)
        self.window.set_title("MDM Theme Emulator")

        self.userName = None

        self.toolbar = ThemeToolbar(self)
        for item in self.toolbar.sensitiveItems:
            item.set_sensitive(False)

        self.scrolledWindow = Gtk.ScrolledWindow()
        self.scrolledWindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)

        self.webView = ThemeWebView()

        self.scrolledWindow.add(self.webView)
        self.scrolledWindow.show()

        vbox = Gtk.VBox(False, 2)
        vbox.pack_start(self.toolbar, False, False, 0)
        vbox.add(self.scrolledWindow)
        self.statusLabel = Gtk.Label("")
        self.statusLabel.set_alignment(0, 0)
        vbox.pack_end(self.statusLabel, False, False, 0)
        self.window.add(vbox)
        self.window.show_all()
        self.setResolution(800, 600)

        self.webView.connect("load-finished", self.onLoadFinished)
        self.webView.connect("script-alert", self.onAlert)
        self.setStatus("Initialized")

        self.isFullscreen = False
        self.isLoaded = False
        self.isEnabled = True
        self.window.connect('window-state-event', self.onWindowStateChange)
        self.window.connect('motion-notify-event', self.onMotion)
        self.window.connect("key-press-event", self.onWindowKeyPress)

        GObject.timeout_add(1000, self.onClockUpdate, None)
        if len(sys.argv) >= 2:
            self.filename = sys.argv[1]
            self.setStatus("Loading file: " + self.filename)
            self.webView.open(self.filename)
            
            if len(sys.argv) >= 3 and sys.argv[2] == 'fullscreen':
                self.window.fullscreen()

    def callJavascriptMethod(self, method, *arguments):
        jsonData = json.dumps(arguments)
        self.webView.execute_script(method + ".apply(null, " + jsonData + ");");

    def setResolutionFromString(self, string):
        data = string.split('x')
        if len(data) == 2:
            width, height = data
            self.setResolution(int(width), int(height))
            self.setStatus("Resolution changed to: " + string)
        else:
            self.setStatus("Wrong resolution format: " + string)

    def setResolution(self, width, height):
        self.window.unmaximize()
        self.scrolledWindow.set_size_request(width, height)
        self.window.resize(100,100) # force resize to minimum size

    def setEnabled(self, enable):
        if self.isEnabled != enable:
            self.isEnabled = enable
            if enable:
                self.toolbar.disableButton.set_label("Disable")
                self.callJavascriptMethod("mdm_enable")
            else:
                self.toolbar.disableButton.set_label("Enable")
                self.callJavascriptMethod("mdm_disable")

    def setTime(self):
        self.callJavascriptMethod("set_clock", time.strftime("%a %b %d, %H:%M"))

    def setStatus(self, string):
        self.statusLabel.set_text(string)

    def multiPromptDialog(self, title, okLabel, *labels):
        dialog = MultiPromptDialog(self.window, title, okLabel, *labels)
        response = dialog.run()

        if response == Gtk.ResponseType.OK:
            result = dialog.getTexts()
        else:
            result = None

        dialog.destroy()
        return result

    def onClockUpdate(self, *args):
        if self.isLoaded:
            self.setTime()

        time_til_next_min = 60 - datetime.datetime.now().second;
        if time_til_next_min < 0:
            time_til_next_min = 0

        GObject.timeout_add(time_til_next_min*1000, self.onClockUpdate, None)
        return False

    def onWindowStateChange(self, window, event):
        self.isFullscreen = bool(Gdk.WindowState.FULLSCREEN & event.new_window_state)
        if self.isLoaded:
            self.toolbar.resolutionCombo.set_sensitive(self.isFullscreen == False)
        if self.isFullscreen:
            self.toolbar.hide()
            self.statusLabel.hide()
        else:
            self.toolbar.show()
            self.statusLabel.show()

    def onMotion(self, window, event):
        if self.isFullscreen:
            if event.y < 2:
                self.toolbar.show()
            else:
                self.toolbar.hide()

            width, height = self.window.get_size()
            if event.y > height-2:
                self.statusLabel.show()
            else:
                self.statusLabel.hide()

    def onWindowKeyPress(self, widget, event):
        if event.keyval == Gdk.KEY_F11:
            if self.isFullscreen:
                self.window.unfullscreen()
            else:
                self.window.fullscreen()
        elif event.keyval == Gdk.KEY_Escape and self.isFullscreen:
            self.window.unfullscreen()

    def onMenuJavascriptCallPrompt(self, widget, name):
        text = self.multiPromptDialog("Please supply a message", "Set", "Please enter a message for %s:" % widget.get_label())
        if text:
            self.callJavascriptMethod(name, text)

    def onMenuJavascriptCall(self, widget, name):
        self.callJavascriptMethod(name)

    def onEnableDisableClicked(self, widget):
        self.setEnabled(self.isEnabled == False)

    def onResolutionChanged(self, widget):
        if widget.get_active_iter():
            self.setResolutionFromString(widget.get_child().get_text())

    def onResolutionKeyPress(self, widget, event):
        if event.keyval == Gdk.KEY_Return:
            self.setResolutionFromString(widget.get_text())

    def onRefreshClicked(self, widget):
        self.userName = None
        self.webView.reload()
        self.setStatus("Reloading")

    def onAddDummiesClicked(self, widget):
        self.callJavascriptMethod("mdm_add_user", "dummy", "John Dummy", "Already logged in")
        self.callJavascriptMethod("mdm_add_user", "joe", "Joe User", "")
        self.callJavascriptMethod("mdm_add_session", "Cinnamon", "cinnamon.desktop")
        self.callJavascriptMethod("mdm_add_session", "Mate", "mate.desktop")
        self.callJavascriptMethod("mdm_add_session", "Gnome", "gnome3.desktop")
        self.callJavascriptMethod("mdm_add_language", "English (USA)", "en_US.UTF-8")
        self.callJavascriptMethod("mdm_add_language", "Chinese (China)", "zh_CN.UTF-8")
        self.callJavascriptMethod("mdm_add_language", "Spanish (Argentina)", "es_AR.UTF-8")
        self.callJavascriptMethod("mdm_add_language", "French (France)", "fr_FR.UTF-8")
        self.callJavascriptMethod("mdm_add_language", "Dummy (DummyLand)", "du_DU")        
        self.setStatus("Added 2 dummy users, 5 dummy languages and 3 dummy sessions")

    def onAddSessionClicked(self, widget):
        data = self.multiPromptDialog("Add a new Session", "Add", "Please enter a session name:", "Please enter a desktop filename:")
        if data:
            name, filename = data
            if name != "" and filename != "":
                self.callJavascriptMethod("mdm_add_session", name, filename)
                self.setStatus("Added session: %s / %s" % (name, filename))
            else:
                self.setStatus("Add session failed due to empty field")

    def onAddUserClicked(self, widget):
        data = self.multiPromptDialog("Add a new User", "Add", "Please enter a user name:", "Please enter gecos:")
        if data:
            name, gecos = data
            if name != "" and gecos != "":
                self.callJavascriptMethod("mdm_add_user", name, gecos, "true")
                self.setStatus("Added user: %s / %s" % (name, gecos))
            else:
                self.setStatus("Add user failed due to empty field")

    def onOpenClicked(self, widget):
        dialog = Gtk.FileChooserDialog("Please select an mdm theme", self.window,
            Gtk.FileChooserAction.OPEN,
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
             Gtk.STOCK_OPEN, Gtk.ResponseType.OK))

        filter_text = Gtk.FileFilter()
        filter_text.set_name("HTML files")
        filter_text.add_mime_type("text/html")
        dialog.add_filter(filter_text)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            self.filename = dialog.get_filename()
            self.setStatus("Loading file: " + self.filename)
            self.webView.open(self.filename)

        dialog.destroy()

    def onLoadFinished(self, *args):
        self.isLoaded = True
        self.setEnabled(True)
        self.userName = None
        if self.toolbar.sensitiveItems:
            for item in self.toolbar.sensitiveItems:
                item.set_sensitive(True)
            self.toolbar.sensitiveItems = None

        self.callJavascriptMethod("set_welcome_message", "Welcome to the MDM Theme Emulator")
        self.callJavascriptMethod("mdm_msg", "Please enter your username")
        self.callJavascriptMethod("mdm_set_current_language", "English (USA)", "en_US.UTF-8")
        self.callJavascriptMethod("mdm_prompt", "Username: ")
        self.setTime()

        self.setStatus("Load finished: " + self.filename)
        
    def onAlert(self, view, frame, message):
        data = message.split('###', 1)
        if len(data) == 2:
            action, param = data
            if action == "USER":
                self.userName = param
                self.callJavascriptMethod("mdm_noecho", 'Password:')
                self.setStatus("Selected user: " + param)
            elif action == "LOGIN":
                if self.userName:
                    self.setStatus("Logged in as %s, with password %s" % (self.userName, param))
                    self.userName = None
                    self.callJavascriptMethod("mdm_error", "Can't log in in emulator")
                    self.callJavascriptMethod("mdm_prompt", 'Username:')
                else:
                    self.userName = param
                    self.setStatus("Entered user: " + param)
                    self.callJavascriptMethod("mdm_noecho", 'Password:')
            elif action == "SESSION":
                self.setStatus("Session: " + param)
            elif action == "LANGUAGE":
                self.setStatus("Language: " + param)
            elif action == "SHUTDOWN":
                self.setStatus("Shutdown")
            elif action == "SUSPEND":
                self.setStatus("Suspend")
            elif action == "RESTART":
                self.setStatus("Restart")
            elif action == "QUIT":
                self.setStatus("Quit")
            elif action == "XDMCP":
                self.setStatus("Xdmcp")
            else:
                self.setStatus('Unrecognized action: ' + action)
            return 1
        
def main():
    Gtk.main()
    return 0

if __name__ == "__main__":
    ThemeEmulator()
    main()
