#!/usr/bin/env python

import argparse
import re

import rumba.model as mod
import rumba.log as log


def make_experiment(filename, experiment_class, experiment_kwargs,
                    testbed_class, testbed_kwargs, verbosity):
    """
    :type filename str
    :param filename: path to the .conf file
    :param experiment_class: subclass of mod.Experiment
    :param experiment_kwargs: args dict for experiment constructor
    (nodes will be overwritten)
    :param testbed_class: subclass of mod.Testbed
    :param testbed_kwargs: args dict for experiment constructor
    (nodes will be overwritten)
    """
    shims = {}
    nodes = {}
    difs = {}

    print('Reading file %s.' % (filename,))
    print('+++++++++++++++++++')
    print()

    with open(filename, 'r') as conf:

        line_cnt = 0

        while 1:
            line = conf.readline()
            if line == '':
                break
            line_cnt += 1

            line = line.replace('\n', '').strip()

            if line.startswith('#') or line == "":
                continue

            m = re.match(r'\s*eth\s+([\w-]+)\s+(\d+)([GMK])bps\s+(\w.*)$', line)
            if m:
                shim = m.group(1)
                speed = int(m.group(2))
                speed_unit = m.group(3).lower()
                vm_list = m.group(4).split()

                if shim in shims or shim in difs:
                    print('Error: Line %d: shim %s already defined'
                          % (line_cnt, shim))
                    continue

                if speed_unit == 'K':
                    speed = speed // 1000
                if speed_unit == 'G':
                    speed = speed * 1000

                shims[shim] = {'name': shim,
                               'speed': speed,
                               'type': 'eth'}

                for vm in vm_list:
                    nodes.setdefault(vm, {'name': vm, 'difs': [],
                                          'dif_registrations': {},
                                          'registrations': {}})
                    nodes[vm]['difs'].append(shim)
                continue

            m = re.match(r'\s*dif\s+([\w-]+)\s+([\w-]+)\s+(\w.*)$', line)
            if m:
                dif = m.group(1)
                vm = m.group(2)
                dif_list = m.group(3).split()

                if dif in shims:
                    print('Error: Line %d: dif %s already defined as shim'
                          % (line_cnt, dif))
                    continue

                difs.setdefault(dif, {
                    'name': dif})  # Other dict contents might be policies.

                if vm in nodes and dif in nodes[vm]['dif_registrations']:
                    print('Error: Line %d: vm %s in dif %s already specified'
                          % (line_cnt, vm, dif))
                    continue

                nodes.setdefault(vm, {'name': vm, 'difs': [],
                                      'dif_registrations': {},
                                      'registrations': {}})
                nodes[vm]['difs'].append(dif)
                nodes[vm]['dif_registrations'][dif] = dif_list
                # It is not defined yet, per check above.

                continue

            # No match, spit a warning
            print('Warning: Line %d unrecognized and ignored' % line_cnt)

    # File parsed

    parsed_difs = {}

    for shim_name, shim in shims.items():
        parsed_difs[shim_name] = (mod.ShimEthDIF(shim_name,
                                                 link_speed=shim['speed']))

    for dif_name, dif in difs.items():
        parsed_difs[dif_name] = (mod.NormalDIF(dif_name))

    parsed_nodes = []
    for node, node_data in nodes.items():
        name = node_data['name']
        difs = [parsed_difs[x] for x in node_data['difs']]
        dif_registrations = {parsed_difs[x]: [parsed_difs[y] for y in l]
                             for x, l in node_data['dif_registrations']
                             .items()}
        parsed_nodes.append(mod.Node(name, difs, dif_registrations))

    log.set_logging_level(verbosity)
    print()
    print('++++++++++++++++++++')
    print('Calling constructor of testbed %s with args %s.'
          % (testbed_class, testbed_kwargs))
    print('++++++++++++++++++++')
    print()

    testbed = testbed_class(**testbed_kwargs)

    experiment_kwargs['testbed'] = testbed
    experiment_kwargs['nodes'] = parsed_nodes

    exp = experiment_class(**experiment_kwargs)
    try:
        exp.swap_in()
        exp.bootstrap_prototype()
        input("Press ENTER to quit")
    finally:
        exp.swap_out()


def setup_testbed_common_args(t_p):

    t_p.add_argument('-E', '--exp_name', metavar='EXP_NAME', type=str,
                     required=True,
                     help='Experiment name')
    t_p.add_argument('-U', '--username', metavar='USERNAME', type=str,
                     required=True,
                     help='Testbed user name')
    t_p.add_argument('-P', '--proj_name', metavar='PROJECT_NAME', type=str,
                     help='Project name')
    t_p.add_argument('-W', '--password', metavar='PASSWORD', type=str,
                     help='Testbed password')


if __name__ == '__main__':
    description = "Demonstrator config file to rumba script converter"
    epilog = "2017 Marco Capitani <m.capitani@nextworks.it>"

    parser = argparse.ArgumentParser(description=description,
                                     epilog=epilog)

    parser.add_argument('-P', '--prototype', type=str, required=True,
                        choices=['irati', 'ouroboros', 'rlite'],
                        help='The kind of prototype plugin to use to run'
                             ' the experiment.')

    parser.add_argument('-C', '--conf', metavar='CONFIG', type=str,
                        required=True,
                        help='Path to the config file to parse')

    parser.add_argument(
        '--verbosity', metavar='VERBOSITY', type=str,
        default='INFO',
        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
        help='Path to the config file to parse')

    subparsers = parser.add_subparsers(dest='testbed')
    emulab_p = subparsers.add_parser('emulab', help='Use emulab testbed')
    jfed_p = subparsers.add_parser('jfed', help='Use jfed testbed')
    qemu_p = subparsers.add_parser('qemu', help='Use qemu testbed')
    fake_p = subparsers.add_parser('fake', help='Use fake testbed')

    for t in [emulab_p, jfed_p, qemu_p, fake_p]:
        setup_testbed_common_args(t)

    qemu_p.add_argument('-B', '--bzimage', metavar='BZIMAGE', type=str,
                        required=True,
                        help='path to the bzImage file to use')
    qemu_p.add_argument('-I', '--initramfs', metavar='INITRAMFS', type=str,
                        required=True,
                        help='path to the initramfs file to use')
    qemu_p.add_argument('-V', '--use_vhost', action='store_true',
                        default=False, help='Use vhost')
    qemu_p.add_argument('-Q', '--qemu_logs_dir', metavar='QEMU_LOGS', type=str,
                        default=None, help='path to the folder for qemu logs')

    emulab_p.add_argument('-R', '--url', metavar='URL', type=str,
                          default="wall2.ilabt.iminds.be",
                          help='Url')
    emulab_p.add_argument('-I', '--image', metavar='IMG', type=str,
                          default="UBUNTU14-64-STD",
                          help='Ubuntu image')

    jfed_p.add_argument('-C', '--cert_file', metavar='CERT', type=str,
                        required=True,
                        help='Certificate file')
    jfed_p.add_argument('-H', '--exp_hours', metavar='HOURS', type=str,
                        default="2", help='Experiment hours')
    jfed_p.add_argument('-A', '--authority', metavar='AUTH', type=str,
                        default="wall2.ilabt.iminds.be",
                        help='Authority')
    jfed_p.add_argument('-I', '--image', metavar='IMAGE', type=str,
                        default=None,
                        help='Image to be used')

    args = parser.parse_args()

    if args.testbed == 'emulab':
        import rumba.testbeds.emulab as emulab
        test_class = emulab.Testbed
        testbed_args = {a.dest: getattr(args, a.dest)
                        for a in emulab_p._actions
                        if a.dest != 'help'
                        and getattr(args, a.dest) is not None}
    elif args.testbed == 'jfed':
        import rumba.testbeds.jfed as jfed
        test_class = jfed.Testbed
        testbed_args = {a.dest: getattr(args, a.dest)
                        for a in jfed_p._actions
                        if a.dest != 'help'
                        and getattr(args, a.dest) is not None}
    elif args.testbed == 'qemu':
        import rumba.testbeds.qemu as qemu
        test_class = qemu.Testbed
        testbed_args = {a.dest: getattr(args, a.dest)
                        for a in qemu_p._actions
                        if a.dest != 'help'
                        and getattr(args, a.dest) is not None}
    elif args.testbed == 'fake':
        import rumba.testbeds.faketestbed as fake
        test_class = fake.Testbed
        testbed_args = {a.dest: getattr(args, a.dest)
                        for a in fake_p._actions
                        if a.dest != 'help'
                        and getattr(args, a.dest) is not None}
    else:
        if args.testbed is None:
            print('Testbed type must be specified!')
            print(parser.format_help())
            exit(1)
        raise ValueError('Unexpected testbed: %s.' % args.testbed)

    if args.prototype == 'irati':
        import rumba.prototypes.irati as irati
        exp_class = irati.Experiment
    elif args.prototype == 'ouroboros':
        import rumba.prototypes.ouroboros as ouroboros
        exp_class = ouroboros.Experiment
    elif args.prototype == 'rlite':
        import rumba.prototypes.rlite as rlite
        exp_class = rlite.Experiment
    else:
        raise ValueError('Unexpected prototype: %s.' % args.testbed)

    try:
        make_experiment(args.conf,
                        experiment_class=exp_class,
                        experiment_kwargs={},
                        testbed_class=test_class,
                        testbed_kwargs=testbed_args,
                        verbosity=args.verbosity)

    except KeyboardInterrupt:
        print("Interrupted. Closing down.")
