-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local cmn = require 'common'
local class = require 'mc.class'
local log = require 'mc.logging'
local mdb = require 'mc.mdb'
local def = require 'biz_topo.def'
local c_business_connector = require 'biz_topo.class.business_connector'
local c_bdf_config = require 'biz_topo.class.bdf_config'
local c_ipmi_channel_config = require 'biz_topo.class.ipmi_channel_config'
local c_serdes = require 'biz_topo.class.serdes'
local c_unit_configuration = require 'biz_topo.class.unit_configuration'
local c_pcie_addr_info = require 'biz_topo.class.pcie_addr_info'
local file_sec = require 'utils.file'
local log_helper = require 'infrastructure.log_helper'
local c_topo_reader = require 'biz_topo.topo_reader'
local skynet = require 'skynet'
local utils = require 'mc.utils'
local imu_cmd = require 'imu'

---@class BizTopo @业务Topo组合类
---@field private biz_connector_list BusinessConnector[]
---@field private serdes_list SerDes[]
---@field private pcie_addr_info_list PCIeAddrInfo[]
---@field private unit_config_list UnitConfiguration[]
---@field private src_connectors SrcBizConnector[]
---@field private target_pcie_connectors PCIeBizConnector[] @对端组件（PCIe槽位容器）PCIe业务连接器
local c_biz_topo = class()

local e_serdes_mode <const> = {
    NONE = 0,
    PCIE = 1,
    HCCS_DMI = 2,
    SATA = 3,
    SAS = 4,
    CXL = 5,
    GBE_ETH = 6,
    USB = 7,
    GPIO = 8
}

local ZONE_TABLE <const> = {
    [1] = 'A',
    [2] = 'B',
    [3] = 'C',
    [4] = 'D',
    [5] = 'E',
    [6] = 'F'
}

function c_biz_topo:init_bdf_map(object)
    local socket_id = object.mds_obj.SocketId
    local configs = object.configs

    if not socket_id then
        return
    end

    object:build_bdf_cfgs()
    self.bdf_config_map = self.bdf_config_map or {}
    self.bdf_config_map[socket_id] = self.bdf_config_map[socket_id] or {}

    local port_id
    for _, config in pairs(configs) do
        port_id = config.port_id
        if port_id then
            self.bdf_config_map[socket_id][port_id] = {config.bus, config.device, config.func}
        end
    end

end

-- 对象分发处理回调函数
function c_biz_topo:on_add_object(class_name, mds_obj, position, complete_signal, bus)
    local switch = {
        ['PcieAddrInfo'] = function()
            local object = c_pcie_addr_info.new(mds_obj, position)
            table.insert(self.pcie_addr_info_list, object)
        end,
        ['BusinessConnector'] = function()
            local object = c_business_connector.new(mds_obj, position, complete_signal, bus)
            table.insert(self.biz_connector_list, object)
            -- PCIe业务连接器
            if object:is_pcie_biz_connector() then
                self.pcie_container[position] = true
            end
        end,
        ['SerDes'] = function()
            local object = c_serdes.new(mds_obj, position)
            table.insert(self.serdes_list, object)
            self.ubc_config_position = position
            self.ubc_config_add_complete = false
        end,
        ['UnitConfiguration'] = function()
            local object = c_unit_configuration.new(mds_obj, position)
            table.insert(self.unit_config_list, object)
            self.psr_position = position
        end,
        ---@class BusinessTopoNode @业务拓扑节点
        ['BusinessTopoNode'] = function()
            local object = { mds_obj = mds_obj, position = position }
            table.insert(self.biz_topo_node_list, object)
            if object.mds_obj.Type == 'Compute' then
                self.src_biz_connector_container[position] = true
            end
        end,
        ---@class BDFConfig @SocketId/PortId与SSBDF映射关系
        ['BDFConfig'] = function()
            local object = c_bdf_config.new(mds_obj, position)
            self:init_bdf_map(object)
            self.bdfconfigs[object.mds_obj.SocketId] = object
        end,
        ---@class PCIeIPMIChannelConfig @multihost hostId与ipmi通道映射关系
        ['PCIeIPMIChannelConfig'] = function()
            local config_obj = c_ipmi_channel_config.new(mds_obj, position)
            log:notice('[BizTopo] imu_cmd ipmi_channel_config')
            log:notice(config_obj.configs)
            imu_cmd.ipmi_channel_config = config_obj.configs
        end
    }

    if switch[class_name] then
        log:debug('[BizTopo] Add object %s', mds_obj.name)
        switch[class_name]()
        log:notice('[BizTopo] Add object %s successfully', mds_obj.name)
    end
end

local function remove_ele_by_position(list, position)
    for i, obj in pairs(list) do
        if obj.position == position then
            table.remove(list, i)
            break
        end
    end
end

function c_biz_topo:on_delete_object(class_name, mds_obj, position)
    local switch = {
        ['PcieAddrInfo'] = function()
            remove_ele_by_position(self.pcie_addr_info_list, position)
        end,
        ['BusinessConnector'] = function()
            remove_ele_by_position(self.biz_connector_list, position)
            remove_ele_by_position(self.target_pcie_connectors, position)
            remove_ele_by_position(self.src_connectors, position)
        end,
        ['BusinessTopoNode'] = function()
            remove_ele_by_position(self.biz_topo_node_list, position)
        end,
    }

    if switch[class_name] then
        log:notice('[BizTopo] Delete object, class: %s, position: %s', class_name, position)
        switch[class_name]()
    end
end

-- 从元素集合中查找指定元素
--     1.如果元素为基本数据类型，则传入元素值
--     2.如果元素为table类型，则传入条件匹配函数（匹配成功返回true）
local function find(coll, val_or_match_func)
    for i, item in pairs(coll) do
        -- table类型，按条件查找
        if type(val_or_match_func) == 'function' then
            if val_or_match_func(item) == true then
                return item
            end
        else
            -- 简单类型
            if item == val_or_match_func then
                return i
            end
        end
    end
    return nil
end

-- 根据类名和position获取所有对象
function c_biz_topo:get_objs(class_name, position)
    local obj_coll = {
        ['BusinessTopoNode'] = self.biz_topo_node_list,
        ['BusinessConnector'] = self.biz_connector_list,
        ['PCIeBizConnector'] = self.target_pcie_connectors,
        ['UnitConfiguration'] = self.unit_config_list,
        ['PCIeAddrInfo'] = self.pcie_addr_info_list,
    }
    if not obj_coll[class_name] then
        return nil
    end
    local objs = {}
    for _, obj in pairs(obj_coll[class_name]) do
        if not position or obj.position == position then
            table.insert(objs, obj)
        end
    end
    return objs
end

-- 根据PCIe设备的position查询父级CSR中对应的PCIeAddrInfo对象
function c_biz_topo:get_pcie_addr_by_pos(pos)
    local biz_conn = find(self.target_pcie_connectors, function(item)
        if item.ref_pcie_addr_info and ((item.ref_mgmt_connector and item.ref_mgmt_connector.GroupPosition == pos) or
                (item.ref_mgmt_connector_tianchi and item.ref_mgmt_connector_tianchi.GroupPosition == pos)) then
            return true
        end
    end)
    return biz_conn and biz_conn.ref_pcie_addr_info or nil
end

-- MaxLinkRate解析（MaxLinkRate为："PCIe 5.0"）
local function parse_max_link_rate(max_link_rate)
    local ok, num = pcall(function()
        return tonumber(string.match(max_link_rate, "%d+"))
    end)
    if not ok or not num then
        log:error("[BizTopo] parse_max_link_rate fail, max_link_rate: %s", max_link_rate)
        return ""
    end
    return "Gen" .. num
end

-- 根据PCIe设备的position查询父级CSR中对应的下行连接器对象，获取PCIeType槽位信息
function c_biz_topo:get_pcie_type_by_pos(pos)
    local biz_conn = find(self.target_pcie_connectors, function(item)
        if item.ref_pcie_addr_info and ((item.ref_mgmt_connector and item.ref_mgmt_connector.GroupPosition == pos) or
                (item.ref_mgmt_connector_tianchi and item.ref_mgmt_connector_tianchi.GroupPosition == pos)) then
            return true
        end
    end)
    if not biz_conn or not biz_conn.mds_obj.MaxLinkRate then
        return ""
    end
    return parse_max_link_rate(biz_conn.mds_obj.MaxLinkRate)
end

-- @param lane_index: serdes的LinkWidth递增的偏移，代表在UBC口上行serdes的偏移
-- @param pcie_start_lane: src_connector中Port的连接带宽Offset，代表下行端口的偏移
-- @param link_width: cie下行连接器的连接带宽，一般是x4
-- @param port_offset: port_offset大于0时：BCU到SEU/IEU一分多场景，且BCU的连接器中Port的ID重复，即Port内拆分时，需要以port_offset记录port内偏移
local function is_valid_serdes(serdes, lane_index, pcie_start_lane, link_width, port_offset)
    -- 偏移在当前serdes内
    if lane_index >= pcie_start_lane + port_offset and lane_index < pcie_start_lane + port_offset + link_width then
        return true
    end

    -- 存在内部偏移，内部偏移小于端口总带宽，为一分多场景
    -- 支持UBC一分二x8 SerDes, x8 SerDes一分二后对应两个PCIe槽位场景
    if pcie_start_lane == lane_index and port_offset > 0 and port_offset < serdes.mds_obj.LinkWidth then
        return true
    end

    return false
end

local function get_device_index(actual_res_order, mode_config, link_width, port_offset)
    local device_index = 1 + port_offset

    if not actual_res_order or not mode_config or not mode_config.Device then
        return device_index
    end

    if #mode_config.Device >= link_width + port_offset and #actual_res_order > 0 then
        device_index = 1 + #mode_config.Device - link_width - port_offset
    end

    return device_index
end

-- 在SerDes中找到对应的port id
local function get_port_id(pai, high_serdes, actual_res_order, link_width, port_offset)
    if not high_serdes or not high_serdes.mds_obj then
        log:error('[BizTopo] No matching Mode found, mds_obj invalid.')
        return nil
    end

    local work_mode = high_serdes.mds_obj.WorkMode -- 默认是csr配置的值
    if pai and def.controller_type_to_work_mode[pai.mds_obj.ControllerType] then
        work_mode = def.controller_type_to_work_mode[pai.mds_obj.ControllerType]
    end
    local mode_config = cmn.find(high_serdes.mds_obj.ModeConfigs, function(item)
        if item.Mode == work_mode then
            return true
        end
    end)

    -- 在SerDes模式配置中找到对应的工作模式：1是PCIe；3是SATA；4是SAS
    if not mode_config then
        log:error('[BizTopo] No matching Mode found, WorkMode=%s', high_serdes.mds_obj.WorkMode)
        return nil
    end

    if port_offset >= high_serdes.mds_obj.LinkWidth then
        port_offset = 0
    end
    local device_index = get_device_index(actual_res_order, mode_config, link_width, port_offset)
    return mode_config.Device[device_index], mode_config.ControllerIndex[1], high_serdes
end

-- @param src_connector: bcu上的连接器
-- @param pcie_start_lane: src_connector中Port的连接带宽Offset，代表下行端口的偏移
-- @param link_width: pcie下行连接器的连接带宽，一般是x4
-- @param port_offset: BCU到SEU/IEU一分多场景，且BCU的连接器中Port的ID重复，即Port内拆分时，需要以port_offset记录port内偏移
-- @param pai: PcieAddrInfo对象
-- @return string: 从SrcBizConnector的SerDes模式配置表中获取Device号，作为PcieAddrInfo的portId
local function get_device(src_connector, pcie_start_lane, link_width, port_offset, pai)
    local res_serdeses = {}
    -- ActualResourceOrder中配置了SerDes的实际排布，如果ActualResourceOrder未配置则以默认排布为准
    local actual_res_order = src_connector.mds_obj.ActualResourceOrder
    if actual_res_order and #actual_res_order > 0 then
        for i, name in ipairs(actual_res_order) do
            res_serdeses[i] = cmn.find(src_connector.res_serdeses, function(item)
                if item.mds_obj.Name == name then
                    return true
                end
            end)
        end
    else
        -- UpstreamResources中配置了SerDes的默认排布
        res_serdeses = src_connector.res_serdeses
    end
    -- 默认SerDes序列编号，编号小的在高位
    local default_order = {}
    for i, res in ipairs(src_connector.mds_obj.UpstreamResources) do
        default_order[res.Name] = i
    end

    -- 当一个PCIeBizConnector对应多个SerDes时，取这几个SerDes在默认排布中的最高位Device
    local high_serdes = nil
    local lane_index = 0
    local entry_flag = false
    for _, serdes in ipairs(res_serdeses) do
        if is_valid_serdes(serdes, lane_index, pcie_start_lane, link_width, port_offset) then
            if not entry_flag then
                high_serdes = serdes
                entry_flag = true
            end
            high_serdes = default_order[high_serdes.mds_obj.Name] <
                default_order[serdes.mds_obj.Name] and high_serdes or serdes
        end
        lane_index = lane_index + serdes.mds_obj.LinkWidth
    end

    if not high_serdes then
        return nil
    end

    return get_port_id(pai, high_serdes, actual_res_order, link_width, port_offset)
end

-- 带宽解析（位宽格式为："aXb"，a为端口数，b为端口带宽，a可以不存在）
local function parse_link_width(src_link_width)
    local port_cnt = 1
    src_link_width = string.upper(src_link_width)
    local pos = string.find(src_link_width, 'X')
    if pos > 1 then
        port_cnt = tonumber(string.sub(src_link_width, 1, pos - 1))
    end
    local link_width = tonumber(string.sub(src_link_width, pos + 1))
    return port_cnt, link_width
end

local function parse_port_name(port_name)
    local a, b, c = string.byte(port_name, 1, 3)
    local zone = a - string.byte('A', 1) + 1
    local port_id = (b - string.byte('1', 1)) * 4 + (c - string.byte('a', 1) + 1)
    return zone, port_id
end

-- 根据对端组件信息获取源端连接器端口信息
local function match_src_connector(src_connectors, uid, unit_config, target_port_id)
    if not target_port_id or not unit_config or not unit_config.SrcPortName or
        not unit_config.SrcPortName[1] then
        return
    end

    local index = find(unit_config.TargetPortID, target_port_id)
    if not index then
        return
    end
    local port_name = unit_config.SrcPortName[index]
    local port
    local sc = find(src_connectors, function(item)
        port = find(item.mds_obj.Ports, function(item0)
            if port_name == item0.Name then
                return true
            end
        end)
        if port then
            return true
        end
    end)
    if not sc or not port then
        log:error('[BizTopo] No matching SrcBizConnector found, port_name=%s', port_name)
        return
    end

    -- 获取该端口在业务连接器内的索引，每个UB连接器都由若干个X4端口组合而成
    local start_lane = port.Offset
    return sc, start_lane
end

local function init_config_match_info(configuration)
    configuration.matched_times = 0
    configuration.status = def.TOPO_STATUS.INITIAL_STATE
end

local function get_valid_uid(uid)
    local valid_uid = ''
    for letter in string.gmatch(uid, '[%w.]') do
        valid_uid = valid_uid .. letter
    end
    return valid_uid
end

local function match_configuration(configuration, index, topo_info, zone, port_id)
    if not configuration.TargetPortID or not configuration.SrcPortName then
        log:error("[BizTopo] unit configuration param error")
        return def.TOPO_STATUS.INCOMPLETE_CONNECTION
    end
    local target_port_id = configuration.TargetPortID[index]

    if not topo_info or not topo_info[zone] or not topo_info[zone][port_id] or
        get_valid_uid(topo_info[zone][port_id].uid) ~= get_valid_uid(configuration.UID) then
        log:debug("[BizTopo] INCOMPLETE_CONNECTION uid: %s", configuration.UID)
        return def.TOPO_STATUS.INCOMPLETE_CONNECTION
    end

    if target_port_id ~= topo_info[zone][port_id].target_port_id then
        log:debug("[BizTopo] INCORRECT_CONNECTION uid: %s", configuration.UID)
        return def.TOPO_STATUS.INCORRECT_CONNECTION
    end

    configuration.matched_times = configuration.matched_times + 1
    if configuration.matched_times == #configuration.TargetPortID then
        log:debug("[BizTopo] CONNECTION MATCHED uid: %s", configuration.UID)
        return def.TOPO_STATUS.MATCHED
    elseif configuration.matched_times < #configuration.SrcPortName then
        log:debug("[BizTopo] INCOMPLETE_CONNECTION uid: %s", configuration.UID)
        return def.TOPO_STATUS.INCOMPLETE_CONNECTION
    end
end

local function get_default_configuration(configuration_list)
    for _, configuration in pairs(configuration_list) do
        if configuration.Default then
            return configuration
        end
    end

    return configuration_list[1]
end

local function sort_configurations(configurations)
    table.sort(configurations, function(a, b)
        a.port_num = a.TargetPortID and #a.TargetPortID or 0
        b.port_num = b.TargetPortID and #b.TargetPortID or 0
        if a.port_num > b.port_num then
            return true
        else
            return false
        end
    end)
end

-- 1.线缆连接信息完全读不上来或者读上来的配置均不符合当前Riser所有支持的配置，由硬件提供默认配置，BMC使用默认配置进行拓扑建链和线缆检测告警，默认配置通过增加字段标识。
-- 2.Riser支持1/2/3和2/3两种配置时，读到2/3配置，BMC按照2/3配置进行拓扑建链和线缆检测告警。
-- 3.如果线缆连接信息只满足配置A的子集，不满足其他配置的子集，BMC按照配置A进行拓扑建链和线缆检测告警。
-- 4.以上逻辑均在Riser卡支持多种配置下进行，若只Riser卡支持单一配置，BMC在拓扑建链时不读取线缆连接信息来匹配拓扑建链配置。
function c_biz_topo:match_unit_config_with_topo(configuration_list, default_configuration)
    if not configuration_list or not next(configuration_list) then
        error("[BizTopo] unit config list empty")
    end

    if not self.biz_topo_node_list or #self.biz_topo_node_list == 0 or 
        not self.biz_connector_list or #self.biz_connector_list == 0 then
        error("[BizTopo] parameter check fail")
    end

    self:topo_reader_init()

    -- configurations按照TargetPortID长度降序排列，以长度从大到小匹配
    sort_configurations(configuration_list)

    -- 找到正确的实时拓扑信息
    local zone, port_id
    for _, configuration in pairs(configuration_list) do
        init_config_match_info(configuration)
        for index, port_name in pairs(configuration.SrcPortName) do
            zone, port_id = parse_port_name(port_name)
            configuration.status = match_configuration(configuration, index, self.topo_info, ZONE_TABLE[zone], port_id)
            if configuration.status == def.TOPO_STATUS.MATCHED then
                return configuration
            end
            if configuration.status == def.TOPO_STATUS.INCORRECT_CONNECTION then
                break
            end
        end
    end

    -- 优先匹配属于子集的默认配置
    local configuration = cmn.find(configuration_list, function(item)
        if item.Default and item.matched_times > 0 and item.status ~= def.TOPO_STATUS.INCORRECT_CONNECTION then
            return true
        end
    end)
    if configuration then
        return configuration
    end

    -- 其次从最接近的子集匹配
    configuration = cmn.find(configuration_list, function(item)
        if item.status == def.TOPO_STATUS.INCOMPLETE_CONNECTION and item.matched_times > 0  then
            return true
        end
    end)
    if configuration then
        return configuration
    end

    -- 全都匹配不上用默认
    return default_configuration
end

local function is_bcu_index_valid(config_bcu_index, port_bcu_index)
    if not port_bcu_index or port_bcu_index == 0 then
        return true
    end

    if not config_bcu_index or config_bcu_index == 0 then
        return true
    end

    if config_bcu_index == port_bcu_index then
        return true
    end

    return false
end

-- 根据组件类型、槽位号、UID从整机组件配置信息中获取对应的配置项
function c_biz_topo:get_matched_unit_config(unit_config_list, container_type, container_slot,
                                       container_uid, port_bcu_index)
    local configuration_list = {}
    for _, v in pairs(unit_config_list) do
        if v.mds_obj.SlotType ~= container_type or v.mds_obj.SlotNumber ~= container_slot then
            goto continue
        end

        for _, config in pairs(v.mds_obj.Configurations) do
            if config.UID ~= container_uid or not is_bcu_index_valid(config.BCUIndex, port_bcu_index) then
                goto next
            end

            table.insert(configuration_list, config)
            ::next::
        end

        ::continue::
    end

    log:notice("[BizTopo] configuration list length: %s, uid: %s", #configuration_list, container_uid)
    if #configuration_list == 0 then
        log:notice("[BizTopo] uid: %s config empty", container_uid)
        return
    elseif #configuration_list == 1 then
        return configuration_list[1]
    else
        local default_configuration = get_default_configuration(configuration_list)
        local ok, configuration = pcall(function()
            return self:match_unit_config_with_topo(configuration_list, default_configuration)
        end)
        if not ok or not configuration then
            log:error("match unit config with topo fail, ret: %s", configuration)
            configuration = default_configuration
        end
        if configuration then
            log:notice("[BizTopo] ----------- %s configuration info:-------------", container_uid)
            log:notice(configuration.SrcPortName)
            log:notice(configuration.TargetPortID)
            log:notice(configuration.Default)
        end

        return configuration
    end
end

local override_root_bdf <const> = {
    [0x0] = {
        [0] = { 0x01, 0, 0 },
        [2] = { 0x01, 0x2, 0 },
        [4] = { 0x01, 0x4, 0 },
        [6] = { 0x01, 0x6, 0 },
        [8] = { 0x16, 0, 0 },
        [10] = { 0x16, 0x4, 0 },
        [12] = { 0x40, 0, 0 },
        [14] = { 0x40, 0x2, 0 },
        [16] = { 0x40, 0x4, 0 },
        [18] = { 0x40, 0x6, 0 },
        [20] = { 0x56, 0, 0 },
        [22] = { 0x56, 0x2, 0 },
        [24] = { 0x56, 0x4, 0 },
        [26] = { 0x56, 0x6, 0 },
        [32] = { 0x5F, 0, 0 },
        [34] = { 0x5F, 0x2, 0 },
        [36] = { 0x5F, 0x4, 0 },
        [38] = { 0x5F, 0x6, 0 }
    },
    [0x1] = {
        [0] = { 0x80, 0, 0 },
        [2] = { 0x80, 0x2, 0 },
        [4] = { 0x80, 0x4, 0 },
        [6] = { 0x80, 0x6, 0 },
        [8] = { 0x95, 0, 0 },
        [10] = { 0x95, 0x4, 0 },
        [12] = { 0xAA, 0, 0 },
        [14] = { 0xAA, 0x2, 0 },
        [16] = { 0xAA, 0x4, 0 },
        [18] = { 0xAA, 0x6, 0 },
        [20] = { 0xC0, 0, 0 },
        [22] = { 0xC0, 0x2, 0 },
        [24] = { 0xC0, 0x4, 0 },
        [26] = { 0xC0, 0x6, 0 },
        [32] = { 0xD5, 0, 0 },
        [34] = { 0xD5, 0x2, 0 },
        [36] = { 0xD5, 0x4, 0 },
        [38] = { 0xD5, 0x6, 0 }
    }
}

-- SAS根据SocketID、PortID得到RootBDF
local sas_root_bdf <const> = {
    [0x0] = {
        [0] = { 0x32, 0x04, 0 },
        [1] = { 0x32, 0x04, 0 },
        [2] = { 0x32, 0x04, 0 },
        [3] = { 0x32, 0x04, 0 },
        [4] = { 0x32, 0x04, 0 },
        [5] = { 0x32, 0x04, 0 },
        [6] = { 0x32, 0x04, 0 },
        [7] = { 0x32, 0x04, 0 },
        [8] = { 0x32, 0x04, 0 },
        [9] = { 0x32, 0x04, 0 },
        [10] = { 0x32, 0x04, 0 },
        [11] = { 0x32, 0x04, 0 },
        [12] = { 0x32, 0x04, 0 },
        [13] = { 0x32, 0x04, 0 },
        [14] = { 0x32, 0x04, 0 },
        [15] = { 0x32, 0x04, 0 }
    },
    [0x1] = {
        [0] = { 0x72, 0x04, 0 },
        [1] = { 0x72, 0x04, 0 },
        [2] = { 0x72, 0x04, 0 },
        [3] = { 0x72, 0x04, 0 },
        [4] = { 0x72, 0x04, 0 },
        [5] = { 0x72, 0x04, 0 },
        [6] = { 0x72, 0x04, 0 },
        [7] = { 0x72, 0x04, 0 }
    }
}

function c_biz_topo:set_bdf(pai)
    if def.com_type_to_type[pai.mds_obj.ComponentType] == def.device_type.SAS then
        if sas_root_bdf[pai.mds_obj.SocketID] and sas_root_bdf[pai.mds_obj.SocketID][pai.mds_obj.PortID] then
            pai.mds_obj.Bus, pai.mds_obj.Device, pai.mds_obj.Function = table.unpack(
                sas_root_bdf[pai.mds_obj.SocketID][pai.mds_obj.PortID])
        end
        return
    end

    if override_root_bdf[pai.mds_obj.SocketID] and override_root_bdf[pai.mds_obj.SocketID][pai.mds_obj.PortID] then
        pai.mds_obj.Bus, pai.mds_obj.Device, pai.mds_obj.Function = table.unpack(
            override_root_bdf[pai.mds_obj.SocketID][pai.mds_obj.PortID])
    end

    -- PSR中的配置进行覆盖
    if self.bdf_config_map[pai.mds_obj.SocketID] and self.bdf_config_map[pai.mds_obj.SocketID][pai.mds_obj.PortID] then
        local root_bdf = self.bdf_config_map[pai.mds_obj.SocketID][pai.mds_obj.PortID]
        if root_bdf then
            pai.mds_obj.Bus, pai.mds_obj.Device, pai.mds_obj.Function = table.unpack(root_bdf)
        end
    end
end

function c_biz_topo:set_simple_device(pai, unit_config, slot)
    if not pai then
        return
    end

    if unit_config and unit_config.Device and #unit_config.Device > 0 then
        pai.mds_obj.PortID = unit_config.Device[slot]
        self:set_bdf(pai)
        log:notice('[BizTopo] AddrInfo:SocketID: %s, PortID: %s, SlotID: %s, Bus: %s, Device: %s, Function: %s',
            pai.mds_obj.SocketID, pai.mds_obj.PortID, pai.mds_obj.SlotID, pai.mds_obj.Bus, pai.mds_obj.Device,
            pai.mds_obj.Function)
    end
end

-- 业务Topo关系建立，基础计算组件（源端-src），PCIe卡槽位所在的组件（对端-target）
function c_biz_topo:construct_biz_topo()
    local pai, c_slot, c_type, c_uid, unit_config, src_connector, start_lane
    local device, controller_index, serdes
    for _, c in ipairs(self.target_pcie_connectors) do
        -- PcieAddrInfo
        pai = c.ref_pcie_addr_info
        if not pai then
            log:error('[BizTopo] No matching ref PCIeAddrInfo found, PCIeBizConnector: Slot=%s, position=%s',
                c.mds_obj.Slot, c.position)
            goto next_loop
        end
        c_slot, c_type = pai:get_prop('ContainerSlot'), pai:get_prop('ContainerUnitType')
        c_uid = pai:get_prop('ContainerUID')

        -- 默认组件内上行端口和下行端口是一对一和多对一的关系
        -- 上行端口和下行端口多对多场景（下行连接器出多个Port）不考虑

        log:notice('[BizTopo] Name=%s, ContainerSlot=%s, ContainerUnitType=%s, ContainerUID=%s',
            pai.mds_obj.name, c_slot, c_type, c_uid)
        -- 当前PCIe业务连接器所在IEU组件的Index和起始槽位号PCIeStartSlot
        unit_config = self:get_matched_unit_config(self.unit_config_list, c_type, c_slot, c_uid, c.bcu_index)
        if not unit_config then
            log:error('[BizTopo] No matching UnitConfig found, PCIeBizConnector: Slot=%s, position=%s',
                c.mds_obj.Slot, c.position)
            goto next_loop
        end
        pai.mds_obj.SlotID = unit_config.Slot[c.mds_obj.Slot]
        -- 匹配源端业务连接器
        src_connector, start_lane = match_src_connector(self.src_connectors, c_uid, unit_config, c.first_res_port_id)
        if not src_connector then
            log:error('[BizTopo] No matching src BizConnector found: Slot=%s, position=%s', c.mds_obj.Slot, c.position)
            goto next_loop
        end
        c.src_connector = src_connector

        -- 根据lane的偏移计算出Device
        -- 默认PCIeBizConnector的所有Width都归属于同一个SrcBizConnector下的同一组SerDes
        device, controller_index, serdes = get_device(src_connector, start_lane, c.link_width, c.port_offset, pai)
        if not serdes or not device or not controller_index then
            log:error('[BizTopo] No matching SerDes found. Slot=%s, position=%s', c.mds_obj.Slot, c.position)
            goto next_loop
        end
        log:notice('[BizTopo] PCIeBizConnector: Slot=%s, position=%s serdes_name=%s', c.mds_obj.Slot,
            c.position, serdes.mds_obj.Name)
        if unit_config.Device and #unit_config.Device > 0 then
            pai.mds_obj.PortID = unit_config.Device[c.mds_obj.Slot]
        else
            pai.mds_obj.PortID = device
        end

        pai.mds_obj.SocketID, pai.mds_obj.ControllerIndex = serdes.mds_obj.SocketID, controller_index
        if c.bcu_index and c.bcu_index > 1 then
            pai.mds_obj.SocketID = serdes.mds_obj.SocketID + self.per_bcu_cpu_count * (c.bcu_index - 1)
        end
        self:set_bdf(pai)

        log:notice('[BizTopo] AddrInfo:SocketID: %s, PortID: %s, SlotID: %s, Bus: %s, Device: %s, Function: %s',
            pai.mds_obj.SocketID, pai.mds_obj.PortID, pai.mds_obj.SlotID, pai.mds_obj.Bus, pai.mds_obj.Device,
            pai.mds_obj.Function)
        ::next_loop::
        self:set_simple_device(pai, unit_config, c.mds_obj.Slot)
    end
end

-- 打印输出业务Topo
function c_biz_topo:print_biz_topo()
    log:notice('[BizTopo] Print business topo ----- Start')
    local c_info, rc, rc_info, sc, sc_info, serdes_info, format

    for _, c in ipairs(self.target_pcie_connectors) do
        c_info = string.format('PCIeBizConnector%s_%s', c.mds_obj.Slot, c.position)

        rc = c.res_connectors[1]
        rc_info = '???'
        if rc then
            rc_info = string.format('TargetBizConnector%s_%s', rc.mds_obj.Slot, rc.position)
        end

        sc = c.src_connector
        if not sc then
            log:debug('[BizTopo] %s -- %s <-> %s -- %s', c_info, rc_info, '???', '???')
            goto next
        end

        sc_info = string.format('SrcBizConnector%s_%s', sc.mds_obj.Slot, sc.position)
        for i, serdes in ipairs(sc.res_serdeses) do
            serdes_info = string.format('CPU%s-%s', serdes.mds_obj.SocketID,
                serdes.mds_obj.Name)
            if i == 1 then
                log:debug('[BizTopo] %s -- %s <-> %s -- %s', c_info, rc_info, sc_info, serdes_info)
            else
                format = '[BizTopo] %' .. #c_info .. 's    %' .. #rc_info .. 's     %' ..
                    #sc_info .. 's -- %s'
                log:debug(format, '', '', '', serdes_info)
            end
        end

        ::next::
    end

    for _, info in ipairs(self.pcie_addr_info_list) do
        log:notice('[BizTopo] PCIeAddrInfo: Location=%s, SlotID=%s, ComponentType=0x%02x,' ..
            'SSBDF=%d-%d-0x%02x-0x%02x-0x%02x', info.mds_obj.Location,
            info.mds_obj.SlotID, info.mds_obj.ComponentType, info.mds_obj.Segment,
            info.mds_obj.SocketID, info.mds_obj.Bus, info.mds_obj.Device, info.mds_obj.Function)
    end

    log:notice('[BizTopo] Print business topo ----- End')
end

local function get_ref_pcie_addr_info(connector, pcie_addr_info_list)
    local pcie_addr_info = find(pcie_addr_info_list, function(item)
        if connector.position == item.position and connector.mds_obj.RefPCIeAddrInfo ==
            item.mds_obj.name then
            return true
        end
    end)
    return pcie_addr_info
end

-- 根据类型和槽位号获取对应的PcieAddrInfo对象和其绑定的BussisConnector对象
function c_biz_topo:get_pcie_addr_info(type, slot, position)
    local is_biz_conn_exit = false
    for _, c in ipairs(self.target_pcie_connectors) do
        if position and c.position ~= position then
            goto next
        end
        is_biz_conn_exit = true
        if c.ref_pcie_addr_info and
            def.com_type_to_type[c.ref_pcie_addr_info:get_prop('ComponentType')] == type and
            c.ref_pcie_addr_info:get_prop('SlotID') == slot then
            return true, { addr = c.ref_pcie_addr_info, biz_conn = c }
        end
        ::next::
    end
    if not is_biz_conn_exit then
        return false, def.load_error.NO_MATCHING_BUSINESS_CONNECTOR
    end
    return false, def.load_error.NO_MATCHING_PCIE_ADDR_INFO
end

-- 根据类型和槽位号获取对应的管理连接器，传入position时只遍历指定层级对象
function c_biz_topo:get_mgmt_connector(type, slot, position)
    local ok, ret = self:get_pcie_addr_info(type, slot, position)
    if not ok then
        return ok, ret
    end
    -- 给业务连接器绑定对应的管理连接器，计算下一级加载对象的position
    local mgmt_conn_obj = ret.biz_conn:get_prop('RefMgmtConnector')
    if not mgmt_conn_obj or not mgmt_conn_obj.path then
        return false, 'mgmt_conn_obj is nil or mgmt_conn_obj.path is nil'
    end

    -- 注：对引用对象设置属性值不会生效，这里需要通过mdb重新获取资源树对象
    ok, mgmt_conn_obj = pcall(mdb.get_object, self.bus, mgmt_conn_obj.path, 'bmc.kepler.Connector')

    if not ok or not mgmt_conn_obj then
        return false, string.format('unable to find connector: %s, error: $s', mgmt_conn_obj.path, mgmt_conn_obj)
    end

    ret.biz_conn.ref_mgmt_connector = mgmt_conn_obj

    mgmt_conn_obj = ret.biz_conn:get_prop('RefMgmtConnectorTianChi')

    if not mgmt_conn_obj or not mgmt_conn_obj.path then
        return true, ret.biz_conn.ref_mgmt_connector
    end

    ok, mgmt_conn_obj = pcall(mdb.get_object, self.bus, mgmt_conn_obj.path, 'bmc.kepler.Connector')

    if not ok or not mgmt_conn_obj then
        return true, ret.biz_conn.ref_mgmt_connector
    end

    ret.biz_conn.ref_mgmt_connector_tianchi = mgmt_conn_obj
    return true, ret.biz_conn.ref_mgmt_connector, ret.biz_conn.ref_mgmt_connector_tianchi
end

-- 在objs中查找指定Name的同一级对象（position相同）
local function get_obj_by_name(objs, name, position)
    local obj = cmn.find(objs, function(item)
        if item.mds_obj.Name == name and item.position == position then
            return true
        end
    end)
    return obj
end

local function get_port_offset(ports, id, offset)
    local count = 0
    local port_offset = 0

    for _, port in ipairs(ports) do
        if port.ID == id then
            count = count + 1
        end
    end

    if count > 1 then
        port_offset = offset
    end

    return port_offset
end

function c_biz_topo:pcie_biz_connectors_init()
    local res_connector, start_lane, slot_index, res_port
    for _, c in ipairs(self.target_pcie_connectors) do
        log:info('[BizTopo] Init PCIeBizConnector: Slot=%s, position=%s', c.mds_obj.Slot, c.position)
        -- 查找到所有关联的上行连接器
        c.port_offset = 0
        c.res_connectors = {}
        for _, res in ipairs(c.mds_obj.UpstreamResources) do
            res_connector = get_obj_by_name(self.biz_connector_list, res.Name, c.position)
            if res_connector then
                table.insert(c.res_connectors, res_connector)
                log:info('[BizTopo] Res TargetBizConnector: Name=%s, Slot=%d',
                    res_connector.mds_obj.Name, res_connector.mds_obj.Slot)
            else
                log:error(
                    '[BizTopo] No matching res TargetBizConnector found, UpstreamResource: Name=%s, ID=%d',
                    res.Name, res.ID)
            end
        end

        c.ref_pcie_addr_info = get_ref_pcie_addr_info(c, self.pcie_addr_info_list)
        if not c.mds_obj.UpstreamResources[1] then
            goto next
        end
        -- 获取PCIeConnector对应的上行端口ID和宽度Width
        start_lane = c.mds_obj.UpstreamResources[1].Offset
        res_port = nil
        if not c.res_connectors[1] then
            goto next
        end
        for index, port in ipairs(c.res_connectors[1].mds_obj.Ports) do
            if port.Offset == start_lane then
                slot_index = index
                res_port = port
                res_port.port_offset = get_port_offset(c.res_connectors[1].mds_obj.Ports, port.ID, port.Offset)
            end
        end
        if not res_port then
            log:error('[BizTopo] No matching res TargetBizConnector Ports found')
        else
            c.first_res_port_id = res_port.ID
            c.port_offset = res_port.port_offset
        end
        c.slot_index = slot_index
        _, c.link_width = parse_link_width(c.mds_obj.LinkWidth)
        ::next::
    end
end

-- 源端业务连接器初始化
function c_biz_topo:src_biz_connectors_init()
    local start_lane, res_serdes
    for _, c in ipairs(self.src_connectors) do
        log:info('[BizTopo] Init SrcBizConnector: Slot=%s, position=%s', c.mds_obj.Slot, c.position)
        c.res_serdeses = {}
        start_lane = 0
        -- 查找到所有的上行SerDes资源
        for _, res in ipairs(c.mds_obj.UpstreamResources) do
            res_serdes = get_obj_by_name(self.serdes_list, res.Name, c.position)
            if res_serdes then
                res_serdes.start_lane = start_lane
                table.insert(c.res_serdeses, res_serdes)
                log:info('[BizTopo] Res SerDes: Name=%s, ID=%d, start_lane=%d',
                    res_serdes.mds_obj.Name, res_serdes.mds_obj.ID, res_serdes.start_lane)
                start_lane = start_lane + res_serdes.mds_obj.LinkWidth
            else
                log:error(
                    '[BizTopo] No matching res SerDes found, UpstreamResource: Name=%s, ID=%d',
                    res.Name, res.ID)
            end
        end
    end
end

function c_biz_topo:topo_reader_init()
    -- 只需要读一次线缆信息
    if self.topo_info and next(self.topo_info) then
        log:debug("[BizTopo] topo reader init skip")
        return
    end

    log:notice("[BizTopoMonitor] topo reader init start")
    self.topo_reader = c_topo_reader.new(self.biz_topo_node_list[1], self.biz_connector_list)
    local ok, ret = pcall(function()
        self.topo_info = self.topo_reader:read_topo_info()
    end)
    if not ok then
        log:error('[BizTopo] read topo info fail, ret=%s', ret)
    end
 
    log:notice("[BizTopoMonitor] topo reader init end")
end

function c_biz_topo:topo_info_dump(ctx, path)
    log:notice('[BizTopo] App log dump.')
    local file = file_sec.open_s(path .. '/topo_info', 'w+')
    if not file then
        log:error('[BizTopo] topo_info_dump: open dir fail')
        return
    end

    utils.safe_close_file(file, function()
        local get_mds_obj = function(item)
            return item.mds_obj
        end
        log_helper.parser_collection(self.src_connectors, log_helper.dump_mdb_info, get_mds_obj, file, 0)
        log_helper.parser_collection(self.serdes_list, log_helper.dump_mdb_info, get_mds_obj, file, 0)
        log_helper.parser_collection(self.unit_config_list, log_helper.dump_mdb_info, get_mds_obj, file, 0)
        log_helper.parser_collection(self.target_pcie_connectors, log_helper.dump_mdb_info, get_mds_obj, file, 0)
        log_helper.parser_collection(self.pcie_addr_info_list, log_helper.dump_mdb_info, get_mds_obj, file, 0)
    end)
end

function c_biz_topo:cable_info_dump(path)
    log:notice('[BizTopo] cable log dump.')
    local file = file_sec.open_s(path .. '/cable_info', 'w+')
    if not file then
        log:error('[BizTopo] cable_info_dump: open dir fail')
        return
    end

    utils.safe_close_file(file, function()
        local port_map = {}
        log_helper.parser_collection(self.src_connectors, log_helper.dump_cable_info, nil, file, 1, port_map)
        log_helper.parser_collection(self.unit_config_list, log_helper.dump_cable_info, nil, file, 2, port_map)
    end)
end

function c_biz_topo:biz_topo_init()
    self.target_pcie_connectors = {}
    self.src_connectors = {}
    for _, c in ipairs(self.biz_connector_list) do
        if c:is_pcie_biz_connector() then
            table.insert(self.target_pcie_connectors, c)
        end

        if c:is_src_biz_connector() then
            table.insert(self.src_connectors, c)
        end
    end


    -- 初始化
    self:pcie_biz_connectors_init()
    self:src_biz_connectors_init()
end

function c_biz_topo:ctor(bus)
    self.bus = bus
    -- 保存自发现分发过来的原始对象
    self.pcie_addr_info_list = {}
    self.biz_connector_list = {}
    self.serdes_list = {}
    self.unit_config_list = {}
    self.biz_topo_node_list = {}
    self.topo_info = {}
    self.bdf_config_map = {}
    self.per_bcu_cpu_count = 2

    -- BCU 下行BusinessConnector
    self.src_connectors = {}
    -- IEU 下行BusinessConnector（PCIe卡槽位对应的业务连接器）
    self.target_pcie_connectors = {}

    -- 标记PCIe连接器所在组件
    self.pcie_container = {}
    self.psr_position = nil
    self.psr_add_complete = false
    self.ubc_config_position = nil
    self.ubc_config_add_complete = true
    -- 源端业务连接器所在组件
    self.src_biz_connector_container = {}
    self.bdfconfigs = {}
end

function c_biz_topo:main()
    log:notice('[BizTopo] Main start.')

    self:biz_topo_init()
    self:construct_biz_topo()
    self:print_biz_topo()
end

-- Test
c_biz_topo.parse_link_width = parse_link_width
c_biz_topo.match_src_connector = match_src_connector
c_biz_topo.get_device = get_device
c_biz_topo.find = find
c_biz_topo.parse_port_name = parse_port_name

return c_biz_topo
