from ..bf_gbl_env.var_env import *
from ..api import RareApi


class BfForwarder(Thread, RareApi):
    def __init__(
        self,
        threadID,
        name,
        platform,
        bfgc,
        salgc,
        sckr_file,
        sckw_file,
        no_log_keepalive,
    ):
        self.class_name = type(self).__name__
        Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.platform = platform
        self.bfgc = bfgc
        self.salgc = salgc
        self.no_log_keepalive = no_log_keepalive
        self.die = False
        self.hairpins = []
        self.mcast_nid = []
        self.mcast_xid = []
        self.file_r = sckr_file
        self.file_w = sckw_file
        self.recirc_port = 68
        self.dp_capabilities = {}
        ## List of physical ports that have been successfully added by
        ## the ports_add API call
        self.active_ports = {}
        self._clearTable()

    from .message_loop import run

    def haveSAL(self):
        if self.platform == "stordis_bf2556x_1t":
            if self.salgc is not None:
                return True
            self.controlPlaneMsg("%s: gearbox ports are not supported in BSP-less mode"
                                 % self.platform)
        return False

    def tearDown(self):
        os._exit(0)
        self.bfgc.interface.tear_down_stream()

    def _getTableKeys(self, table_name_key, table_dict, keys):
        try:
            for (d, k) in table_dict[table_name_key].entry_get(self.bfgc.target):
                if k is not None:
                    keys.append(k)
        except Exception as e:
            logger.warning("Error cleaning up: {}".format(e))
        logger.debug("   Keys to delete: %s" % (keys))

    def _delTableKeys(self, table_name_key, table_dict, keys):
        print("  Clearing Table {}".format(table_name_key))

        for k in keys:
            try:
                logger.debug("   Key to delete: %s" % (k))
                table_dict[table_name_key].entry_del(self.bfgc.target, [k])
            except Exception as e:
                logger.warning("Error cleaning up: {}".format(e))

    def _clearOneTable(self, table_name_key, table_dict):
        keys = []
        self._getTableKeys(table_name_key, table_dict, keys)
        self._delTableKeys(table_name_key, table_dict, keys)

    def _clearTable(self):
        table_dict = {}
        for (
            table_name,
            table_info,
        ) in self.bfgc.bfrt_info.parsed_info.table_info_dict_get().items():
            table_dict[table_info.name_get()] = gc._Table(
                table_info, self.bfgc.interface.reader_writer_interface
            )

        logger.warning("Generic table clearing: (Order not matters)")

        for table_name_key in table_dict.keys():
            if "ig_ctl_pkt_pre_emit" in table_name_key:
                continue
            if "ig_ctl_bundle" in table_name_key:
                continue
            # IN/OUT meters cannot be cleared
            if "policer" in table_name_key:
                continue
            if "rater" in table_name_key:
                continue
            if "hash" in table_name_key:
                continue
            # IN/OUT counters cannot be cleared
            if "stats" in table_name_key:
                continue
            if "stats4" in table_name_key:
                continue
            if "stats6" in table_name_key:
                continue
            if "ig_ctl" in table_name_key:
                self._clearOneTable(table_name_key, table_dict)
            if "eg_ctl" in table_name_key:
                self._clearOneTable(table_name_key, table_dict)

        logger.warning("Bundle specific clearing: (Order matters)")

        nhp_name = "%s.ig_ctl.ig_ctl_bundle.tbl_nexthop_bundle" % self.bfgc.pipe_name
        ase_name = "%s.ig_ctl.ig_ctl_bundle.ase_bundle" % self.bfgc.pipe_name
        apr_name = "%s.ig_ctl.ig_ctl_bundle.apr_bundle" % self.bfgc.pipe_name
        nhp_keys = []
        ase_keys = []
        apr_keys = []
        self._getTableKeys(nhp_name, table_dict, nhp_keys)
        self._getTableKeys(ase_name, table_dict, ase_keys)
        self._getTableKeys(apr_name, table_dict, apr_keys)
        self._delTableKeys(nhp_name, table_dict, nhp_keys)
        self._delTableKeys(ase_name, table_dict, ase_keys)
        self._delTableKeys(apr_name, table_dict, apr_keys)

        logger.warning("Bundle specific workaround: (Order matters)")
        tbl_apr_bundle = self.bfgc.bfrt_info.table_get(apr_name)
        apr_keys = [
            tbl_apr_bundle.make_key(
                [gc.KeyTuple("$ACTION_MEMBER_ID", 0x7fffffff)]
            )
        ]
        apr_data = [
            tbl_apr_bundle.make_data(
                [],
                "ig_ctl.ig_ctl_bundle.act_send_identical",
            )
        ]
        try:
           tbl_apr_bundle.entry_add(
               self.bfgc.target, apr_keys, apr_data
           )
        except gc.BfruntimeRpcException as e:
            logger.warning("bundle_add - %s" %  e.__str__())
            logger.warning("bundle_add - GRPC error code: %s" % e.grpc_error.code())
        tbl_nexthop_bundle = self.bfgc.bfrt_info.table_get(nhp_name)
        nhp_keys = []
        nhp_data = [
            tbl_nexthop_bundle.make_data(
                [
                    gc.DataTuple("$ACTION_MEMBER_ID", 0x7fffffff),
                ]
            )
        ]
        tbl_nexthop_bundle.default_entry_set(
            self.bfgc.target, nhp_data[0]
        )


        logger.warning("Multicast specific clearing: (Order matters)")

        mgid_name = "$pre.mgid"
        node_name = "$pre.node"
        mgid_keys = []
        node_keys = []
        self._getTableKeys(mgid_name, table_dict, mgid_keys)
        self._getTableKeys(node_name, table_dict, node_keys)
        self._delTableKeys(mgid_name, table_dict, mgid_keys)
        self._delTableKeys(node_name, table_dict, node_keys)

        logger.warning("Port metadata specific clearing: (Order not matters)")

        mtdt_name = "%s.ig_prs_main.$PORT_METADATA" % self.bfgc.pipe_name
        mtdt_keys = []
        self._getTableKeys(mtdt_name, table_dict, mtdt_keys)
        self._delTableKeys(mtdt_name, table_dict, mtdt_keys)

        self._getDataplaneCapability()
        self._setDataplaneCapability()

        pltfm = "platform tna/%s\n" % self.class_name.lower()
        self.file_w.write(pltfm)
        self.file_w.flush()

        data = "capabilities "
        for capability in self.dp_capabilities.keys():
            if self.dp_capabilities[capability] == True:
                data = data + capability + " "
        self.file_w.write(data + "\n")
        self.file_w.flush()
        logger.debug("tx: %s" % data)

    def _getDataplaneCapability(self):
        capabilities = {
            "mpls": "ig_ctl.ig_ctl_mpls.tbl_mpls_fib",
            "bridge": "ig_ctl.ig_ctl_bridge.tbl_bridge_target",
            "copp": "ig_ctl.ig_ctl_copp.tbl_ipv4_copp",
            "flowspec": "ig_ctl.ig_ctl_flowspec.tbl_ipv4_flowspec",
            "mcast": "eg_ctl.eg_ctl_mcast.tbl_mcast",
            "nat": "ig_ctl.ig_ctl_nat.tbl_ipv4_nat_trns",
            "polka": "ig_ctl.ig_ctl_polka.tbl_polka",
            "pbr": "ig_ctl.ig_ctl_pbr.tbl_ipv4_pbr",
            "pppoe": "ig_ctl.ig_ctl_pppoe.tbl_pppoe",
            "nsh": "ig_ctl.ig_ctl_nsh.tbl_nsh",
            "sgt": "ig_ctl.ig_ctl_sgt.tbl_sgt_in",
            "inacl": "ig_ctl.ig_ctl_acl_in.tbl_ipv4_acl",
            "outacl": "ig_ctl.ig_ctl_acl_out.tbl_ipv4_acl",
            "inqos": "ig_ctl.ig_ctl_qos_in.tbl_ipv4_qos",
            "outqos": "ig_ctl.ig_ctl_qos_out.tbl_ipv4_qos",
            "tun": "ig_ctl.ig_ctl_tunnel.tbl_tunnel4",
            "srv6": "ig_ctl.ig_ctl_ipv6b.tbl_ipv6_fib_host",
        }
        sub_capabilities = {
            "bier": {
                "parent": {"id": "mpls", "path": "ig_ctl_mpls.tbl_mpls_fib"},
                "action": "ig_ctl.ig_ctl_mpls.act_mpls_bier_label",
            },
            "duplab": {
                "parent": {"id": "mpls", "path": "ig_ctl_mpls.tbl_mpls_fib"},
                "action": "ig_ctl.ig_ctl_mpls.act_mpls_bcast_label",
            },
            "pwhe": {
                "parent": {"id": "mpls", "path": "ig_ctl_mpls.tbl_mpls_fib"},
                "action": "ig_ctl.ig_ctl_mpls.act_mpls_decap_pwhe",
            },
            "gre": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_gre",
            },
            "ipip": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_ip4ip",
            },
            "pckoudp": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_pckoudp",
            },
            "vxlan": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_vxlan",
            },
            "etherip": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_etherip",
            },
            "eoip": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_eoip",
            },
            "tmux": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_tmux",
            },
            "l2tp": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_l2tp",
            },
            "l3tp": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_l3tp",
            },
            "gtp": {
                "parent": {"id": "tun", "path": "ig_ctl_tunnel.tbl_tunnel4"},
                "action": "ig_ctl.ig_ctl_tunnel.act_tunnel_gtp",
            },
            "tap": {
                "parent": {"id": "bridge", "path": "ig_ctl_bridge.tbl_bridge_target"},
                "action": "ig_ctl.ig_ctl_bridge.act_set_bridge_routed",
            },
            "inspect_in": {
                "parent": {"id": "inacl", "path": "ig_ctl_acl_in.tbl_ipv4_insp"},
                "action": "ig_ctl.ig_ctl_acl_in.act_insp4",
            },
            "inspect_out": {
                "parent": {"id": "outacl", "path": "ig_ctl_acl_out.tbl_ipv4_insp"},
                "action": "ig_ctl.ig_ctl_acl_out.act_insp4",
            },
            "racl": {
                "parent": {"id": "inacl", "path": "ig_ctl_acl_in.tbl_ipv4_acl"},
                "action": "ig_ctl.ig_ctl_acl_in.act_punt",
            },
        }

        ig_ctl_path = "%s.ig_ctl" % self.bfgc.pipe_name
        tbl_list = self.bfgc.bfrt_info.table_dict.keys()
        for capability in capabilities.keys():
            capability_name = "%s" % (capabilities[capability])
            if capability_name in tbl_list:
                self.dp_capabilities[capability] = True
                logger.debug("%s - %s supported" % (self.class_name, capability))
            else:
                self.dp_capabilities[capability] = False
                logger.debug("%s - %s not supported" % (self.class_name, capability))

        for sub_capability in sub_capabilities.keys():
            capability_name = "%s.%s" % (
                ig_ctl_path,
                sub_capabilities[sub_capability]["parent"]["path"],
            )

            logger.debug(
                "%s - capability name %s " % (self.class_name, capability_name)
            )
            act_sub_capability_name = "%s" % (
                sub_capabilities[sub_capability]["action"]
            )
            logger.debug(
                "%s - action capability name %s "
                % (self.class_name, act_sub_capability_name)
            )
            if self.dp_capabilities[sub_capabilities[sub_capability]["parent"]["id"]]:
                tbl_capability = None
                try:
                    tbl_capability = self.bfgc.bfrt_info.table_get(capability_name)
                    logger.debug(
                        "%s - table %s action list %s "
                        % (
                            self.class_name,
                            capability_name,
                            tbl_capability.info.action_dict.keys(),
                        )
                    )
                    if (
                        act_sub_capability_name
                        in tbl_capability.info.action_dict.keys()
                    ):
                        logger.debug(
                            "%s - %s supported" % (self.class_name, sub_capability)
                        )
                        self.dp_capabilities[sub_capability] = True
                    else:
                        logger.debug(
                            "%s - %s not supported"
                            % (
                                self.class_name,
                                sub_capability,
                            )
                        )
                        self.dp_capabilities[sub_capability] = False

                except Exception as e:
                    self.dp_capabilities[sub_capability] = False
                    logger.debug(
                        "%s - cannot fetch table %s for %s not supported: %s "
                        % (self.class_name, capability_name, sub_capability, e)
                    )

            else:
                logger.debug(
                    "%s - %s not supported"
                    % (
                        self.class_name,
                        sub_capability,
                    )
                )
                self.dp_capabilities[sub_capability] = False

    def _setDataplaneCapability(self):
        for capability in self.dp_capabilities:
            self.__dict__[capability] = self.dp_capabilities[capability]

    def controlPlaneMsg(self, msg):
        logger.warning(msg)
        if msg[-1] != '\n':
            msg = msg + "\n"
        for line in msg.splitlines():
            self.file_w.write("dataplane-say " + line + "\n")
        self.file_w.flush()
