-- 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 class = require 'mc.class'
local c_base = require 'ub_cable.class.base'
local log = require 'mc.logging'
local def = require 'ub_cable.infrastructure.def'
local client = require 'pcie_device.client'

local c_node_port_addr = class(c_base)

function c_node_port_addr:init()
    local event_key
    for event_code, _ in pairs(def.UB_CABLE_EVENTCODE) do
        -- 获取当前告警的唯一标识
        event_key = string.format("%s-%s", self.component_name, event_code)
        self.event_status[event_key] = nil
    end
end

function c_node_port_addr:ctor(mds_obj, position, bus)
    self.mds_obj = mds_obj
    self.position = position
    self.bus = bus
    self.component_name_prefix = string.format("%s_%s_%s_%s_%s_%s", "NodePortAddr", mds_obj.CPUId, mds_obj.DieId,
        mds_obj.MacroId, mds_obj.InterfaceDeviceSlotId, mds_obj.InterfaceDevicePortId)
    self.component_name = self.component_name_prefix
    self.component_object_name = '' --资源树对象名
    self.src_info = ''    --本端信息
    self.src_guid = ''

    self.target_node_id = 0
    self.target_slot_id = 0
    self.target_port_id = 0
    self.target_cpu_id = 0
    self.target_die_id = 0
    self.target_macro_id = 0
    self.target_info = '' --对端信息

    self.peer_node_id = 0
    self.peer_slot_id = 0
    self.peer_port_id = 0
    self.peer_cpu_id = 0
    self.peer_die_id = 0
    self.peer_macro_id = 0
    self.peer_guid = ''
    self.peer_info = ''   --实际信息

    self.status = def.TOPO_STATUS.INITIAL_STATE
    self.event_status = {}
end

-- 读取本端信息
function c_node_port_addr:update_src_info(local_info)
    if not local_info or not self:node_id_ready() then
        return
    end
    local src_info_temp = string.format("%s_%s_%s_%s_%s_%s", self.mds_obj.NodeId, self.mds_obj.InterfaceDeviceSlotId, self.mds_obj.InterfaceDevicePortId,
        self.mds_obj.CPUId, self.mds_obj.DieId, self.mds_obj.MacroId)
    if src_info_temp ~= self.src_info or self.src_guid ~= local_info.guid then
        self.src_info = src_info_temp
        self.src_guid = local_info.guid
        log:notice('update_src_info, src_info=%s, src_guid=%s', self.src_info, self.src_guid)
        self:update_component_name()
    end
end

function c_node_port_addr:update_target_info(cable_cfg_list, node_id_guid_map)
    -- 读取线缆配置，目前只有一种线缆配置，匹配到就用
    local src_key = string.format("%s_%s_%s_%s", self.mds_obj.NodeId, self.mds_obj.DieId, self.mds_obj.MacroId,
        self.mds_obj.InterfaceDevicePortId)
    for _, cfg in pairs(cable_cfg_list) do
        if cfg.src_target_info_map[src_key] then

            if not node_id_guid_map[cfg.src_target_info_map[src_key].TargetNodeId] then
                -- 没有同步到的节点信息说明对应节点没起来，兼容4节点8节点，没插的节点白名单不检测。
                self.target_info = ''
                return
            end

            self.target_node_id = cfg.src_target_info_map[src_key].TargetNodeId
            self.target_slot_id = self.mds_obj.InterfaceDeviceSlotId
            self.target_port_id = cfg.src_target_info_map[src_key].TargetPortId
            self.target_cpu_id = self.mds_obj.CPUId
            self.target_die_id = cfg.src_target_info_map[src_key].TargetDieId
            self.target_macro_id = cfg.src_target_info_map[src_key].TargetMacroId

            -- 设置对端线缆配置
            local tmp = string.format("%s_%s_%s_%s_%s_%s", self.target_node_id, self.target_slot_id,
                self.target_port_id, self.target_cpu_id, self.target_die_id, self.target_macro_id)
            if tmp ~= self.target_info then
                self.target_info = tmp
                log:notice('update_target_info, target_info=%s', self.target_info)
            end
            break
        end
    end
end

function c_node_port_addr:clear_peer_info()
    self.peer_node_id = 0
    self.peer_cpu_id = 0
    self.peer_die_id = 0
    self.peer_macro_id = 0
    self.peer_slot_id = 0
    self.peer_port_id = 0
    self.peer_info = ''
    self.peer_guid = ''
end

function c_node_port_addr:update_peer_info(port_info)
    if not port_info then
        return
    end
    -- 目前查询的是单个port的信息
    -- guid能对应到每个控制器的信息，每个控制器内部根据macro id区分。
    self.peer_node_id = port_info.node_id
    self.peer_cpu_id = port_info.cpu_id
    self.peer_die_id = port_info.die_id
    self.peer_macro_id = port_info.macro_id
    self.peer_slot_id = port_info.slot_id
    self.peer_port_id = port_info.port_id
    self.peer_guid = port_info.guid

    local tmp = string.format("%s_%s_%s_%s_%s_%s", self.peer_node_id, self.peer_slot_id,
        self.peer_port_id, self.peer_cpu_id, self.peer_die_id, self.peer_macro_id)
    if tmp ~= self.peer_info then
        self.peer_info = tmp
        log:notice('update_peer_info, peer_info=%s, peer_guid=%s', self.peer_info, self.peer_guid)
    end
end

-- 判断线缆告警状态
function c_node_port_addr:update_cable_status()
    -- 判断当前告警状态
    local event_key
    -- 判断漏插
    event_key = string.format("%s-%s", self.component_name_prefix, def.UB_CABLE_NOT_PRESENT_CODE)
    if #self.target_info > 0 and #self.peer_info == 0 then
        self.status = def.TOPO_STATUS.INCOMPLETE_CONNECTION
        self.event_status[event_key] = true
    else
        self.event_status[event_key] = false
    end

    -- 判断错插
    event_key = string.format("%s-%s", self.component_name_prefix, def.UB_CABLE_INCORRECT_CODE)
    if #self.target_info > 0 and #self.peer_info > 0 and self.target_info ~= self.peer_info then
        self.status = def.TOPO_STATUS.INCORRECT_CONNECTION
        self.event_status[event_key] = true
    else
        self.event_status[event_key] = false
    end

    -- 没有告警
    self.status = def.TOPO_STATUS.INITIAL_STATE
end

-- 转换槽位和端口号，北向显示的每个卡的端口号是1/2/3/4
function c_node_port_addr:get_port_id_arg(port_id)
    return port_id % 4 + 1
end

function c_node_port_addr:get_msg_args(event_code)
    local switch = {
        [def.UB_CABLE_INCORRECT_CODE] = function()
            return {
                'Node' .. tostring(self.mds_obj.NodeId),
                'Card' .. tostring(self.mds_obj.InterfaceDeviceSlotId),
                'Port' .. tostring(self:get_port_id_arg(self.mds_obj.InterfaceDevicePortId)),
                'Node' .. tostring(self.target_node_id),
                'Card' .. tostring(self.target_slot_id),
                'Port' .. tostring(self:get_port_id_arg(self.target_port_id))
            }
        end,
        [def.UB_CABLE_NOT_PRESENT_CODE] = function()
            return {
                'Node' .. tostring(self.mds_obj.NodeId),
                'Card' .. tostring(self.mds_obj.InterfaceDeviceSlotId),
                'Port' .. tostring(self:get_port_id_arg(self.mds_obj.InterfaceDevicePortId))
            }
        end
    }

    if switch[event_code] then
        return switch[event_code]()
    end
end

function c_node_port_addr:node_id_ready()
    if self.mds_obj.NodeId > 0 and self.mds_obj.NodeId ~= 255 then
        return true
    end
    return false
end

function c_node_port_addr:node_id_guid_ready()
    if self.mds_obj.NodeId > 0 and self.mds_obj.NodeId ~= 255 and type(self.src_guid) == 'string' and #self.src_guid > 0 then
        return true
    end
    return false
end

-- 获取当前对应的Component，设置Name属性
-- NodePortAddr_CPUId_DieId_MacroId_InterfaceDeviceSlotId_InterfaceDevicePortId .. _ .. NodeId_GUID
-- 前半段是静态配置，后半段是代码设置
function c_node_port_addr:update_component_name()
    local ok, ret = pcall(function()
        if not self:node_id_guid_ready() then
            return
        end
        local tmp = string.format("%s_%s_%s_%s_%s_%s_%s_%s", "NodePortAddr", self.mds_obj.CPUId,
            self.mds_obj.DieId, self.mds_obj.MacroId, self.mds_obj.InterfaceDeviceSlotId,
            self.mds_obj.InterfaceDevicePortId, self.mds_obj.NodeId, self.src_guid)
        if self.component_name ~= tmp then
            self.component_name = tmp
            log:notice('update_component_name %s', self.component_name)
        end

        client:ForeachComponentObjects(function(component_obj)
            if self:node_id_guid_ready() and component_obj.Name ~= self.component_name and
                string.match(component_obj.Name, "^" .. self.component_name_prefix) then  -- "^" 表示匹配开头
                log:notice('%s Name changed from %s to %s', component_obj.extra_params.ObjectName, component_obj.Name, self.component_name)
                component_obj.Name = self.component_name
                self.component_object_name = component_obj.extra_params.ObjectName
            end
        end)
    end)
    if not ok then
        log:error('update_component_name error=%s', ret)
    end
end

return c_node_port_addr