#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  ftlaunch
#
#  Copyright (C) 2015 FabulaTech
#

import subprocess
import threading
import shlex
import sys
import os
import time
import gobject
import errno
import ConfigParser
import json
import pygtk
pygtk.require('2.0')
import gtk
import re

# Base message class
class ShowMessage(object):
    def __init__(self, parent_window=None, title=None, msg_str="",
                 msg_type=gtk.MESSAGE_INFO):
        self.dialog = gtk.MessageDialog(parent_window,
                                        type = msg_type,
                                        buttons = gtk.BUTTONS_CLOSE)
        if title:
            self.dialog.set_title(title)
        if len(msg_str):
            self.dialog.set_markup(msg_str)
        self.dialog.run()
        gtk.Widget.destroy(self.dialog)

class ErrorMessage(ShowMessage):
    def __init__(self, parent_window=None, msg=""):
        super(ErrorMessage, self).__init__(parent_window,
                                           os.path.basename(__file__),
                                           msg,
                                           gtk.MESSAGE_ERROR)

class InfoMessage(ShowMessage):
    def __init__(self, parent_window=None, msg=""):
        super(InfoMessage, self).__init__(parent_window,
                                          os.path.basename(__file__),
                                          msg,
                                          gtk.MESSAGE_INFO)

class FinishDialog(object):
    def __init__(self, parent_window=None, log_str=""):
        self.dialog = gtk.MessageDialog(parent_window,
                                        type = gtk.MESSAGE_INFO,
                                        buttons = gtk.BUTTONS_YES_NO)
        self.dialog.set_title(os.path.basename(__file__))
        self.dialog.set_markup("Remote desktop session is finished\nShow log?")
        response = self.dialog.run()
        self.dialog.destroy()
        if response == gtk.RESPONSE_YES:
            text_area = gtk.TextView()
            text_area.get_buffer().set_text(log_str)
            text_area.set_cursor_visible(False)
            text_area.set_editable(False)
            dialog = gtk.Dialog(os.path.basename(__file__),
                                parent_window,
                                gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
            dialog.vbox.pack_start(text_area)
            text_area.show()
            button = dialog.add_button("Close", gtk.RESPONSE_CLOSE)
            button.grab_default()
            dialog.run()
            dialog.destroy()

# Application main window
class MainWindow(object):
    FT_TITLE = "FabulaTech Remote Desktop Client"
    FT_RESOURCE_PATH = [".", "/opt/ftplugins/lib",
                        "/opt/ftplugins/share/ftlaunch"]
    FT_ICON_FILE = "ftlaunch-gui-icon.svg"
    FT_LOGO_FILE = "ftlaunch-gui-logo.png"
    FT_RDPBR_FILE = "ftrdpbr"
    FT_HISTORY_FILE = ".ftlaunch-history"
    MAX_LIST_ITEMS = 10

    def __init__(self):
        self.proc = None
        self.FT_HISTORY_FILE = os.path.join(os.path.expanduser("~"),
                                            self.FT_HISTORY_FILE)

        # Find all necessary files
        if not self.locate_freerdp():
            ErrorMessage(msg = "xfreerdp is not found")
            sys.exit()

        if not self.locate_file(self.FT_ICON_FILE):
            ErrorMessage(msg = self.FT_ICON_FILE + " is not found")
            sys.exit()
        self.FT_ICON_FILE = self.locate_file(self.FT_ICON_FILE)

        if not self.locate_file(self.FT_LOGO_FILE):
            ErrorMessage(msg = self.FT_LOGO_FILE + " is not found")
            sys.exit()
        self.FT_LOGO_FILE = self.locate_file(self.FT_LOGO_FILE)

        if not self.locate_file(self.FT_RDPBR_FILE + ".so"):
            ErrorMessage(msg = self.FT_RDPBR_FILE + " is not found")
            sys.exit()

        # Create GUI
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_size_request(400, 300)
        self.window.set_title(self.FT_TITLE)
        self.window.set_icon(gtk.gdk.pixbuf_new_from_file(self.FT_ICON_FILE))
        self.window.set_border_width(0)
        self.window.set_position(gtk.WIN_POS_CENTER)
        self.window.connect("delete-event", self.on_delete_event)

        self.logo = gtk.Image()
        self.logo.set_from_file(self.FT_LOGO_FILE)
        self.logo_height = 0
        self.logo_width = 0
        self.logo.connect("expose-event", self.on_logo_resize)

        label1 = gtk.Label("Server")
        label1.set_alignment(0, 0.5)
        label2 = gtk.Label("Login")
        label2.set_alignment(0, 0.5)
        label3 = gtk.Label("Password")
        label3.set_alignment(0, 0.5)
        label4 = gtk.Label("FreeRDP arguments")
        label4.set_alignment(0, 0.5)

        # Server address text filed completion
        model = gtk.ListStore(gobject.TYPE_STRING)
        completion = gtk.EntryCompletion()
        completion.set_text_column(0)
        completion.set_model(model)
        self.server_entry = gtk.Entry(0)
        self.server_entry.set_completion(completion)
        self.server_entry.set_activates_default(True)

        # Login text filed completion
        model = gtk.ListStore(gobject.TYPE_STRING)
        completion = gtk.EntryCompletion()
        completion.set_text_column(0)
        completion.set_model(model)
        self.login_entry = gtk.Entry(0)
        self.login_entry.set_completion(completion)
        self.login_entry.set_activates_default(True)

        # Password text field
        self.passwd_entry = gtk.Entry(0)
        self.passwd_entry.set_visibility(False)
        self.passwd_entry.set_activates_default(True)

        # FreeRDP parameters text filed completion
        model = gtk.ListStore(gobject.TYPE_STRING)
        completion = gtk.EntryCompletion()
        completion.set_text_column(0)
        completion.set_model(model)
        self.freerdp_params = gtk.Entry(0)
        self.freerdp_params.set_completion(completion)
        self.freerdp_params.set_activates_default(True)

        entry_table = gtk.Table(4, 2, False)
        entry_table.set_col_spacings(6)
        entry_table.set_row_spacings(6)
        entry_table.attach(label1, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
        entry_table.attach(self.server_entry, 1, 2, 0, 1)
        entry_table.attach(label2, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
        entry_table.attach(self.login_entry, 1, 2, 1, 2)
        entry_table.attach(label3, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
        entry_table.attach(self.passwd_entry, 1, 2, 2, 3)
        entry_table.attach(label4, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
        entry_table.attach(self.freerdp_params, 1, 2, 3, 4)

        entry_table_box = gtk.HBox(False, 0)
        entry_table_box.pack_start(entry_table, True, True, 10)

        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
        self.connect_button = gtk.Button("_Connect", None, True)
        self.connect_button.connect("clicked", self.on_connect_button_click)
        self.connect_button.set_image(image)
        self.connect_button.set_can_default(True)

        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_QUIT, gtk.ICON_SIZE_BUTTON)
        self.quit_button = gtk.Button("_Quit", None, True)
        self.quit_button.set_image(image)
        self.quit_button.connect("clicked", self.on_quit_button_click)

        button_hbox = gtk.HBox(False, 0)
        button_hbox.pack_start(self.connect_button, True, False, 0)
        button_hbox.pack_start(self.quit_button, True, False, 0)

        main_vbox = gtk.VBox(False, 0)
        main_vbox.pack_start(self.logo, False, False, 0)
        main_vbox.pack_start(entry_table_box, False, False, 15)
        main_vbox.pack_end(button_hbox, False, False, 15)

        self.window.add(main_vbox)
        self.connect_button.grab_default()

        # Systray icon
        self.tray = gtk.StatusIcon()
        self.tray.set_visible(False)
        self.tray.set_from_file(self.FT_ICON_FILE)
        self.tray.connect("popup-menu", self.on_tray_right_click)
        self.tray.set_tooltip_text(self.FT_TITLE)

        self.restore_history_from_file()

        # Show window
        self.window.show_all()

    def on_logo_resize(self, widget, event):
        allocation = widget.get_allocation()
        if self.logo_height != allocation.height or self.logo_width != allocation.width:
            self.logo_height = allocation.height
            self.logo_width = allocation.width
            dest_pixbuf = gtk.gdk.Pixbuf(colorspace = gtk.gdk.COLORSPACE_RGB,
                                         has_alpha = False,
                                         bits_per_sample = 8,
                                         width = allocation.width,
                                         height = allocation.height)
            dest_pixbuf.fill(0xffffffff)
            src_pixbuf = gtk.gdk.pixbuf_new_from_file(self.FT_LOGO_FILE)
            src_pixbuf.composite(dest_pixbuf,
                                 0, 0,
                                 dest_pixbuf.get_width(),
                                 dest_pixbuf.get_height(),
                                 0, 0,
                                 1.0, 1.0,
                                 gtk.gdk.INTERP_BILINEAR,
                                 255)
            widget.set_from_pixbuf(dest_pixbuf)

    def save_history_to_file(self):
        # Save text entries history to file
        history = ConfigParser.RawConfigParser()
        history.add_section("Entries")

        value = [ row[0] for row in self.server_entry.get_completion().get_model() ]
        history.set("Entries", "address", json.dumps(value))

        value = [ row[0] for row in self.login_entry.get_completion().get_model() ]
        history.set("Entries", "login", json.dumps(value))

        value = [ row[0] for row in self.freerdp_params.get_completion().get_model() ]
        history.set("Entries", "parameters", json.dumps(value))

        with open(self.FT_HISTORY_FILE, "wb") as historyfile:
            history.write(historyfile)

    def restore_history_from_file(self):
        # Restore text fields history from file
        try:
            history = ConfigParser.RawConfigParser()
            history.read(self.FT_HISTORY_FILE)

            for value in json.loads(history.get("Entries", "address")):
                self.list_add_value(self.server_entry.get_completion().get_model(),
                                    value)

            for value in json.loads(history.get("Entries", "login")):
                self.list_add_value(self.login_entry.get_completion().get_model(),
                                    value)

            for value in json.loads(history.get("Entries", "parameters")):
                self.list_add_value(self.freerdp_params.get_completion().get_model(),
                                    value)
        except:
            pass

    def quit(self):
        self.save_history_to_file()
        gtk.main_quit()

    def on_delete_event(self, widget, event, data=None):
        self.quit()
        return True

    def on_quit_button_click(self, button):
        self.quit()

    def on_connect_button_click(self, button):
        # Check for entries filling
        if len(self.server_entry.get_text()) == 0:
            ErrorMessage(self.window, "You must enter server address")
            return
        elif len(self.login_entry.get_text()) == 0:
            ErrorMessage(self.window, "You must enter login")
            return
        elif len(self.passwd_entry.get_text()) == 0:
            ErrorMessage(self.window, "You must enter password")
            return
        # Save text entries
        try:
            self.list_add_value(self.server_entry.get_completion().get_model(),
                                self.server_entry.get_text())
            self.list_add_value(self.login_entry.get_completion().get_model(),
                                self.login_entry.get_text())
            self.list_add_value(self.freerdp_params.get_completion().get_model(),
                                self.freerdp_params.get_text())
        except:
            pass
        # Hide main window and set icon in tray
        self.window.hide()
        self.tray.set_visible(True)
        # Start subprocess thread
        t = threading.Thread(target = self.subprocess_thread)
        t.daemon = True
        t.start()

    def on_tray_right_click(self, icon, event_button, event_time):
        label = "Disconnect from " + self.server_entry.get_text()
        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_DISCONNECT, gtk.ICON_SIZE_MENU)
        item = gtk.ImageMenuItem()
        item.set_image(image)
        item.set_label(label)
        item.connect("activate", self.disconnect_from_server)
        item.show()
        menu = gtk.Menu()
        menu.append(item)
        menu.popup(None, None, gtk.status_icon_position_menu,
                   event_button, event_time, icon)

    def disconnect_from_server(self, menuitem):
        if self.proc:
            self.proc.terminate()

    def get_rdp_version(self):
        prefix = "This is FreeRDP version "
        proc = subprocess.Popen(shlex.split("xfreerdp --version"),
                        stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT)
        stdoutdata, stderrdata = proc.communicate(None)
        re_full = re.compile(prefix + "[\d]+\.[\d]+\.[\d]+")
        if re_full.match(stdoutdata):
            stdoutdata = stdoutdata[len(prefix):]
            re_version = re.compile("[\d]+\.[\d]+")
            return float(re_version.match(stdoutdata).group())
        return 0.0

    def subprocess_thread(self):
        log_str = ""
        err_str = ""
        rdp_version = self.get_rdp_version()
        try:
            # Make process args
            cmd_line  = "xfreerdp"
            if rdp_version > 1.0 or rdp_version == 0.0:
                cmd_line += " /u:" + self.login_entry.get_text()
                cmd_line += " /p:" + self.passwd_entry.get_text()
                cmd_line += " /dvc:" + self.FT_RDPBR_FILE
                # XXX - temporary solution. We should ask users whether to continue or not
                proc = subprocess.Popen(shlex.split("xfreerdp --help"),
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT)
                stdoutdata, stderrdata = proc.communicate(None)
                if stdoutdata.find("cert-ignore") != -1:
                    cmd_line += " /cert-ignore"                
            else:
                cmd_line += " -u " + self.login_entry.get_text()
                cmd_line += " -p " + self.passwd_entry.get_text()
                cmd_line += " --plugin " + self.FT_RDPBR_FILE
                # XXX - temporary solution. We should ask users whether to continue or not
                proc = subprocess.Popen(shlex.split("xfreerdp --help"),
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT)
                stdoutdata, stderrdata = proc.communicate(None)
                if stdoutdata.find("ignore-certificate") != -1:
                    cmd_line += " --ignore-certificate"                

                
            if len(self.freerdp_params.get_text()) != 0:
                cmd_line += " " + self.freerdp_params.get_text()
                
            if rdp_version > 1.0 or rdp_version == 0.0:
                cmd_line += " /v:" + self.server_entry.get_text()
            else:
                cmd_line += " " + self.server_entry.get_text()

            # Run subprocess
            self.proc = subprocess.Popen(shlex.split(cmd_line),
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.STDOUT)
            stdoutdata, stderrdata = self.proc.communicate(None)

            # Check output
            if stdoutdata and len(stdoutdata):
                log_str += stdoutdata
        except (ValueError, OSError) as err:
            err_str = str(cmd_line) + "\n" + str(err)
        finally:
            self.proc = None
            # Restore (unhide) main window
            gtk.gdk.threads_enter()
            if not gtk.Widget.get_visible(self.window):
                self.tray.set_visible(False)
                self.window.show()
            if len(err_str) != 0:
                ErrorMessage(self.window, err_str)
            elif len(log_str) != 0:
                FinishDialog(self.window, log_str)
            gtk.gdk.threads_leave()

    def list_add_value(self, list_store, value = None):
        if value and (not len(list_store)
                      or value not in [ row[0] for row in list_store ]):
            while len(list_store) >= self.MAX_LIST_ITEMS:
                del list_store[0]
            list_store.set(list_store.append(), 0, value)

    def locate_file(self, filename):
        for path in self.FT_RESOURCE_PATH:
            if os.path.isfile(os.path.join(path, filename)):
                return os.path.join(path, filename)
        return None

    def locate_freerdp(self):
        for path in os.environ["PATH"].split(":"):
            if os.path.exists(os.path.join(path, "xfreerdp")):
                return os.path.join(path, "xfreerdp")
        return None

def main():
    gtk.gdk.threads_init()
    MainWindow()
    gtk.main()

if __name__ == "__main__":
    main()
