
from __future__ import absolute_import
from __future__ import division

import logging
import six

from vdsm.network.ip import address
from vdsm.network.ip import dhclient

from .bond import Bond


class SetupBonds(object):
    def __init__(self, bonds2add, bonds2edit, bonds2remove, config):
        self._bonds2add = bonds2add
        self._bonds2edit = bonds2edit
        self._bonds2remove = bonds2remove
        self._config = config
        self._acquired_ifaces = set()

    def remove_bonds(self):
        logging.debug('Removing bonds: %s', list(self._bonds2remove))
        for bond_name in self._bonds2remove:
            with Bond(bond_name) as bond:
                bond.destroy()
            self._config.removeBonding(bond_name)

    def edit_bonds(self):
        logging.debug('Editing bonds: %s', list(self._bonds2edit))
        init_bond_pool = [(Bond(bond_name), attrs)
                          for bond_name, attrs in self._bonds2edit.items()]

        for bond, attrs in init_bond_pool:
            with bond:
                slaves2remove = self._slaves2remove(bond.slaves,
                                                    frozenset(attrs['nics']))
                bond.del_slaves(slaves2remove)

            self._config.setBonding(
                bond.master,
                {'nics': sorted(bond.slaves), 'options': bond.options,
                 'switch': attrs['switch']})

        for bond, attrs in init_bond_pool:
            with bond:
                if 'options' in attrs:
                    bond.set_options(parse_bond_options(attrs['options']))
                requested_slaves = frozenset(attrs['nics'])
                slaves2add = self._slaves2add(bond.slaves, requested_slaves)
                bond.add_slaves(slaves2add)

            self._config.setBonding(bond.master, attrs)

            bond.up()
            _ip_flush(slaves2add)

    def add_bonds(self):
        logging.debug('Creating bonds: %s', list(self._bonds2add))
        for bond_name, attrs in six.iteritems(self._bonds2add):
            requested_slaves = set(attrs['nics'])
            if 'options' in attrs:
                requested_options = parse_bond_options(attrs['options'])
            else:
                requested_options = None
            with Bond(bond_name, slaves=requested_slaves,
                      options=requested_options) as bond:
                bond.create()

            self._config.setBonding(bond_name, attrs)

            bond.up()
            _ip_flush(requested_slaves)

    @property
    def ifaces_for_acquirement(self):
        acquire_ifaces = set()
        for bond_name, attrs in six.iteritems(self._bonds2add):
            acquire_ifaces |= set(attrs['nics']) | {bond_name}
        for bond_name, attrs in six.iteritems(self._bonds2edit):
            acquire_ifaces |= set(attrs['nics']) | {bond_name}

        return acquire_ifaces

    def _slaves2remove(self, running_slaves, requested_slaves):
        return running_slaves - requested_slaves

    def _slaves2add(self, running_slaves, requested_slaves):
        return requested_slaves - running_slaves


def _ip_flush(ifaces):
    for iface in ifaces:
        dhclient.kill(iface, family=4)
        dhclient.kill(iface, family=6)
        address.flush(iface)


def parse_bond_options(options, keep_custom=False):
    def _string_to_dict(str, div, eq):
        if options == '':
            return {}
        return dict(option.split(eq, 1)
                    for option in str.strip(div).split(div))
    if options:
        d_options = _string_to_dict(options, ' ', '=')
        if d_options.get('custom') and not keep_custom:
            d_options['custom'] = _string_to_dict(d_options['custom'], ',',
                                                  ':')
        return d_options
    else:
        return {}


def remove_custom_bond_option(options):
    return ' '.join((option for option in options.split()
                     if not option.startswith('custom=')))
