#!/usr/bin/env python
#
# -*-python-*-
#
# indicator-network - user interface for connman
# Copyright 2010 Canonical Ltd.
#
# Authors:
# Kalle Valo <kalle.valo@canonical.com>
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
#

import pygtk
pygtk.require('2.0')
import gtk
import dbus
import dbus.mainloop.glib
import gobject
import locale
import gettext

# FIXME: translate these
IPV4_METHOD_DHCP = "dhcp"
IPV4_METHOD_MANUAL = "manual"

class Service():

    def get_name(self):
        return self.properties["Name"]

    def get_path(self):
        return self.path

    def get_autoconnect(self):
        return self.properties["AutoConnect"] == dbus.Boolean(1)

    def set_autoconnect(self, autoconnect):
        if autoconnect:
            value = dbus.Boolean(True)
        else:
            value = dbus.Boolean(False)

        self.service.SetProperty("AutoConnect", value);

    def get_nameservers_configuration(self):
        val = self.properties["Nameservers.Configuration"]
        if val != None:
            return " ".join(val)
        else:
            return ""

    def set_nameservers_configuration(self, nameservers):
        value = nameservers.split()
        self.service.SetProperty("Nameservers.Configuration",
                                 dbus.Array(value, signature="s"))

    def get_domains_configuration(self):
        val = self.properties["Domains.Configuration"]
        if val != None:
            return " ".join(val)
        else:
            return ""

    def set_domains_configuration(self, domains):
        value = domains.split()
        self.service.SetProperty("Domains.Configuration",
                                 dbus.Array(value, signature="s"))

    def get_ipv4_configuration_method(self):
        d = self.properties["IPv4.Configuration"]
        if d.has_key('Method'):
            return d['Method']
        else:
            return ""

    def get_ipv4_configuration_address(self):
        d = self.properties["IPv4.Configuration"]
        if d.has_key('Address'):
            return d['Address']
        else:
            return ""

    def get_ipv4_configuration_netmask(self):
        d = self.properties["IPv4.Configuration"]
        if d.has_key('Netmask'):
            return d['Netmask']
        else:
            return ""

    def get_ipv4_configuration_gateway(self):
        d = self.properties["IPv4.Configuration"]
        if d.has_key('Gateway'):
            return d['Gateway']
        else:
            return ""

    def set_ipv4_configuration(self, ipv4):
        self.service.SetProperty("IPv4.Configuration", ipv4)

    def remove(self):
        if not self.is_favorite():
            return

        self.service.Remove()

    def is_favorite(self):
        if not "Favorite" in self.properties:
            return False

        return self.properties["Favorite"] == dbus.Boolean(1)

    def update_property(self, name, value):
        self.properties[name] = value
        # FIXME: emit property-changed signal

    def __init__(self, service, path, properties):
        self.service = service
        self.path = path
        self.properties = properties

class EthernetService(Service):

    def get_type(self):
        return "ethernet"

class WifiService(Service):

    def get_type(self):
        return "wifi"

    def uses_passphrase(self):
        return self.properties["Security"] != "none"

    def get_passphrase(self):
        return self.properties["Passphrase"]

    def set_passphrase(self, passphrase):
        self.service.SetProperty("Passphrase", passphrase);

class BluetoothService(Service):

    def get_type(self):
        return "bluetooth"

class CellularService(Service):

    def get_type(self):
        return "cellular"

    def get_apn(self):
        return self.properties["APN"]

    def set_apn(self, apn):
        self.service.SetProperty("APN", apn);

class ConnManManager(gobject.GObject):
    def get_properties(self):
        return self.properties

    def is_available(self, technology):
        if technology in self.properties["AvailableTechnologies"]:
            return True

        return False

    def is_enabled(self, technology):
        if technology in self.properties["EnabledTechnologies"]:
            return True

        return False

    def is_connected(self, technology):
        if technology in self.properties["ConnectedTechnologies"]:
            return True

        return False

    def enable_technology(self, technology):
        if not self.is_available(technology):
            return

        if self.is_enabled(technology):
            return

        self.manager.EnableTechnology(technology)

    def disable_technology(self, technology):
        if not self.is_available(technology):
            return

        if not self.is_enabled(technology):
            return

        self.manager.DisableTechnology(technology)

    def get_available_technologies(self):
        return self.properties["AvailableTechnologies"]

    def request_scan(self, technology=""):
        self.manager.RequestScan(technology)

    def service_property_changed(self, name, value, path, interface):
        service = self.get_service(path)

        if not service:
            return

        service.update_property(name, value)

    def manager_property_changed(self, name, value, path, interface):
        self.properties[name] = value

        if name == "Services":
            self.update_services()
            self.emit("services-changed")
        elif name in ["AvailableTechnologies", "EnabledTechnologies",
                      "ConnectedTechnologies"]:
            self.emit("technologies-changed")

    def add_service(self, path):
        service = dbus.Interface(self.bus.get_object("org.moblin.connman",
                                                     path),
                                 "org.moblin.connman.Service")

        try:
            properties = service.GetProperties()
        except dbus.exceptions.DBusException as e:
            print "GetProperties failed for service %s: %s" % (path, e)
            return

        service_type = properties["Type"]
        if service_type == "ethernet":
            self.services[path] = EthernetService(service, path, properties)
        elif service_type == "wifi":
            self.services[path] = WifiService(service, path, properties)
        elif service_type == "bluetooth":
            self.services[path] = BluetoothService(service, path, properties)
        elif service_type == "cellular":
            self.services[path] = CellularService(service, path, properties)
        else:
            print "unknown service '%s': %s" % (service_type, path)

    def get_services(self):
        return self.services.values()

    def get_services_by_technology(self, technology):
        result = []
        for service in self.get_services():
            if service.get_type() == technology:
                result.append(service)

        return result

    def get_service(self, path):
        if path not in self.services:
            return None

        return self.services[path]

    def update_services(self):
        self.services = {}

        for path in self.properties["Services"]:
            self.add_service(path)

    def __init__(self):
        self.__gobject_init__()
        self.services = {}
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
	self.bus = dbus.SystemBus()

        self.bus.add_signal_receiver(self.service_property_changed,
                                     bus_name="org.moblin.connman",
                                     dbus_interface="org.moblin.connman.Service",
                                     signal_name = "PropertyChanged",
                                     path_keyword="path",
                                     interface_keyword="interface")

	self.manager = dbus.Interface(self.bus.get_object("org.moblin.connman",
                                                          "/"),
                                      "org.moblin.connman.Manager")
        self.bus.add_signal_receiver(self.manager_property_changed,
                                     bus_name="org.moblin.connman",
                                     dbus_interface="org.moblin.connman.Manager",
                                     signal_name = "PropertyChanged",
                                     path_keyword="path",
                                     interface_keyword="interface")

        self.properties = self.manager.GetProperties()

        # FIXME: handle if connmand disappears
        # FIXME: how to handle ordering and other changes in service list?

        self.update_services()


gobject.type_register(ConnManManager)
gobject.signal_new("technologies-changed", ConnManManager,
                   gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
gobject.signal_new("services-changed", ConnManManager,
                   gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())

# service properties:
#
# * passphrase (wifi)
# * autoconnect (all)
#   * boolean
# * apn (cellular)
# * nameservers (all)
#   * list
# * domains (all)
#   * list
# * ipv4 (ethernet, wifi, bluetooth)
#   * dhcp, manual (and off)
#   * manual: address, netmask, gateway

class NetworkEditDialog(gtk.Dialog):

    def add_autoconnect(self):
        box = gtk.HBox()
        label = gtk.Label(_("Autoconnect:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        button = gtk.CheckButton()
        self.autoconnect_button = button
        button.set_active(self.service.get_autoconnect())
        box.pack_start(button, False, False)
        self.vbox.pack_start(box)

    def set_autoconnect(self):
        active = self.autoconnect_button.get_active()
        if self.service.get_autoconnect() != active:
            self.service.set_autoconnect(active)

    def add_nameservers(self):
        box = gtk.HBox()
        label = gtk.Label(_("Nameservers:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        entry = gtk.Entry()
        self.nameservers_entry = entry
        entry.set_width_chars(30)
        entry.set_text(self.service.get_nameservers_configuration())
        box.pack_start(entry)
        self.vbox.pack_start(box)

    def set_nameservers(self):
        servers = self.nameservers_entry.get_text()
        if self.service.get_nameservers_configuration() != servers:
            self.service.set_nameservers_configuration(servers)

    def add_domains(self):
        box = gtk.HBox()
        label = gtk.Label(_("Domains:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        entry = gtk.Entry()
        self.domains_entry = entry
        entry.set_width_chars(30)
        entry.set_text(self.service.get_domains_configuration())
        box.pack_start(entry)
        self.vbox.pack_start(box)

    def set_domains(self):
        domains = self.domains_entry.get_text()
        if self.service.get_domains_configuration() != domains:
            self.service.set_domains_configuration(domains)

    def set_ipv4_sensitivity(self, method):
        if method == IPV4_METHOD_MANUAL:
            sensitive = True
        else:
            sensitive = False

        self.ipv4_address_entry.set_sensitive(sensitive)
        self.ipv4_netmask_entry.set_sensitive(sensitive)
        self.ipv4_gateway_entry.set_sensitive(sensitive)

    def ipv4_method_changed(self, combobox):
        method = combobox.get_active_text()
        self.set_ipv4_sensitivity(method)

    def add_ipv4(self):
        method = self.service.get_ipv4_configuration_method()

        if method == "fixed":
            return

        box = gtk.HBox()
        label = gtk.Label(_("IPv4:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        combo = gtk.combo_box_new_text()
        self.ipv4_method_combo = combo
        combo.insert_text(0, IPV4_METHOD_DHCP)
        combo.insert_text(1, IPV4_METHOD_MANUAL)

        if method == IPV4_METHOD_MANUAL:
            combo.set_active(1)
        else:
            combo.set_active(0)

        combo.connect("changed", self.ipv4_method_changed)
        box.pack_start(combo, False, False)
        self.vbox.pack_start(box)

        box = gtk.HBox()
        label = gtk.Label(_("IPv4 address:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        entry = gtk.Entry()
        self.ipv4_address_entry = entry
        entry.set_text(self.service.get_ipv4_configuration_address())
        box.pack_start(entry)
        self.vbox.pack_start(box)

        box = gtk.HBox()
        label = gtk.Label(_("IPv4 netmask:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        entry = gtk.Entry()
        self.ipv4_netmask_entry = entry
        entry.set_text(self.service.get_ipv4_configuration_netmask())
        box.pack_start(entry)
        self.vbox.pack_start(box)

        box = gtk.HBox()
        label = gtk.Label(_("IPv4 gateway:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        entry = gtk.Entry()
        self.ipv4_gateway_entry = entry
        entry.set_text(self.service.get_ipv4_configuration_gateway())
        box.pack_start(entry)
        self.vbox.pack_start(box)

        self.set_ipv4_sensitivity(method)

    def set_ipv4_dhcp(self):
        current = self.service.get_ipv4_configuration_method()
        if current != IPV4_METHOD_DHCP:
            value = { "Method": IPV4_METHOD_DHCP }
            self.service.set_ipv4_configuration(value)

    def set_ipv4_manual(self):
        changed = False

        current_method = self.service.get_ipv4_configuration_method()
        if current_method != IPV4_METHOD_MANUAL:
            changed = True

        address = self.ipv4_address_entry.get_text()
        if self.service.get_ipv4_configuration_address() != address:
            changed = True

        netmask = self.ipv4_netmask_entry.get_text()
        if self.service.get_ipv4_configuration_netmask() != netmask:
            changed = True

        gateway = self.ipv4_gateway_entry.get_text()
        if self.service.get_ipv4_configuration_gateway() != gateway:
            changed = True

        if changed:
            value = { "Method": IPV4_METHOD_MANUAL,
                      "Address": address,
                      "Netmask": netmask,
                      "Gateway": gateway }
            self.service.set_ipv4_configuration(value)


    def set_ipv4(self):
        method = self.ipv4_method_combo.get_active_text()
        if method == IPV4_METHOD_DHCP:
            self.set_ipv4_dhcp()
        elif method == IPV4_METHOD_MANUAL:
            self.set_ipv4_manual()
        else:
            print "Unknown ipv4 method: %s" % method

    def response(self, dialog, response_id):
        if response_id == gtk.RESPONSE_ACCEPT:
            self.store_settings()

        self.destroy()

    def __init__(self, service):
        buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                   gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
        gtk.Dialog.__init__(self, _("Edit Network"), buttons = buttons)
        self.connect("response", self.response)

        self.service = service
        label = gtk.Label("%s (%s)" % (self.service.get_name(),
                                       self.service.get_type()))
        self.vbox.pack_start(label)

        self.sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)

class WifiEditDialog(NetworkEditDialog):

    def add_passphrase(self):
        box = gtk.HBox()
        label = gtk.Label(_("Passphrase:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        entry = gtk.Entry()
        self.passphrase_entry = entry
        entry.set_width_chars(30)
        entry.set_text(self.service.get_passphrase())
        box.pack_start(entry)
        self.vbox.pack_start(box)

    def set_passphrase(self):
        self.service.set_passphrase(self.passphrase_entry.get_text())

    def store_settings(self):
        if self.service.uses_passphrase():
            self.set_passphrase()
        self.set_autoconnect()
        self.set_nameservers()
        self.set_domains()
        self.set_ipv4()
        
    def __init__(self, service):
        NetworkEditDialog.__init__(self, service)

        if service.uses_passphrase():
            self.add_passphrase()
        self.add_autoconnect()
        self.add_nameservers()
        self.add_domains()
        self.add_ipv4()

        self.show_all()

class BluetoothEditDialog(NetworkEditDialog):

    def store_settings(self):
        self.set_autoconnect()
        self.set_nameservers()
        self.set_domains()
        self.set_ipv4()

    def __init__(self, service):
        NetworkEditDialog.__init__(self, service)

        self.add_autoconnect()
        self.add_nameservers()
        self.add_domains()
        self.add_ipv4()

        self.show_all()

class EthernetEditDialog(NetworkEditDialog):

    def store_settings(self):
        self.set_autoconnect()
        self.set_nameservers()
        self.set_domains()
        self.set_ipv4()

    def __init__(self, service):
        NetworkEditDialog.__init__(self, service)

        self.add_autoconnect()
        self.add_nameservers()
        self.add_domains()
        self.add_ipv4()

        self.show_all()

class CellularEditDialog(NetworkEditDialog):

    def add_apn(self):
        box = gtk.HBox()
        label = gtk.Label(_("APN:"))
        self.sizegroup.add_widget(label)
        box.pack_start(label, False, False)
        entry = gtk.Entry()
        self.apn_entry = entry
        entry.set_text(self.service.get_apn())
        box.pack_start(entry)
        self.vbox.pack_start(box)

    def set_apn(self):
        apn = self.apn_entry.get_text()
        if self.service.get_apn() != apn:
            self.service.set_apn(apn)

    def store_settings(self):
        self.set_autoconnect()
        self.set_apn()
        self.set_nameservers()
        self.set_domains()

    def __init__(self, service):
        NetworkEditDialog.__init__(self, service)

        self.add_autoconnect()
        self.add_apn()
        self.add_nameservers()
        self.add_domains()

        self.show_all()

class NetworkSettings:

    def delete_event(self, widget, event, data=None):
        return False

    def destroy(self, widget, data=None):
        gtk.main_quit()

    def edit_clicked(self, widget):
        selection = self.treeview.get_selection()
        (model, iterator) = selection.get_selected()
        if iterator == None:
            return
        
        path = model.get_value(iterator, 0)
        service = self.cmm.get_service(path)

        if isinstance(service, WifiService):
            WifiEditDialog(service)
        elif isinstance(service, EthernetService):
            EthernetEditDialog(service)
        elif isinstance(service, BluetoothService):
            BluetoothEditDialog(service)
        if isinstance(service, CellularService):
            CellularEditDialog(service)


    def remove_clicked(self, widget):
        selection = self.treeview.get_selection()
        (model, iterator) = selection.get_selected()
        if iterator == None:
            return
        
        path = model.get_value(iterator, 0)
        service = self.cmm.get_service(path)
        service.remove()

    def update_services(self, technology):
        self.connectionsstore.clear()

        services = self.cmm.get_services_by_technology(technology)
        for service in services:
            self.connectionsstore.append([service.get_path(),
                                          service.get_name()])

    def services_updated(self, cmm):
        self.update_services(self.current_technology)

    def clear_status(self, model, path, iterator):
        model[iterator][1] = False

    def technology_chosen(self, selection):
        (model, iterator) = selection.get_selected()
        if iterator == None:
            return

        model.foreach(self.clear_status)

        model[iterator][1] = True
        
        self.current_technology = model.get_value(iterator, 0)
        self.show_technology(self.current_technology)

    def show_technology(self, technology):
        if self.cmm.is_enabled(technology):
            if self.cmm.is_connected(technology):
                text = _("%s is on and connected to the Internet") % (technology)
            else:
                text = _("%s is on") % (technology)
        else:
            text = _("%s is off") % (technology)

        self.statuslabel.set_text(text)

        if self.cmm.is_enabled(technology):
            self.status_enabled_button.set_active(True)
        else:
            self.status_disabled_button.set_active(True)

        self.update_services(technology)

    def technologies_updated(self, cmm):
        self.technologystore.clear()
        first = True

        technologies = self.cmm.get_available_technologies()
        for technology in technologies:
            chosen = False
            if not self.current_technology and first:
                self.current_technology = technology
                chosen = True

            first = False

            if self.current_technology == technology:
                chosen = True

            if technology == "ethernet":
                icon_name = "nm-device-wired"
            elif technology == "wifi":
                icon_name = "nm-signal-100"
            elif technology == "cellular":
                icon_name = "nm-device-wwan"
            elif technology == "bluetooth":
                icon_name = "bluetooth"
            else:
                # FIXME: find out proper icon for unknown technologies
                icon_name = "system-users"

            # FIXME: what's the proper size for the icon?
            img = gtk.icon_theme_get_default().load_icon(icon_name, 32, 0)
            self.technologystore.append([technology, chosen, img])

        self.show_technology(self.current_technology)

    def technology_toggled(self, togglebutton):
        if self.status_enabled_button.get_active():
            self.cmm.enable_technology(self.current_technology)
        else:
            self.cmm.disable_technology(self.current_technology)
            
    def __init__(self):
        self.cmm = ConnManManager()
        self.current_technology = None

        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title(_("Network Settings (beta)"))
        self.window.connect("delete_event", self.delete_event)
        self.window.connect("destroy", self.destroy)

        notebook = gtk.Notebook()
        notebook.set_show_tabs(False)
        self.window.add(notebook)

        vbox = gtk.VBox()
        notebook.append_page(vbox)

        window_box = gtk.HBox()
        vbox.pack_start(window_box, False, False, 15)

        swindow = gtk.ScrolledWindow()
        swindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
        swindow.set_shadow_type(gtk.SHADOW_OUT)
        window_box.pack_start(swindow, False, False, 15)

        self.technologystore = gtk.ListStore(str, bool, gtk.gdk.Pixbuf)

        self.technologyview = gtk.TreeView(self.technologystore)
        self.technologyview.set_headers_visible(False)
        selection = self.technologyview.get_selection()
        selection.connect("changed", self.technology_chosen)
        swindow.add(self.technologyview)

        column = gtk.TreeViewColumn(_("Icon"))
        self.technologyview.append_column(column)
        cell = gtk.CellRendererPixbuf()
        column.pack_start(cell, True)
        column.add_attribute(cell, 'pixbuf', 2)

        column = gtk.TreeViewColumn(_("Network"))
        self.technologyview.append_column(column)
        cell = gtk.CellRendererText()
        column.pack_start(cell, True)
        column.add_attribute(cell, 'text', 0)

        connectionbox = gtk.VBox()
        window_box.pack_start(connectionbox, True, True, 15)

        frame = gtk.Frame()
        connectionbox.pack_start(frame, False, False, 0)

        statusbox = gtk.HBox()
        frame.add(statusbox)

        self.statuslabel = gtk.Label("")
        statusbox.pack_start(self.statuslabel, False, False, 12)

        self.status_enabled_button = gtk.RadioButton(None, _("On"))
        self.status_enabled_button.connect("toggled", self.technology_toggled)
        statusbox.pack_end(self.status_enabled_button, False, False, 12)

        self.status_disabled_button = gtk.RadioButton(self.status_enabled_button,
                                                      _("Off"))

        statusbox.pack_end(self.status_disabled_button, False, False, 12)

        self.connectionsstore = gtk.ListStore(str, str)

        swindow = gtk.ScrolledWindow()
        swindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
        swindow.set_shadow_type(gtk.SHADOW_OUT)
        connectionbox.pack_start(swindow, False, False, 12)

        self.treeview = gtk.TreeView(self.connectionsstore)
        self.treeview.set_size_request(200, 300)
        swindow.add(self.treeview)

        self.tvcolumn = gtk.TreeViewColumn(_("Network"))
        self.treeview.append_column(self.tvcolumn)

        self.cell = gtk.CellRendererText()
        self.tvcolumn.pack_start(self.cell, True)

        self.tvcolumn.add_attribute(self.cell, 'text', 1)

        buttonsbox = gtk.HBox()

        button = gtk.Button(_("Edit"))
        button.connect("clicked", self.edit_clicked)
        buttonsbox.pack_start(button, False, False, 0)

        button = gtk.Button(_("Remove"))
        button.connect("clicked", self.remove_clicked)
        buttonsbox.pack_start(button, False, False, 12)

        connectionbox.pack_end(buttonsbox, False, False, 0)

        self.technologies_updated(self.cmm)
        self.cmm.connect("technologies-changed", self.technologies_updated)
        self.cmm.connect("services-changed", self.services_updated)

        self.window.show_all()

def main():
    settings = NetworkSettings()
    gtk.main()

if __name__ == "__main__":
    APP = 'indicator-network'
    DIR = '/usr/share/locale'
    locale.setlocale(locale.LC_ALL, '')
    gettext.bindtextdomain(APP, DIR)
    gettext.textdomain(APP)
    _ = gettext.gettext

    main()
