#!/usr/bin/python
VERSION = "0.21.1"

import sys
import time
import dbus
from networkmanager import NetworkManager
from networkmanager.monitor import Monitor
from networkmanager.applet import NetworkManagerSettings, SYSTEM_SERVICE, USER_SERVICE
from networkmanager.applet.service import NetworkManagerUserSettings
import networkmanager.applet.settings as settings
from networkmanager.util import Table

# must be set before we ask for signals
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
# for calling quit
import gobject
loop = gobject.MainLoop()
LOOP = False

from optparse import OptionParser

op = OptionParser(version="%prog " + VERSION)

op.add_option("-t", "--terse",
              action="store_true", default=False,
              help="No table headings and padding, suitable for parsing")

# TODO http://docs.python.org/lib/optparse-adding-new-types.html
op.add_option("-w", "--wifi",
              choices=["0","1","off","on","no","yes","false","true"],
              metavar="BOOL",
              help="Enable or disable wireless")
op.add_option("-o", "--online",
              choices=["0","1","off","on","no","yes","false","true"],
              metavar="BOOL",
              help="Enable or disable network at all")
op.add_option("--state",
              action="store_true", default=False,
              help="Print the NM state")
op.add_option("--we", "--wireless-enabled",
              action="store_true", default=False,
              help="Print whether the WiFi is enabled")
op.add_option("--whe", "--wireless-hardware-enabled",
              action="store_true", default=False,
              help="Print whether the WiFi hardware is enabled")

op.add_option("-d", "--device-list", "--dev",
              action="store_true", default=False,
              help="List devices")
op.add_option("--device-info", "--di",
              help="Info about device DEV (by interface or UDI(TODO))",
              metavar="DEV")

op.add_option("-a", "--ap-list", "--ap", "-n", "--nets",# -n is a stopgap
              action="store_true", default=False,
              help="List access points")
op.add_option("--ap-info", "--ai",
              help="Info about access point AP (by hw address or UDI(TODO))",
              metavar="AP")

op.add_option("-u", "--usrcon",
              action="store_true", default=False,
              help="List user connection settings")
op.add_option("-s", "--syscon",
              action="store_true", default=False,
              help="List system connection settings")
op.add_option("--con-info", "--ci",
              help="Info about connection settings ID (of the *user*/system KIND)",
              metavar="[KIND,]ID")

op.add_option("-c", "--actcon",
              action="store_true", default=False,
              help="List active connections")

op.add_option("--demo",
              action="store_true", default=False,
              help="Run a random demonstration of the API")
op.add_option("--activate-connection",
              help="activate the KIND(user/system) connection ID on device DEV using APMAC.",
              metavar="[KIND],ID,[DEV],[APMAC]")
op.add_option("-m", "--monitor",
              action="store_true", default=False,
              help="loop to show dbus signals")

op.add_option("-C", "--connect",
              help="Connect to a wireless network SSID (creating the configuration using the key options below)",
              metavar="SSID")
op.add_option("--unprotected",
              action="store_true", default=False,
              help="network does not require a key")
op.add_option("--wep-hex",
              metavar="KEY",
              help="use this WEP key of 26 hex digits")
op.add_option("--wep-pass",
              metavar="KEY",
              help="use this WEP passphrase")
op.add_option("--wpa-psk-hex",
              metavar="KEY",
              help="use this WPA key of 64 hex digits")
op.add_option("--wpa-pass",
	      metavar="KEY",
	      help="use this WPA passphrase")

(options, args) = op.parse_args()

Table.terse = options.terse

nm = NetworkManager()

true_choices =  ["1", "on", "yes", "true"]
if options.wifi != None:
    nm["WirelessEnabled"] = options.wifi in true_choices
if options.we:
    print nm["WirelessEnabled"]
if options.online != None:
    try:
        nm.Sleep(not options.online in true_choices)
    except dbus.exceptions.DBusException, e:
        if e.get_dbus_name() != "org.freedesktop.NetworkManager.AlreadyAsleepOrAwake":
            raise

if options.state:
    print nm["State"]
if options.whe:
    print nm["WirelessHardwareEnabled"]
# style option: pretend that properties are methods (er, python properties)
# nm["WirelessEnabled"] -> nm.WirelessEnabled() (er, nm.WirelessEnabled )

if options.device_list:
    devs = nm.GetDevices()
    t = Table("Interface", "Type", "State")
    for dev in devs:
        t.row(dev["Interface"], dev["DeviceType"], dev["State"])
    print t

# --device-info, TODO clean up 
def get_device(dev_spec, hint):
    candidates = []
#    print "Hint:", hint
    devs = NetworkManager().GetDevices()
    for dev in devs:
#        print dev
        if dev._settings_type() == hint:
            candidates.append(dev)
#    print "Candidates:", candidates
    if len(candidates) == 1:
        return candidates[0]
    for dev in devs:
        if dev["Interface"] == dev_spec:
            return dev
    print "Device '%s' not found" % dev_spec
    return None

if options.device_info != None:
    d = get_device(options.device_info, "no hint")
    if d == None:
        print "not found"
    else:
        props = ["Udi", "Interface", "Driver", "Capabilities",
                 # "Ip4Address", # bogus, remembers last addr even after disused
                 "State",
                 # "Ip4Config", "Dhcp4Config", # only __repr__
               "Managed", "DeviceType"]
        if d._settings_type() == "802-11-wireless":
            props.extend(["Mode", "WirelessCapabilities"])
        elif d._settings_type() == "802-3-ethernet":
            props.extend(["Carrier"])

        print Table.from_items(d, *props)

if options.ap_list:
    devs = nm.GetDevices()
    # nm.get_wifi_devices()
    # nm.get_devices_by_type(Device.Type.WIRELESS)
    for dev in filter(lambda d: d._settings_type() == "802-11-wireless", devs):
        aap = dev["ActiveAccessPoint"]
        t = Table("Active", "HwAddress", "Ssid")
        for ap in dev.GetAccessPoints():
            active = "*" if ap.object_path == aap.object_path else ""
            t.row(active, ap["HwAddress"], ap["Ssid"])
        print t

if options.ap_info != None:
    devs = nm.GetDevices()
    for dev in filter(lambda d: d._settings_type() == "802-11-wireless", devs):
        aap = dev["ActiveAccessPoint"]
        for ap in dev.GetAccessPoints():
            if ap["HwAddress"] == options.ap_info:
                t = Table.from_items(ap, "Flags", "WpaFlags", "RsnFlags",
                               "Ssid", "Frequency", "HwAddress",
                               "Mode", "MaxBitrate", "Strength")
                t.row("Active", ap.object_path == aap.object_path)
                print t
                
#def is_opath(x):
#    return is_instance(x, str) and x[0] == "/"

def get_service_name(svc):
    if svc == "" or svc == "user":
        svc = USER_SERVICE
    elif svc == "system":
        svc = SYSTEM_SERVICE
    return svc

# move this to networkmanagersettings
def get_connection(svc, conn_spec):
#    if is_opath(conn_spec):
#        return conn_spec
    applet = NetworkManagerSettings(get_service_name(svc))
    for conn in applet.ListConnections():
        cs = conn.GetSettings()
        if cs["connection"]["id"] == conn_spec:
            return conn
    print "Connection '%s' not found" % conn_spec
    return None

def get_connection_devtype(conn):
    cs = conn.GetSettings()
    return cs["connection"]["type"]

def list_connections(svc):
    acs = nm["ActiveConnections"]
    acos = map(lambda a: a["Connection"].object_path, acs)

    try:
        applet = NetworkManagerSettings(svc)
    except dbus.exceptions.DBusException, e:
        print e
        return
    t = Table("Active", "Name", "Type")
    for conn in applet.ListConnections():
        cs = conn.GetSettings()
        active = "*" if conn.object_path in acos else ""
        t.row(active, cs["connection"]["id"], cs["connection"]["type"])
    print t

if options.usrcon:
    list_connections(USER_SERVICE)
if options.syscon:
    list_connections(SYSTEM_SERVICE)

if options.con_info != None:
    (svc, con) = ("", options.con_info)
    if "," in con:
        (svc, con) = con.split(",")

    c = get_connection(svc, con)
    cs = c.GetSettings()
    print Table.from_nested_dict(cs)
    type = cs["connection"]["type"]
    secu = cs[type]["security"]
    cse = c.GetSecrets(secu, [], False)
    print Table.from_nested_dict(cse)

# this shows we do need to add __str__ to the objects
if options.actcon:
    acs = nm["ActiveConnections"]
    t = Table("State", "Name", "AP", "Devices", "Default route")
    for ac in acs:
        cid = ac["Connection"].GetSettings()["connection"]["id"]
        try:
            apmac = ac["SpecificObject"]["HwAddress"]
        except:                 # no AP for wired. TODO figure out "/" object
            apmac = ""
        devs = ", ".join(map(lambda d: d["Interface"], ac["Devices"]))
        hdr = "*" if ac["Default"] else ""
        t.row(ac["State"], cid, apmac, devs, hdr)
    print t

if options.monitor:
    m = Monitor()
    LOOP = True

def print_state_changed(*args):
    print time.strftime("(%X)"),
    print "State:", ", ".join(map(str,args))

if options.connect != None:
    ssid = options.connect
    try:
        us = NetworkManagerUserSettings([]) # request_name may fail
    except dbus.exceptions.NameExistsException, e:
        print "Another applet is running:", e
        sys.exit(1)

    c = None
    if options.unprotected:
        c = settings.WiFi(ssid)
    if options.wep_hex != None:
        c = settings.Wep(ssid, "", options.wep_hex)
    if options.wep_pass != None:
        c = settings.Wep(ssid, options.wep_pass)
    if options.wpa_psk_hex != None:
        c = settings.WpaPsk(ssid, "", options.wpa_psk_hex)
    if options.wpa_pass != None:
        c = settings.WpaPsk(ssid, options.wpa_pass)
    if c == None:
        print "Error, connection settings not specified"
        sys.exit(1)

    svc = USER_SERVICE
    svc_conn = us.addCon(c.conmap)
    hint = svc_conn.settings["connection"]["type"]
    dev = get_device("", hint)
    appath = "/"
    nm._connect_to_signal("StateChanged", print_state_changed)
    # must be async because ourselves are providing the service
    dummy_handler = lambda *args: None
    nm.ActivateConnection(svc, svc_conn, dev, appath,
                          reply_handler=dummy_handler,
                          error_handler=dummy_handler)
    LOOP = True

if options.activate_connection != None:
    (svc, conpath, devpath, appath) = options.activate_connection.split(',')
    svc = get_service_name(svc)
    conn = get_connection(svc, conpath)
    hint = get_connection_devtype(conn)
    dev = get_device(devpath, hint)
    if appath == "":
        appath = "/"
    nm._connect_to_signal("StateChanged", print_state_changed)
    # TODO make it accept both objects and opaths
    nm.ActivateConnection(svc, conn, dev, appath)
    # TODO (optionally) block only until a stable state is reached
    LOOP = True


######## demo ##########

from dbusclient import DBusMio
mio = DBusMio(dbus.SystemBus(), "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
i = mio.Introspect()
d = mio.GetDevices()

if options.demo:
    nm = NetworkManager()

# TODO: generic signal (adapt cnm monitor), print name and args

    nm["WirelessEnabled"] = "yes"

    devs = nm.GetDevices()

    for d in devs:
        print "\n DEVICE"
        # TODO: find API for any object
        d._connect_to_signal("StateChanged", print_state_changed)
        
    LOOP = True
######## demo end ##########

# TODO wrap this
if LOOP:
    try:
        print "Entering mainloop"
        loop.run()
    except KeyboardInterrupt:
        print "Loop exited"
