from pyroute2.netlink.rt_files import GroupFile
from pyroute2.netlink.rtnl.ifinfmsg import IFF_NOARP, IFF_UP, ifinfmsg
from pyroute2.netlink.rtnl.ifinfmsg.plugins.vlan import flags as vlan_flags

from .common import Index, IPRouteFilter, NLAKeyTransform


class LinkFieldFilter(Index, NLAKeyTransform):
    _nla_prefix = 'IFLA_'

    def _link(self, key, context, value):
        if isinstance(value, dict):
            return {key: value['index']}
        return {key: value}

    def set_vxlan_link(self, context, value):
        return self._link('vxlan_link', context, value)

    def set_link(self, context, value):
        return self._link('link', context, value)

    def set_master(self, context, value):
        return self._link('master', context, value)

    def set_address(self, context, value):
        if isinstance(value, str):
            # lower the case
            if not value.islower():
                value = value.lower()
            # convert xxxx.xxxx.xxxx to xx:xx:xx:xx:xx:xx
            if len(value) == 14 and value[4] == value[9] == '.':
                value = ':'.join(
                    [':'.join((x[:2], x[2:])) for x in value.split('.')]
                )
        return {'address': value}

    def set_carrier(self, context, value):
        return {}

    def set_carrier_changes(self, context, value):
        return {}

    def set_info_slave_kind(self, context, value):
        return {}

    def set_mask(self, context, value):
        return {'change': value}

    def set_info_kind(self, context, value):
        return {'kind': value}


class LinkIPRouteFilter(IPRouteFilter):
    def set_altname(self, context, value):
        if self.command in ('property_add', 'property_del'):
            if not isinstance(value, (list, tuple, set)):
                value = [value]
            return {
                'IFLA_PROP_LIST': {
                    'attrs': [
                        ('IFLA_ALT_IFNAME', alt_ifname) for alt_ifname in value
                    ]
                }
            }
        else:
            return {'IFLA_ALT_IFNAME': value}

    def set_xdp_fd(self, context, value):
        return {'xdp': {'attrs': [('IFLA_XDP_FD', value)]}}

    def set_vf(self, context, value):
        return {'IFLA_VFINFO_LIST': self.get_vf(value)}

    def set_state(self, context, value):
        ret = {}
        if self.command == 'dump':
            return {'state': value}
        if value == 'up':
            ret['flags'] = (context.get('flags', 0) or 0) | IFF_UP
        ret['change'] = (context.get('change', 0) or 0) | IFF_UP
        return ret

    def set_arp(self, context, value):
        ret = {}
        if not value:
            ret['flags'] = (context.get('flags', 0) or 0) | IFF_NOARP
        ret['change'] = (context.get('change', 0) or 0) | IFF_NOARP
        return ret

    def set_noarp(self, context, value):
        ret = {}
        if value:
            ret['flags'] = (context.get('flags', 0) or 0) | IFF_NOARP
        ret['change'] = (context.get('change', 0) or 0) | IFF_NOARP
        return ret

    def set_group(self, context, value):
        if isinstance(value, str):
            value = GroupFile().get_rt_id(value)
        return {'group': value}

    def finalize(self, context):
        # set interface type specific attributes
        self.kind = context.pop('kind', None)
        if self.kind is None:
            return
        # load specific NLA names
        self.specific = {}
        cls = ifinfmsg.ifinfo.data_map.get(self.kind, None)
        if cls is not None:
            prefix = cls.prefix or 'IFLA_'
            for nla, _ in cls.nla_map:
                self.specific[nla] = nla
                self.specific[nla[len(prefix) :].lower()] = nla

        if self.command == 'dump':
            context.pop('peer', None)
            context[('linkinfo', 'kind')] = self.kind
            for key, value in tuple(context.items()):
                if key in self.specific:
                    context[('linkinfo', 'data', key)] = value
                    try:
                        del context[key]
                    except KeyError:
                        pass
            return

        # get common ifinfmsg NLAs
        self.common = []
        for key, _ in ifinfmsg.nla_map:
            self.common.append(key)
            self.common.append(key[len(ifinfmsg.prefix) :].lower())
        self.common.append('family')
        self.common.append('ifi_type')
        self.common.append('index')
        self.common.append('flags')
        self.common.append('change')
        for key in ('index', 'change', 'flags'):
            if key not in context:
                context[key] = 0

        linkinfo = {'attrs': []}
        self.linkinfo = linkinfo['attrs']
        self._info_data = None
        self._info_slave_data = None
        context['IFLA_LINKINFO'] = linkinfo
        self.linkinfo.append(['IFLA_INFO_KIND', self.kind])
        # flush deferred NLAs
        for key, value in tuple(context.items()):
            if self.push_specific(key, value):
                try:
                    del context[key]
                except KeyError:
                    pass

    def push_specific(self, key, value):
        # FIXME: vlan hack
        if self.kind == 'vlan':
            if key == 'vlan_flags':
                if isinstance(value, (list, tuple)):
                    if len(value) == 2 and all(
                        (isinstance(x, int) for x in value)
                    ):
                        value = {'flags': value[0], 'mask': value[1]}
                    else:
                        ret = 0
                        for x in value:
                            ret |= vlan_flags.get(x, 1)
                        value = {'flags': ret, 'mask': ret}
                elif isinstance(value, int):
                    value = {'flags': value, 'mask': value}
                elif isinstance(value, str):
                    value = vlan_flags.get(value, 1)
                    value = {'flags': value, 'mask': value}
                elif not isinstance(value, dict):
                    raise ValueError()
            elif key in ('vlan_egress_qos', 'vlan_ingress_qos'):
                if isinstance(value, dict) and {'from', 'to'} == value.keys():
                    value = {'attrs': (('IFLA_VLAN_QOS_MAPPING', value),)}
        # the kind is known: lookup the NLA
        if key in self.specific:
            # FIXME: slave hack
            if self.kind.endswith('_slave'):
                self.info_slave_data.append((self.specific[key], value))
            else:
                self.info_data.append((self.specific[key], value))
            return True
        elif key == 'peer' and self.kind == 'veth':
            # FIXME: veth hack
            if isinstance(value, dict):
                attrs = []
                for k, v in value.items():
                    attrs.append([ifinfmsg.name2nla(k), v])
            else:
                attrs = [['IFLA_IFNAME', value]]
            nla = ['VETH_INFO_PEER', {'attrs': attrs}]
            self.info_data.append(nla)
            return True
        elif key == 'mode':
            # FIXME: ipvlan / tuntap / bond hack
            if self.kind == 'tuntap':
                nla = ['IFTUN_MODE', value]
            else:
                nla = ['IFLA_%s_MODE' % self.kind.upper(), value]
            self.info_data.append(nla)
            return True

        return False

    @property
    def info_data(self):
        if self._info_data is None:
            info_data = ('IFLA_INFO_DATA', {'attrs': []})
            self._info_data = info_data[1]['attrs']
            self.linkinfo.append(info_data)
        return self._info_data

    @property
    def info_slave_data(self):
        if self._info_slave_data is None:
            info_slave_data = ('IFLA_INFO_SLAVE_DATA', {'attrs': []})
            self._info_slave_data = info_slave_data[1]['attrs']
            self.linkinfo.append(info_slave_data)
        return self._info_slave_data

    def get_vf(self, spec):
        vflist = []
        if not isinstance(spec, (list, tuple)):
            spec = (spec,)
        for vf in spec:
            vfcfg = []
            # pop VF index
            vfid = vf.pop('vf')  # mandatory
            # pop VLAN spec
            vlan = vf.pop('vlan', None)  # optional
            if isinstance(vlan, int):
                vfcfg.append(('IFLA_VF_VLAN', {'vf': vfid, 'vlan': vlan}))
            elif isinstance(vlan, dict):
                vlan['vf'] = vfid
                vfcfg.append(('IFLA_VF_VLAN', vlan))
            elif isinstance(vlan, (list, tuple)):
                vlist = []
                for vspec in vlan:
                    vspec['vf'] = vfid
                    vlist.append(('IFLA_VF_VLAN_INFO', vspec))
                vfcfg.append(('IFLA_VF_VLAN_LIST', {'attrs': vlist}))
            # pop rate spec
            rate = vf.pop('rate', None)  # optional
            if rate is not None:
                rate['vf'] = vfid
                vfcfg.append(('IFLA_VF_RATE', rate))
            # create simple VF attrs
            for attr in vf:
                vfcfg.append(
                    (
                        ifinfmsg.vflist.vfinfo.name2nla(attr),
                        {'vf': vfid, attr: vf[attr]},
                    )
                )
            vflist.append(('IFLA_VF_INFO', {'attrs': vfcfg}))
        return {'attrs': vflist}
