#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
import os

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

try:
    raw_input
except NameError:
    raw_input = input

try:
    from string import atoi
except ImportError:
    atoi = int


DEFAULT_XML_PATH = '/usr/share/mobile-broadband-provider-info/serviceproviders.xml'
DEFAULT_CONFIG_PATH = '/etc/wvdial.conf'
DEFAULT_MODEM_DEVICE = '/dev/ttyUSB0'
DEFAULT_PROFILE_NAME = 'DefaultProfile'


class Mkwvconf:

    #########
    # class members
    #########

    introMessage = """
mkwvconf automatically generates a dialer section for use in wvdial.conf based on mobile-broadband-provider-info.

If a provider is missing from the list, add a bug at http://bugzilla.gnome.org/enter_bug.cgi?product=NetworkManager (include your provider name, your country, your plans marketing name if you know it, and of course the APN you're using for data). For more information about the mobile broadband provider database, see http://blogs.gnome.org/dcbw/2009/06/22/mobile-broadband-assistant-makes-it-easy/ .

The configuration generated by mkwvconf overwrites CID 1 with your provider info ('Init2=AT+CGDCONT=1,...') which is then called by dialing *99***1# (the digit before the trailing '#' specifies which CID to use).

Further reading on APNs can be found here: http://mail.gnome.org/archives/networkmanager-list/2008-August/msg00191.html. Thanks goes to Antti Kaijanmäki for explanations and links!"""

    #########
    # class methods
    #########

    def __init__(self, opts=None):
        opts = opts or {}

        if 'configPath' in opts:
            self.configPath = opts['configPath']
            self._configPathIsCustom = True
        else:
            self.configPath = DEFAULT_CONFIG_PATH
            self._configPathIsCustom = False

        self.modemDevice = opts.get('modemDevice')
        self.profileName = opts.get('profileName')

        xmlPath = opts.get('xmlPath', DEFAULT_XML_PATH)
        self.doc = ET.parse(xmlPath)

    def displayIntro(self):
        os.system('clear')
        print(self.introMessage)

    def getCountryCodes(self):
        """returns a list of all country codes"""
        return [str(n.get('code')) for n in self.getNodesFromXml("country[@code]")]

    def selectCountryCode(self):
        """lets user choose a country code and returns the chosen value"""

        countryCodes = self.getCountryCodes()

        print("\nAvailable country codes:\n")
        print(countryCodes)

        country = ""

        while country not in countryCodes:
            country = raw_input("\nGet providers for which country code? : ")

        return country

    def getNodesFromXml(self, xquery):
        """returns results of xquery as a list"""
        return self.doc.findall(xquery)

    def getProviders(self, countryCode):
        """returns list of providers for countryCode"""
        nodes = self.getNodesFromXml("country[@code='%s']/provider/name" % countryCode)
        return [n.text for n in nodes]

    def selectProvider(self, countryCode):
        """lets user choose a provider and returns the chosen provider name"""
        providers = self.getProviders(countryCode)

        index = self.getUserChoice(providers, "Providers for '%s':" % countryCode, "Choose a provider")
        return providers[index]

    def getApns(self, countryCode, provider):
        providerNode = self.getNodesFromXml("country[@code='%s']/provider[name='%s']" % (countryCode, provider))[0]
        apns = providerNode.findall("*/apn")
        apnnames = [n.get("value") for n in apns]
        return apnnames

    def selectApn(self, countryCode, provider):
        apnnames = self.getApns(countryCode, provider)

        apncount = len(apnnames)
        if apncount == 1:
            return apnnames[0]

        index = self.getUserChoice(apnnames, "Available APNs:", "Choose an APN")
        return apnnames[index]

    def makeConfig(self, countryCode, provider, apnname):
        """assembles configuration section. the configuration is either written to wvdial.conf or printed for manual insertion"""
        if self._configPathIsCustom:
            editConf = True
        else:
            editConf = raw_input("\nDo you want me to try to modify %s (you will need superuser rights)? Y/n: " % self.configPath) in ["", "Y", "y"]
            os.system('clear')

        parameters = self.getConfigParameters(countryCode, provider, apnname)
        section = self.formatConfig(parameters)

        if editConf:
            self.writeConfig(parameters["profileName"], section)
        else:
            print("\n\nDone. Insert the following into %s and run '%s' to start the connection.\n\n" % (self.configPath, self.wvdialCommand(parameters)))
            print(section)

    def getConfigParameters(self, countryCode, provider, apnname):
        parameters = self.parseProviderNode(countryCode, provider, apnname)
        parameters["modem"] = self.getModemDevice()
        parameters["profileName"] = self.getProfileName()
        return parameters

    def getProfileName(self):
        if self.profileName:
            return self.profileName

        return self.getUserInput("Enter name for configuration (default is %s): " % DEFAULT_PROFILE_NAME, DEFAULT_PROFILE_NAME)

    def writeConfig(self, profileName, section):
        """append or replace the configuration section to wvdial.conf"""
        if not os.path.exists(self.configPath):
            print("\nWarning: %s doesn't exist, creating new file." % self.configPath)
            with open(self.configPath, 'w') as f:
                pass

        with open(self.configPath, 'r') as f:
            text = f.read()

        snippetStart = text.find("[Dialer %s]" % profileName)
        if snippetStart != -1:
            snippetEnd = text.find("[Dialer ", snippetStart + 1)
            print("\nThe following part of wvdial.conf will be replaced: \n\n%s" % text[snippetStart:snippetEnd])
            print("by: \n\n%s" % section)
            text = text.replace(text[snippetStart:snippetEnd], section)
        else:
            print("\nThe following will be appended to wvdial.conf: \n\n%s" % section)
            text += "\n%s" % section

        if not self._configPathIsCustom:
            editConf = raw_input("Write to file? Y/n: ") in ["", "Y", "y"]
        else:
            editConf = True

        if editConf:
            with open(self.configPath, 'w') as f:
                f.write(text)

            print("wvdial.conf edited successfully, run '%s' to start the connection.\n\n"
                  % self.wvdialCommand(profileName))

    def wvdialCommand(self, profileName):
        args = []
        if self.configPath != DEFAULT_CONFIG_PATH:
            args.append('--config="%s"' % self.configPath)

        return "wvdial %s %s" % (' '.join(args), profileName)

    def formatConfig(self, parameters):
        """formats the information contained in parameters into a valid wvdial.conf format"""

        if 'usr' not in parameters:
            parameters['usr'] = ""

        if 'pw' not in parameters:
            parameters['pw'] = ""

        return """[Dialer %(profileName)s]
Modem Type = Analog Modem
Phone = *99***1#
ISDN = 0
Baud = 460800
Username = %(usr)s
Password = %(pw)s
Modem = %(modem)s
Init1 = ATZ
Init2 = at+cgdcont=1,"ip","%(apn)s"
Stupid Mode = 1
""" % parameters

    def getModemDevice(self):
        """return modem location provided by user"""
        if self.modemDevice:
            return self.modemDevice

        modemDevice = "initialValue"

        while not modemDevice.startswith("/dev/") or len(modemDevice) == 0:
            modemDevice = self.getUserInput("Enter modem location (default is %s): " % DEFAULT_MODEM_DEVICE, DEFAULT_MODEM_DEVICE)

        if len(modemDevice.strip()) == 0:
            modemDevice = DEFAULT_MODEM_DEVICE

        return modemDevice

    def getUserChoice(self, choices, header, prompt):
        """takes a string list, a text prompt and a header, and returns user choice"""

        print('')
        print(header)
        print('')

        count = len(choices)
        for k, v in zip(range(count), choices):
            print('%s: %s' % (k, v))

        choice = -1
        while choice >= count or choice < 0:
            inputStr = self.getUserInput("%s [0-%d]: " % (prompt, count - 1))
            try:
                choice = atoi(inputStr)
                if choice < 0 or choice >= count:
                    print("Input needs to be between 0 and %d" % (count - 1))
            except ValueError:
                choice = -1
                print("Input needs to be an integer.")

        return choice

    def getUserInput(self, prompt, default=""):
        """utility method for getting user input. displays prompt, optional default fallback"""
        accept = "n"
        inp = ""
        while accept == "n" or accept == "N":
            inp = raw_input("\n" + prompt)
            if len(inp.strip()) == 0:
                inp = default
            accept = raw_input("Your choice: '%s'. Is this correct? Y/n: " % inp)
        return inp

    def parseProviderNode(self, countryCode, provider, apnname):
        """return initially filled parameter dictionary from provider xml node"""
        providerNode = self.getNodesFromXml("country[@code='%s']/provider[name='%s']" % (countryCode, provider))[0]
        apns = providerNode.findall("*/apn")
        apnNode = [node for node in apns if node.get("value") == apnname][0]
        parameters = {}

        apn = apnNode.get("value")
        parameters["apn"] = apn

        usr = apnNode.findtext("username")
        if usr:
            parameters["usr"] = usr

        pw = apnNode.findtext("password")
        if pw:
            parameters["pw"] = pw

        return parameters


def cli():
    from argparse import ArgumentParser
    from argparse import FileType

    parser = ArgumentParser(description=Mkwvconf.introMessage)
    parser.add_argument('--configPath', type=FileType('w'))
    parser.add_argument('--xmlPath', type=FileType('r'))
    parser.add_argument('--modemDevice', type=str)
    parser.add_argument('--profileName', type=str)
    parser.add_argument('--countryCode', type=str, help='Country code (non-interactive mode)')
    parser.add_argument('--provider', type=str, help='Provider name (non-interactive mode)')
    parser.add_argument('--apn', type=str, help='APN name (non-interactive mode)')
    args = parser.parse_args()

    opts = {}
    if args.configPath:
        args.configPath.close()
        opts['configPath'] = args.configPath.name
    if args.xmlPath:
        args.xmlPath.close()
        opts['xmlPath'] = args.xmlPath.name
    if args.modemDevice:
        opts['modemDevice'] = args.modemDevice
    if args.profileName:
        opts['profileName'] = args.profileName

    mkwvconf = Mkwvconf(opts)

    # Non-interactive mode: all parameters provided via command-line
    if args.countryCode and args.provider and args.apn:
        countryCode = args.countryCode
        provider = args.provider
        apnname = args.apn
        mkwvconf.makeConfig(countryCode, provider, apnname)
    else:
        # Interactive mode
        mkwvconf.displayIntro()
        countryCode = mkwvconf.selectCountryCode()
        provider = mkwvconf.selectProvider(countryCode)
        apnname = mkwvconf.selectApn(countryCode, provider)
        mkwvconf.makeConfig(countryCode, provider, apnname)


if __name__ == "__main__":
    cli()
