#!/usr/bin/env python
"""FNSS Mininet console

Usage:
  mn-fnss [mn-options] [--no-relabel] <topology-file>
  mn-fnss (--help | -h)
  mn-fnss (--version | -v)

Options:
  mn-options        Mininet mn options.
  --no-relabel      Do not relabel topology nodes to Mininet conventions.
  -h --help         Show help.
  -v --version      Show version.

Launch Mininet console with an FNSS topology.

This script parses an FNSS topology XML file and launches the Mininet console
passing this topology.

This script accepts all the options of Mininet *mn* script, except for the
*custom* and *topo* options which are overwritten by this script.

In addition, if the user specifies the mn *link* option, then all potential
link attributes of the topology (e.g. capacity, delay and max queue size) are
discarded and values provided with the link attributes are used instead.

Unless the option *--no-relabel* is provided, this script relabels all nodes of
the FNSS topology to match Mininet's conventions, i.e. each host label starts
with *h* (e.g. h1, h2, h3...) and each switch label starts with *s*
(e.g. s1, s2, s3...).

Unless used to print this help message or version information, this script
must be run as superuser.

Example usage:
  $ python
  >>> import fnss
  >>> topo = fnss.two_tier_topology(1, 2, 2)
  >>> fnss.write_topology(topo, 'fnss-topo.xml')
  $ sudo mn-fnss fnss-topo.xml
"""
import os
import sys
import tempfile
import signal
import subprocess

from fnss import __version__
from fnss.adapters.mn import to_mininet
from fnss.topologies.topology import read_topology

def version():
    """Print version information and exit"""
    sys.exit(__version__)

def usage():
    """Print usage information and exit"""
    sys.exit(__doc__)

def run_mn(path, argv, relabel_nodes=True):
    """Run Mininet given with a given FNSS topology

    Mininet's *mn* script only accepts custom topologies passed in the form of
    a Python source file that contains the code for generating a Mininet
    topology on the fly. For this reason, this function, given an FNSS topology
    file, creates a temporary Python source file containing the code to parse
    such FNSS topology file and convert it to Mininet format. It then launches
    *mn* passing this temporary file as argument and destroys it after *mn*
    terminates.

    Parameters
    ----------
    path : str
        The absolute path to the FNSS topology XML file
    argv : list
        List of arguments for mn
    relabel_nodes : bool, optional
        If *True*, rename node labels according to Mininet conventions. In
        Mininet all node labels are strings whose values are "h1", "h2", ... if
        the node is a host or "s1", "s2", ... if the node is a switch.

    Returns
    -------
    retcode : int
        mn return code
    """
    t = tempfile.NamedTemporaryFile(prefix='fnss-mn')
    cmd = "from fnss.adapters.mn import to_mininet\n" \
          "from fnss.topologies.topology import read_topology\n" \
          "topos = {'fnss': (lambda: to_mininet(read_topology('%s'), relabel_nodes=%s))}" \
          % (os.path.abspath(path), str(relabel_nodes))
    with open(t.name, 'w') as f:
        f.write(cmd)
    # This signal handling code is required to ensure that these signals are
    # handled only by the mn subprocess instead of the mn-fnss process
    handler = lambda signum, frame: None
    for sig in (signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT):
        signal.signal(sig, handler)
    # I need to force the link type otherwise fail
    if '--link' not in argv:
        argv.extend(('--link', 'tc'))
    retcode = subprocess.call(['mn', '--custom', t.name, '--topo', 'fnss'] + argv)
    t.close()
    return retcode

def main():
    """Main function"""
    if len(sys.argv) < 2:
        usage()
    args = sys.argv[1:]
    arg = args.pop()
    if arg in ('--help', '-h'):
        usage()
    if arg in ('--version', '-v'):
        version()
    if not os.path.isfile(arg):
        print("File '%s' does not exist." % arg)
        usage()
    if '--no-relabel' in args:
        relabel_nodes = False
        args.remove('--no-relabel')
    else:
        relabel_nodes = True
    return run_mn(arg, args, relabel_nodes)


if __name__ == '__main__':
    sys.exit(main())
