# -*- coding: utf-8 -*-
'''
Module for gathering and managing bridging information
'''
from __future__ import absolute_import, print_function, unicode_literals

import sys
import re
import salt.utils.path


__func_alias__ = {
    'list_': 'list'
}


# Other BSD-like derivatives that use ifconfig may work too
SUPPORTED_BSD_LIKE = ['FreeBSD', 'NetBSD', 'OpenBSD']


def __virtual__():
    '''
    Confirm this module is supported by the OS and the system has
    required tools
    '''
    supported_os_tool = {
        'FreeBSD': 'ifconfig',
        'Linux': 'brctl',
        'NetBSD': 'brconfig',
        'OpenBSD': 'ifconfig'
    }
    cur_os = __grains__['kernel']
    for _os in supported_os_tool:
        if cur_os == _os and salt.utils.path.which(supported_os_tool[cur_os]):
            return True
    return (False, 'The bridge execution module failed to load: requires one of the following tool/os'
        ' combinations: ifconfig on FreeBSD/OpenBSD, brctl on Linux or brconfig on NetBSD.')


def _tool_path(ostool):
    '''
    Internal, returns tools path
    '''
    return salt.utils.path.which(ostool)


def _linux_brshow(br=None):
    '''
    Internal, returns bridges and enslaved interfaces (GNU/Linux - brctl)
    '''
    brctl = _tool_path('brctl')

    if br:
        cmd = '{0} show {1}'.format(brctl, br)
    else:
        cmd = '{0} show'.format(brctl)

    brs = {}

    for line in __salt__['cmd.run'](cmd, python_shell=False).splitlines():
        # get rid of first line
        if line.startswith('bridge name'):
            continue
        # get rid of ^\n's
        vals = line.split()
        if not vals:
            continue

        # bridge name bridge id       STP enabled interfaces
        # br0       8000.e4115bac8ddc   no      eth0
        #                                       foo0
        # br1       8000.e4115bac8ddc   no      eth1
        if len(vals) > 1:
            brname = vals[0]

            brs[brname] = {
                'id': vals[1],
                'stp': vals[2],
            }
            if len(vals) > 3:
                brs[brname]['interfaces'] = [vals[3]]

        if len(vals) == 1 and brname:
            brs[brname]['interfaces'].append(vals[0])

    if br:
        try:
            return brs[br]
        except KeyError:
            return None
    return brs


def _linux_bradd(br):
    '''
    Internal, creates the bridge
    '''
    brctl = _tool_path('brctl')
    return __salt__['cmd.run']('{0} addbr {1}'.format(brctl, br),
                               python_shell=False)


def _linux_brdel(br):
    '''
    Internal, deletes the bridge
    '''
    brctl = _tool_path('brctl')
    return __salt__['cmd.run']('{0} delbr {1}'.format(brctl, br),
                               python_shell=False)


def _linux_addif(br, iface):
    '''
    Internal, adds an interface to a bridge
    '''
    brctl = _tool_path('brctl')
    return __salt__['cmd.run']('{0} addif {1} {2}'.format(brctl, br, iface),
                               python_shell=False)


def _linux_delif(br, iface):
    '''
    Internal, removes an interface from a bridge
    '''
    brctl = _tool_path('brctl')
    return __salt__['cmd.run']('{0} delif {1} {2}'.format(brctl, br, iface),
                               python_shell=False)


def _linux_stp(br, state):
    '''
    Internal, sets STP state
    '''
    brctl = _tool_path('brctl')
    return __salt__['cmd.run']('{0} stp {1} {2}'.format(brctl, br, state),
                               python_shell=False)


def _bsd_brshow(br=None):
    '''
    Internal, returns bridges and member interfaces (BSD-like: ifconfig)
    '''
    if __grains__['kernel'] == 'NetBSD':
        return _netbsd_brshow(br)

    ifconfig = _tool_path('ifconfig')

    ifaces = {}

    if br:
        ifaces[br] = br
    else:
        cmd = '{0} -g bridge'.format(ifconfig)
        for line in __salt__['cmd.run'](cmd, python_shell=False).splitlines():
            ifaces[line] = line

    brs = {}

    for iface in ifaces:
        cmd = '{0} {1}'.format(ifconfig, iface)
        for line in __salt__['cmd.run'](cmd, python_shell=False).splitlines():
            brs[iface] = {
                'interfaces': [],
                'stp': 'no'
            }
            line = line.lstrip()
            if line.startswith('member:'):
                brs[iface]['interfaces'].append(line.split(' ')[1])
                if 'STP' in line:
                    brs[iface]['stp'] = 'yes'

    if br:
        return brs[br]
    return brs


def _netbsd_brshow(br=None):
    '''
    Internal, returns bridges and enslaved interfaces (NetBSD - brconfig)
    '''
    brconfig = _tool_path('brconfig')

    if br:
        cmd = '{0} {1}'.format(brconfig, br)
    else:
        cmd = '{0} -a'.format(brconfig)

    brs = {}
    start_int = False

    for line in __salt__['cmd.run'](cmd, python_shell=False).splitlines():
        if line.startswith('bridge'):
            start_int = False
            brname = line.split(':')[0]  # on NetBSD, always ^bridge([0-9]+):
            brs[brname] = {
                'interfaces': [],
                'stp': 'no'
            }
        if 'Interfaces:' in line:
            start_int = True
            continue
        if start_int and brname:
            m = re.match(r'\s*([a-z0-9]+)\s.*<.*>', line)
            if m:
                brs[brname]['interfaces'].append(m.group(1))
                if 'STP' in line:
                    brs[brname]['stp'] = 'yes'

    if br:
        try:
            return brs[br]
        except KeyError:
            return None
    return brs


def _bsd_bradd(br):
    '''
    Internal, creates the bridge
    '''
    kernel = __grains__['kernel']
    ifconfig = _tool_path('ifconfig')

    if not br:
        return False

    if __salt__['cmd.retcode']('{0} {1} create up'.format(ifconfig, br),
                               python_shell=False) != 0:
        return False

    # NetBSD is two cmds
    if kernel == 'NetBSD':
        brconfig = _tool_path('brconfig')
        if __salt__['cmd.retcode']('{0} {1} up'.format(brconfig, br),
                                   python_shell=False) != 0:
            return False

    return True


def _bsd_brdel(br):
    '''
    Internal, deletes the bridge
    '''
    ifconfig = _tool_path('ifconfig')
    if not br:
        return False
    return __salt__['cmd.run']('{0} {1} destroy'.format(ifconfig, br),
                               python_shell=False)


def _bsd_addif(br, iface):
    '''
    Internal, adds an interface to a bridge
    '''
    kernel = __grains__['kernel']
    if kernel == 'NetBSD':
        cmd = _tool_path('brconfig')
        brcmd = 'add'
    else:
        cmd = _tool_path('ifconfig')
        brcmd = 'addem'

    if not br or not iface:
        return False

    return __salt__['cmd.run']('{0} {1} {2} {3}'.format(cmd, br, brcmd, iface),
                               python_shell=False)


def _bsd_delif(br, iface):
    '''
    Internal, removes an interface from a bridge
    '''
    kernel = __grains__['kernel']
    if kernel == 'NetBSD':
        cmd = _tool_path('brconfig')
        brcmd = 'delete'
    else:
        cmd = _tool_path('ifconfig')
        brcmd = 'deletem'

    if not br or not iface:
        return False

    return __salt__['cmd.run']('{0} {1} {2} {3}'.format(cmd, br, brcmd, iface),
                               python_shell=False)


def _bsd_stp(br, state, iface):
    '''
    Internal, sets STP state. On BSD-like, it is required to specify the
    STP physical interface
    '''
    kernel = __grains__['kernel']
    if kernel == 'NetBSD':
        cmd = _tool_path('brconfig')
    else:
        cmd = _tool_path('ifconfig')

    if not br or not iface:
        return False

    return __salt__['cmd.run']('{0} {1} {2} {3}'.format(cmd, br, state, iface),
                               python_shell=False)


def _os_dispatch(func, *args, **kwargs):
    '''
    Internal, dispatches functions by operating system
    '''
    if __grains__['kernel'] in SUPPORTED_BSD_LIKE:
        kernel = 'bsd'
    else:
        kernel = __grains__['kernel'].lower()

    _os_func = getattr(sys.modules[__name__], '_{0}_{1}'.format(kernel, func))

    if callable(_os_func):
        return _os_func(*args, **kwargs)


# End of internal functions


def show(br=None):
    '''
    Returns bridges interfaces along with enslaved physical interfaces. If
    no interface is given, all bridges are shown, else only the specified
    bridge values are returned.

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.show
        salt '*' bridge.show br0
    '''
    return _os_dispatch('brshow', br)


def list_():
    '''
    Returns the machine's bridges list

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.list
    '''
    brs = _os_dispatch('brshow')
    if not brs:
        return None
    brlist = []
    for br in brs:
        brlist.append(br)

    return brlist


def interfaces(br=None):
    '''
    Returns interfaces attached to a bridge

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.interfaces br0
    '''
    if not br:
        return None

    br_ret = _os_dispatch('brshow', br)
    if br_ret:
        return br_ret['interfaces']


def find_interfaces(*args):
    '''
    Returns the bridge to which the interfaces are bond to

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.find_interfaces eth0 [eth1...]
    '''
    brs = _os_dispatch('brshow')
    if not brs:
        return None

    iflist = {}

    for iface in args:
        for br in brs:
            try:  # a bridge may not contain interfaces
                if iface in brs[br]['interfaces']:
                    iflist[iface] = br
            except Exception:
                pass

    return iflist


def add(br=None):
    '''
    Creates a bridge

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.add br0
    '''
    return _os_dispatch('bradd', br)


def delete(br=None):
    '''
    Deletes a bridge

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.delete br0
    '''
    return _os_dispatch('brdel', br)


def addif(br=None, iface=None):
    '''
    Adds an interface to a bridge

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.addif br0 eth0
    '''
    return _os_dispatch('addif', br, iface)


def delif(br=None, iface=None):
    '''
    Removes an interface from a bridge

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.delif br0 eth0
    '''
    return _os_dispatch('delif', br, iface)


def stp(br=None, state='disable', iface=None):
    '''
    Sets Spanning Tree Protocol state for a bridge

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.stp br0 enable
        salt '*' bridge.stp br0 disable

    For BSD-like operating systems, it is required to add the interface on
    which to enable the STP.

    CLI Example:

    .. code-block:: bash

        salt '*' bridge.stp bridge0 enable fxp0
        salt '*' bridge.stp bridge0 disable fxp0
    '''
    kernel = __grains__['kernel']
    if kernel == 'Linux':
        states = {'enable': 'on', 'disable': 'off'}
        return _os_dispatch('stp', br, states[state])
    elif kernel in SUPPORTED_BSD_LIKE:
        states = {'enable': 'stp', 'disable': '-stp'}
        return _os_dispatch('stp', br, states[state], iface)
    else:
        return False


# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
