from ..bf_gbl_env.cst_env import *


class BfRuntimeGrpcClient:
    def __init__(self, grpc_addr, p4_program_name, client_id, pipe_name, bind):
        self.class_name = type(self).__name__
        self.grpc_addr = grpc_addr  # "localhost:50052"
        self.p4_name = p4_program_name
        self.client_id = client_id
        self.pipe_name = pipe_name
        self.bind = bind
        logger.warning("GRPC_ADDRESS: %s" % self.grpc_addr)
        logger.warning("P4_NAME: %s" % self.p4_name)
        logger.warning("CLIENT_ID: %s" % self.client_id)

        while True:
            try:
                self.interface = gc.ClientInterface(
                    self.grpc_addr, client_id=self.client_id, device_id=0
                )
            except Exception as e:
                logger.error("Failed to connect to grpc server: %s, retrying" % e)
                ## Note: there appears to be a race condition inside
                ## gc.ClientInterface that can cause the call to throw
                ## an exception when in fact the socket was connected
                ## successfully.  In that case, this loop never
                ## terminates, because bf_switchd signals to the
                ## caller that a client is already connected (with
                ## this client being ourselves). This behaviour was
                ## seen when using a sleep duration of 2 seconds
                ## here. 4 seconds appears to be safe. This should be
                ## reported to Intel as a bug.
                sleep(4)
                continue
            else:
                logger.warning("Connected to grcp server")
                break

        # self.bfrt_info = self.interface.bfrt_info_get(self.p4_name)
        self.bfrt_info = self.interface.bfrt_info_get()
        self.port_table = self.bfrt_info.table_get("$PORT")
        self.port_str_info_table = self.bfrt_info.table_get("$PORT_STR_INFO")
        self.port_hdl_info_table = self.bfrt_info.table_get("$PORT_HDL_INFO")
        self.stat_table = self.bfrt_info.table_get("$PORT_STAT")
        self.metadata_table = self.bfrt_info.table_get("$PORT_METADATA")
        self.target = gc.Target(device_id=0, pipe_id=0xFFFF)
        self.client_id = int(client_id)
        if self.bind == True:
            self.interface.bind_pipeline_config(self.p4_name)

        ## Get the device configuration table. It contains the list of
        ## external and internal ports, among other things
        tables = self.bfrt_info.table_name_list_get()
        if 'tf1.dev.device_configuration' in tables:
            dev_cfg = self.bfrt_info.table_get('tf1.dev.device_configuration')
            self.arch = "tofino"
        elif 'tf2.dev.device_configuration' in tables:
            dev_cfg = self.bfrt_info.table_get('tf2.dev.device_configuration')
            self.arch = "tofino2"
        else:
            raise Exception("Can't fetch device configuration table")
        resp = dev_cfg.default_entry_get(
            self.target, { "from_hw": False}, p4_name = self.p4_name)
        self.dev_cfg = next(resp)[0].to_dict()

    def tearDown(self):
        self.interface.tear_down_stream()
