"""
  BIER Tables and Routes
"""

import socket
from vpp_object import VppObject
from vpp_ip_route import MPLS_LABEL_INVALID, VppRoutePath, VppMplsLabel


class BIER_HDR_PAYLOAD:
    BIER_HDR_PROTO_MPLS_DOWN_STREAM = 1
    BIER_HDR_PROTO_MPLS_UP_STREAM = 2
    BIER_HDR_PROTO_ETHERNET = 3
    BIER_HDR_PROTO_IPV4 = 4
    BIER_HDR_PROTO_IPV6 = 5
    BIER_HDR_PROTO_VXLAN = 6
    BIER_HDR_PROTO_CTRL = 7
    BIER_HDR_PROTO_OAM = 8


class VppBierTableID:
    def __init__(self, sub_domain_id, set_id, hdr_len_id):
        self.set_id = set_id
        self.sub_domain_id = sub_domain_id
        self.hdr_len_id = hdr_len_id


def find_bier_table(test, bti):
    tables = test.vapi.bier_table_dump()
    for t in tables:
        if (
            bti.set_id == t.bt_tbl_id.bt_set
            and bti.sub_domain_id == t.bt_tbl_id.bt_sub_domain
            and bti.hdr_len_id == t.bt_tbl_id.bt_hdr_len_id
        ):
            return True
    return False


def find_bier_route(test, bti, bp):
    routes = test.vapi.bier_route_dump(bti)
    for r in routes:
        if (
            bti.set_id == r.br_route.br_tbl_id.bt_set
            and bti.sub_domain_id == r.br_route.br_tbl_id.bt_sub_domain
            and bti.hdr_len_id == r.br_route.br_tbl_id.bt_hdr_len_id
            and bp == r.br_route.br_bp
        ):
            return True
    return False


def find_bier_disp_table(test, bdti):
    tables = test.vapi.bier_disp_table_dump()
    for t in tables:
        if bdti == t.bdt_tbl_id:
            return True
    return False


def find_bier_disp_entry(test, bdti, bp):
    entries = test.vapi.bier_disp_entry_dump(bdti)
    for e in entries:
        if bp == e.bde_bp and bdti == e.bde_tbl_id:
            return True
    return False


def find_bier_imp(test, bti, bp):
    imps = test.vapi.bier_imp_dump()
    for i in imps:
        if (
            bti.set_id == i.bi_tbl_id.bt_set
            and bti.sub_domain_id == i.bi_tbl_id.bt_sub_domain
            and bti.hdr_len_id == i.bi_tbl_id.bt_hdr_len_id
            and bp == i.bi_src
        ):
            return True
    return False


class VppBierTable(VppObject):
    """
    BIER Table
    """

    def __init__(self, test, id, mpls_label):
        self._test = test
        self.id = id
        self.mpls_label = mpls_label

    def add_vpp_config(self):
        self._test.vapi.bier_table_add_del(self.id, self.mpls_label, is_add=1)
        self._test.registry.register(self, self._test.logger)

    def remove_vpp_config(self):
        self._test.vapi.bier_table_add_del(self.id, self.mpls_label, is_add=0)

    def object_id(self):
        return "bier-table;[%d:%d:%d]" % (
            self.id.set_id,
            self.id.sub_domain_id,
            self.id.hdr_len_id,
        )

    def query_vpp_config(self):
        return find_bier_table(self._test, self.id)


class VppBierRoute(VppObject):
    """
    BIER route
    """

    def __init__(self, test, tbl_id, bp, paths):
        self._test = test
        self.tbl_id = tbl_id
        self.bp = bp
        self.paths = paths
        self.encoded_paths = []
        for path in self.paths:
            self.encoded_paths.append(path.encode())

    def add_vpp_config(self):
        self._test.vapi.bier_route_add_del(
            self.tbl_id, self.bp, self.encoded_paths, is_add=1
        )
        self._test.registry.register(self, self._test.logger)

    def remove_vpp_config(self):
        self._test.vapi.bier_route_add_del(
            self.tbl_id, self.bp, self.encoded_paths, is_add=0
        )

    def update_paths(self, paths):
        self.paths = paths
        self.encoded_paths = []
        for path in self.paths:
            self.encoded_paths.append(path.encode())
        self._test.vapi.bier_route_add_del(
            self.tbl_id, self.bp, self.encoded_paths, is_replace=1
        )

    def add_path(self, path):
        self.encoded_paths.append(path.encode())
        self._test.vapi.bier_route_add_del(
            self.tbl_id, self.bp, [path.encode()], is_add=1, is_replace=0
        )
        self.paths.append(path)
        self._test.registry.register(self, self._test.logger)

    def remove_path(self, path):
        self.encoded_paths.remove(path.encode())
        self._test.vapi.bier_route_add_del(
            self.tbl_id, self.bp, [path.encode()], is_add=0, is_replace=0
        )
        self.paths.remove(path)

    def remove_all_paths(self):
        self._test.vapi.bier_route_add_del(
            self.tbl_id, self.bp, [], is_add=0, is_replace=1
        )
        self.paths = []

    def object_id(self):
        return "bier-route;[%d:%d:%d:%d]" % (
            self.tbl_id.set_id,
            self.tbl_id.sub_domain_id,
            self.tbl_id.hdr_len_id,
            self.bp,
        )

    def query_vpp_config(self):
        return find_bier_route(self._test, self.tbl_id, self.bp)


class VppBierImp(VppObject):
    """
    BIER route
    """

    def __init__(self, test, tbl_id, src, ibytes):
        self._test = test
        self.tbl_id = tbl_id
        self.ibytes = ibytes
        self.src = src

    def add_vpp_config(self):
        res = self._test.vapi.bier_imp_add(self.tbl_id, self.src, self.ibytes)
        self.bi_index = res.bi_index
        self._test.registry.register(self, self._test.logger)

    def remove_vpp_config(self):
        self._test.vapi.bier_imp_del(self.bi_index)

    def object_id(self):
        return "bier-imp;[%d:%d:%d:%d]" % (
            self.tbl_id.set_id,
            self.tbl_id.sub_domain_id,
            self.tbl_id.hdr_len_id,
            self.src,
        )

    def query_vpp_config(self):
        return find_bier_imp(self._test, self.tbl_id, self.src)


class VppBierDispTable(VppObject):
    """
    BIER Disposition Table
    """

    def __init__(self, test, id):
        self._test = test
        self.id = id

    def add_vpp_config(self):
        self._test.vapi.bier_disp_table_add_del(self.id, is_add=1)
        self._test.registry.register(self, self._test.logger)

    def remove_vpp_config(self):
        self._test.vapi.bier_disp_table_add_del(self.id, is_add=0)

    def object_id(self):
        return "bier-disp-table;[%d]" % (self.id)

    def query_vpp_config(self):
        return find_bier_disp_table(self._test, self.id)


class VppBierDispEntry(VppObject):
    """
    BIER Disposition Entry
    """

    def __init__(
        self, test, tbl_id, bp, payload_proto, nh_proto, nh, nh_tbl, rpf_id=~0
    ):
        self._test = test
        self.tbl_id = tbl_id
        self.nh_tbl = nh_tbl
        self.nh_proto = nh_proto
        self.bp = bp
        self.payload_proto = payload_proto
        self.rpf_id = rpf_id
        self.nh = socket.inet_pton(socket.AF_INET, nh)

    def add_vpp_config(self):
        self._test.vapi.bier_disp_entry_add_del(
            self.tbl_id,
            self.bp,
            self.payload_proto,
            self.nh_proto,
            self.nh,
            self.nh_tbl,
            self.rpf_id,
            is_add=1,
        )
        self._test.registry.register(self, self._test.logger)

    def remove_vpp_config(self):
        self._test.vapi.bier_disp_entry_add_del(
            self.tbl_id,
            self.bp,
            self.payload_proto,
            self.nh_proto,
            self.nh,
            self.nh_tbl,
            self.rpf_id,
            is_add=0,
        )

    def object_id(self):
        return "bier-disp-entry;[%d:%d]" % (self.tbl_id, self.bp)

    def query_vpp_config(self):
        return find_bier_disp_entry(self._test, self.tbl_id, self.bp)