# encoding: utf-8
"""
Copyright (c) 2016 Evelio Vila <eveliovila@gmail.com>
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
License: 3-clause BSD. (See the COPYRIGHT file)
"""

import json
import binascii
import itertools
from struct import unpack

from exabgp.util import concat_strs
from exabgp.vendoring.bitstring import BitArray
from exabgp.bgp.message.notification import Notify
from exabgp.bgp.message.update.attribute.attribute import Attribute


@Attribute.register()
class LINKSTATE(Attribute):
    ID = Attribute.CODE.BGP_LS
    FLAG = Attribute.Flag.OPTIONAL
    TLV = -1

    # Registered subclasses we know how to decode
    registered_lsids = dict()

    # what this implementation knows as LS attributes
    node_lsids = []
    link_lsids = []
    prefix_lsids = []

    def __init__(self, ls_attrs):
        self.ls_attrs = ls_attrs

    @classmethod
    def register(cls, lsid=None, flag=None):
        def register_lsid(klass):
            scode = klass.TLV if lsid is None else lsid
            if scode in cls.registered_lsids:
                raise RuntimeError('only one class can be registered per BGP link state attribute type')
            cls.registered_lsids[scode] = klass
            return klass

        return register_lsid

    @classmethod
    def registered(cls, lsid, flag=None):
        return lsid in cls.registered_lsids

    @classmethod
    def unpack(cls, data, direction, negotiated):
        ls_attrs = []
        while data:
            scode, length = unpack('!HH', data[:4])
            if scode in cls.registered_lsids:
                klass = cls.registered_lsids[scode].unpack(data[4 : length + 4], length)
            else:
                klass = GenericLSID(scode, data[4 : length + 4])
            klass.TLV = scode
            ls_attrs.append(klass)
            data = data[length + 4 :]
        for klass in ls_attrs:
            if hasattr(klass, 'terids'):
                klass.reset()

        return cls(ls_attrs=ls_attrs)

    def json(self, compact=None):
        content = ', '.join(d.json() for d in self.ls_attrs)
        return '{ %s }' % (content)

    def __str__(self):
        return ', '.join(str(d) for d in self.ls_attrs)


class GenericLSID(object):
    TLV = 99999

    def __init__(self, code, rep):
        self.rep = rep
        self.code = code

    def __repr__(self):
        return "Attribute with code [ %s ] not implemented" % (self.code)

    @classmethod
    def unpack(cls, scode, data):
        length = len(data)
        info = binascii.b2a_uu(data[:length])
        return cls(code=scode, rep=info)

    def json(self, compact=None):
        return '"attribute-not-implemented": "%s"' % (self.code)


class LsGenericFlags(object):

    # 	draft-ietf-isis-segment-routing-extensions Prefix-SID Sub-TLV
    ISIS_SR_FLAGS = ['R', 'N', 'P', 'E', 'V', 'L', 'RSV', 'RSV']
    # 	RFC 7794 IPv4/IPv6 Extended Reachability Attribute Flags
    ISIS_SR_ATTR_FLAGS = ['X', 'R', 'N', 'RSV', 'RSV', 'RSV', 'RSV', 'RSV']
    # 	draft-ietf-isis-segment-routing-extensions - Adj-SID IS-IS Flags
    ISIS_SR_ADJ_FLAGS = ['F', 'B', 'V', 'L', 'S', 'P', 'RSV', 'RSV']
    # 	isis-segment-routing-extensions 3.1. SR-Capabilities Sub-TLV
    ISIS_SR_CAP_FLAGS = ['I', 'V', 'RSV', 'RSV', 'RSV', 'RSV', 'RSV', 'RSV']
    # 	RFC 7752 3.3.1.1. Node Flag Bits TLV
    LS_NODE_FLAGS = ['O', 'T', 'E', 'B', 'R', 'V', 'RSV', 'RSV']
    # 	RFC 7752 3.3.3.1. IGP Flags TLV
    LS_IGP_FLAGS = ['D', 'N', 'L', 'P', 'RSV', 'RSV', 'RSV', 'RSV']
    # 	RFC 7752 3.3.2.2.  MPLS Protocol Mask TLV
    LS_MPLS_MASK = ['LDP', 'RSVP-TE', 'RSV', 'RSV', 'RSV', 'RSV', 'RSV', 'RSV']
    # 	RFC 5307 1.2.
    LS_PROTECTION_MASK = [
        'ExtraTrafic',
        'Unprotected',
        'Shared',
        'Dedicated 1:1',
        'Dedicated 1+1',
        'Enhanced',
        'RSV',
        'RSV',
    ]

    def __init__(self, flags):
        self.flags = flags

    @classmethod
    def unpack(cls, data, pattern):
        pad = pattern.count('RSV')
        repeat = len(pattern) - pad
        flag_array = binascii.b2a_hex(data)
        hex_rep = hex(int(flag_array, 16))
        bit_array = BitArray(hex_rep)
        valid_flags = [
            concat_strs(''.join(item), ''.join(itertools.repeat('0', pad)))
            for item in itertools.product('01', repeat=repeat)
        ]
        valid_flags.append('0000')
        if bit_array.bin in valid_flags:
            flags = dict(zip(pattern, [0,] * len(pattern)))
            flags.update(dict((k, int(v)) for k, v in zip(pattern, bit_array.bin)))
        else:
            raise Notify(3, 5, "Invalid SR flags mask")
        return cls(flags=flags)

    def json(self, compact=None):
        return json.dumps(self.flags)

    def __repr__(self):
        return str(self.flags)
