#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (applies if no explicit header in the file):
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# Copyright (C) 2011 Canonical Ltd.
# Authors:
#  * Stéphane Graber <stephane.graber@canonical.com>, 2011

import os, gettext, sys, subprocess
from threading import Thread
from gettext import gettext as _
from gi.repository import Gtk as gtk
from gi.repository import GObject as gobject
gettext.textdomain("ltsp-live")
status = ""

def generate_network(name, address):
    import uuid

    try:
        from ConfigParser import ConfigParser
    except:
        from configparser import ConfigParser

    parser = ConfigParser()
    path = "/etc/NetworkManager/system-connections/%s" % name

    if path not in parser.read(path):
        if os.path.exists(path):
            print(_("Unable to parse config"))
            sys.exit(1)

    if "802-3-ethernet" not in parser.sections():
        parser.add_section("802-3-ethernet")
    parser.set("802-3-ethernet", "duplex", "full")

    if "connection" not in parser.sections():
        parser.add_section("connection")
    parser.set("connection", "id", name)
    parser.set("connection", "uuid", str(uuid.uuid4()))
    parser.set("connection", "type", "802-3-ethernet")

    if "ipv6" not in parser.sections():
        parser.add_section("ipv6")
    parser.set("ipv6", "method", "ignore")

    if "ipv4" not in parser.sections():
        parser.add_section("ipv4")
    parser.set("ipv4", "method", "manual")
    parser.set("ipv4", "addresses1", address)


    config=open("%s.tmp" % path, "w+")
    parser.write(config)
    config.close()

    os.chmod("%s.tmp" % path, 0o600)
    os.rename("%s.tmp" % path, path)

def enable_network(name, interface):
    cmd=['nmcli', 'con', 'up', 'id', name, 'iface', interface]
    enable=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,env={'LANG':'C'})
    retval=enable.wait()

    return retval

def list_devices():
    devices={}

    cmd=['nmcli', '-e', 'yes', '-t', '-f', 'DEVICE,TYPE,STATE', 'dev']
    listdev=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,env={'LANG':'C'})
    retval=listdev.wait()

    if retval != 0:
        return []
    else:
        for line in listdev.stdout.readlines():
            fields=str(line).strip().split(':')
            if len(fields) == 3:
                if fields[1] != "802-3-ethernet" or fields[2] == "unavailable":
                    continue
                devices[fields[0]]=(fields[1],fields[2])

    return devices

def start_ltsplive(interface):
    global status
    import time

    # FIXME: ugly hack for inotify not working on overlayfs
    status = _("Restarting Network Manager")
    cmd=["initctl", "restart", "network-manager"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()
    time.sleep(5)

    # Add the network to Network Manager
    status = _("Adding LTSP network to Network Manager")
    generate_network("LTSP","192.168.0.1;24;0.0.0.0;")
    time.sleep(2)

    # Switch to that network now
    status = _("Enabling LTSP network in Network Manager")
    if enable_network("LTSP",interface) != 0:
        # Something went wrong
        status = _("Failed")
        return False

    # Install the needed packages
    status = _("Installing the required packages")
    cmd=["apt-get", "install", "--no-install-recommends", "-qq", "-y", "ltsp-server", "openssh-server", "ldm-server"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    # FIXME: ugly hack for inotify not working on overlayfs
    status = _("Starting OpenSSH server")
    cmd=["initctl", "reload-configuration"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()
    cmd=["initctl", "start", "ssh"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    # Kill inetd as it's not needed anyway
    status = _("Restarting openbsd-inetd")
    cmd=["/etc/init.d/openbsd-inetd","restart"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    # Create LTSP configuration
    status = _("Configuring LTSP")
    os.makedirs("/var/lib/tftpboot/ltsp/i386", mode=0o755)
    ltsconf=open("/var/lib/tftpboot/ltsp/i386/lts.conf","w+")
    ltsconf.write("""
[default]
LDM_DIRECTX=True
LDM_SSHOPTIONS="-o StrictHostKeyChecking=no -o CheckHostIP=no -o LogLevel=silent"
LDM_GUESTLOGIN=True
LANG=en_US.UTF-8
LANGUAGE=%s
LDM_LANGUAGE=%s
""" % (os.getenv("LANG","en_US.UTF-8"), os.getenv("LANG","en_US.UTF-8")))

    # Edubuntu theming
    if os.path.exists("/cdrom/README.diskdefines") and "Edubuntu" in open("/cdrom/README.diskdefines").read():
        ltsconf.write("LDM_THEME=edubuntu\n")
    ltsconf.close()

    # NBD
    nbdconf=open("/etc/nbd-server/conf.d/ltsp.conf","w+")
    nbdconf.write("""
[ltsp]
exportname = /cdrom/ltsp/i386.img
readonly = true
""")
    nbdconf.close()

    # Create LTSP Guest users
    status = _("Creating the guest users")
    newusers=open("/tmp/userlist","w+")
    for user in range(50,151):
        newusers.write("ltsp%s:ltsp%s:%s:1000:LTSP Guest:/home/ltsp%s:/bin/bash\n" % (user, user, 2000+user, user))
    newusers.close()

    cmd=["newusers","/tmp/userlist"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    # Configuring dnsmasq
    status = _("Configuring DNSmasq")
    dnsmasq=open("/tmp/dnsmasq-ltsp-livecd.conf","w+")
    dnsmasq.write("""
pxe-prompt="Starting Edubuntu Live LTSP... Press F8 for boot menu.", 3
pxe-service=X86PC, "Boot from network", /ltsp/i386/pxelinux
pxe-service=X86PC, "Boot from local hard disk", 0
enable-tftp
tftp-root=/var/lib/tftpboot
dhcp-boot=/ltsp/i386/pxelinux.0
dhcp-option=vendor:PXEClient,6,2b
dhcp-no-override
dhcp-range=192.168.0.50,192.168.0.150,8h
bind-interfaces
listen-address=192.168.0.1
except-interface=lo
""")
    dnsmasq.close()

    # Call dnsmasq -C /tmp/dnsmasq-ltsp-livecd.conf
    status = _("Starting DNSmasq")
    cmd=["dnsmasq", "-C", "/tmp/dnsmasq-ltsp-livecd.conf"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    # Set up kernels, etc:
    status = _("Extracting thin client kernel and initrd")
    os.makedirs("/opt/ltsp/i386",0o755)

    cmd=["mount", "/cdrom/ltsp/i386.img", "/opt/ltsp/i386", "-o", "loop"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    cmd=["ltsp-update-kernels"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    cmd=["umount", "/opt/ltsp/i386"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    # Configure tftp
    cmd=["sed", "-i", "s/vt.handoff=7/vt.handoff=7 nbdroot=:ltsp/g", "/var/lib/tftpboot/ltsp/i386/pxelinux.cfg/default"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    # Start nbd-server
    status = _("Starting NBD server")
    cmd=["/etc/init.d/nbd-server", "start"]
    runcmd=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    runcmd.wait()

    status = _("Ready")

def start_ui():
    builder = gtk.Builder()
    builder.set_translation_domain("ltsp-live")
    if os.path.exists("ltsp-live.xml"):
        xml_file = "ltsp-live.xml"
    elif os.path.exists("/usr/share/ltsp-live/ltsp-live.xml"):
        xml_file = "/usr/share/ltsp-live/ltsp-live.xml"
    else:
        sys.exit(1)

    builder.add_from_file(xml_file)

    winLTSP = builder.get_object("winLTSP")
    hbox2 = builder.get_object("hbox2")
    label2 = builder.get_object("label2")
    cmbInterface = builder.get_object("cmbInterface")
    btnStart = builder.get_object("btnStart")

    # Populate drop-down
    model_interface = gtk.ListStore(str,str)
    cmbInterface.set_model(model_interface)
    cell = gtk.CellRendererText()
    cmbInterface.pack_start(cell, False)
    cmbInterface.add_attribute(cell,'text',0)

    entry_cache=[]
    devices={}

    def check_remove_entry(model, path, rowiter, data):
        if model.get_value(rowiter, 1) == data:
            model.remove(rowiter)
            return True
        return False

    def update_interfaces():
        if winLTSP.get_sensitive() == False:
            return True

        new_devices = list_devices()
        if not new_devices and len(devices) != 0:
            return True

        devices.clear()
        devices.update(new_devices)

        if len(devices) == 0:
            if len(model_interface) == 1 and model_interface[0][1] == "None":
                return True

            for entry in entry_cache:
                entry_cache.remove(entry)
            model_interface.clear()
            model_interface.append([_("None"),"None"])
            cmbInterface.set_active(0)
            cmbInterface.set_sensitive(False)
            btnStart.set_sensitive(False)
            return True

        if len(entry_cache) == 0:
            cmbInterface.set_sensitive(True)
            btnStart.set_sensitive(True)
            model_interface.clear()

        # Add new entries
        for device in devices:
            entry=["%s (%s)" % (device, devices[device][0]),device]
            if entry not in entry_cache:
                model_interface.append(entry)
                entry_cache.append(entry)

        # Remove old entries
        for entry in entry_cache:
            if entry[1] not in devices:
                model_interface.foreach(check_remove_entry,(entry[1]))
                entry_cache.remove(entry)

        cmbInterface.set_active(0)
        return True

    update_interfaces()
    gobject.timeout_add(2000, update_interfaces)

    def update_ui(winLTSP, hbox2, label2):
        global status

        winLTSP.set_sensitive(False)
        hbox2.set_visible(True)
        label2.set_text(status)

        if status == _("Ready"):
            dialog = gtk.MessageDialog(winLTSP,
                gtk.DialogFlags.DESTROY_WITH_PARENT,
                gtk.MessageType.INFO,
                gtk.ButtonsType.CLOSE,
                _("LTSP-Live should now be ready to use!"))
            dialog.run()
            dialog.destroy()
            sys.exit(0)
            return False
        elif status == _("Failed"):
            dialog = gtk.MessageDialog(winLTSP,
                    gtk.DialogFlags.DESTROY_WITH_PARENT,
                    gtk.MessageType.ERROR,
                    gtk.ButtonsType.CLOSE,
                    _("Unable to configure Network Manager"))
            dialog.run()
            dialog.destroy()

            hbox2.set_visible(False)
            winLTSP.set_sensitive(True)
            return False
        else:
            return True

    def start(button):
        global status

        interface = model_interface[cmbInterface.get_active()][1]
        if devices[interface][1] == "connected":
            warning=gtk.MessageDialog(
                parent=winLTSP,
                flags=gtk.DialogFlags.MODAL,
                message_type=gtk.MessageType.WARNING,
                buttons=gtk.ButtonsType.OK_CANCEL,
                message_format=_("The selected network interface is already in use.\nAre you sure you want to use it?"))
            retval = warning.run()
            warning.destroy()
            if retval != gtk.ResponseType.OK:
                return

        status = ""
        gobject.timeout_add(500, update_ui, winLTSP, hbox2, label2)
        t = Thread(target=start_ltsplive, args=(interface,))
        t.start()

    builder.connect_signals({
        "on_winLTSP_destroy" : gtk.main_quit,
        "on_btnCancel_clicked" : gtk.main_quit,
        "on_btnStart_clicked" : start,
    })

    winLTSP.show()
    gobject.threads_init()
    gtk.main()

start_ui()
