#!/usr/bin/python3
# -*- 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-2012 Stéphane Graber
# Authors:
#  * Stéphane Graber <stgraber@ubuntu.com>, 2011-2012

import gettext
import os
import subprocess
import sys
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 = ""

if os.geteuid() != 0:
    print("You need to be root to run this command.")
    sys.exit(1)


def generate_network(name, address):
    import uuid
    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'},
                              universal_newlines=True)
    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 = line.decode('utf-8').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, universal_newlines=True)
    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", "nbd-server",
           "ltspfs"]
    runcmd = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE, universal_newlines=True)
    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, universal_newlines=True)
    runcmd.wait()

    cmd = ["initctl", "start", "ssh"]
    runcmd = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE, universal_newlines=True)
    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, universal_newlines=True)
    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"
LDM_GUESTLOGIN=True
LDM_AUTOLOGIN=True
LANG=en_US.UTF-8
LANGUAGE=%s
LDM_LANGUAGE=%s
LDM_SESSION=gnome-fallback
""" % (os.getenv("LANG", "en_US.UTF-8"), os.getenv("LANG", "en_US.UTF-8")))

    # Edubuntu theming
    with open("/cdrom/README.diskdefines") as diskdefines:
        if os.path.exists("/cdrom/README.diskdefines") \
           and "Edubuntu" in 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/casper/ltsp.squashfs
readonly = true
""")
    nbdconf.close()

    # Create LTSP Guest users
    status = _("Creating the guest users")
    for user in range(50, 151):
        # Create a user
        cmd = ["useradd", "-c", "LTSP Guest", "-g", "999", "-m",
               "-u", str(2000 + user), "ltsp%s" % user]
        runcmd = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  universal_newlines=True)
        runcmd.wait()

        # Set the password
        runcmd = subprocess.Popen(["chpasswd"], stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  stdin=subprocess.PIPE,
                                  universal_newlines=True)
        runcmd.stdin.write("ltsp%s:ltsp%s\n" % (user, user))
        runcmd.stdin.close()
        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, universal_newlines=True)
    runcmd.wait()

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

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

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

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

    # Configure tftp
    cmd = ["sed", "-i", "s/splash/splash nbdroot=:ltsp/g",
           "/var/lib/tftpboot/ltsp/i386/pxelinux.cfg/default"]
    runcmd = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE, universal_newlines=True)
    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, universal_newlines=True)
    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 not winLTSP.get_sensitive():
            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.
Are 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()
