-- Copyright (c) 2025 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 cjson = require 'cjson'
local m = {}

local function get_media_type(MediumType)
    if MediumType == 'Copper' then
        return 'Electrical'
    elseif MediumType == 'FiberOptic' then
        return 'Optical'
    else
        return ''
    end
end

local function get_linkstatus(obj, model)
    local os_link_status = obj.OSLinkStatus
    local link_status = obj.LinkStatus
    local name = obj.Name

    if model == 'NPU' then
        return link_status
    end

    if name and #name ~= 0 then
        if os_link_status and #os_link_status ~= 0 then
            return os_link_status
        else
            if link_status == "Disconnected" then
                return "NoLink"
            else
                return null
            end
        end
    else
        if link_status == "Disconnected" then
            return "Down"
        elseif link_status == "Connected" then
            return "Up"
        else
            return null
        end
    end
end

function m.get_port_related_processor_port(port_path, processor_map)
    local related_key_str, transceiver_Lane
    local related_processor_ports = cjson.json_object_new_array()
    local ok, obj = pcall(mdb.get_object, bus, port_path, "bmc.kepler.Systems.NetworkPort.RelatedItems")
    if ok and obj then
        for _, processor_port in pairs(obj.ProcessorPorts) do
            related_key_str = string.format("%s%s", processor_port[1], processor_port[2])
            if processor_map[related_key_str] then
                transceiver_Lane = cjson.json_object_new_object()
                transceiver_Lane.ProcessorSilkText = processor_map[related_key_str][1]
                transceiver_Lane.PortId = processor_map[related_key_str][2]
                transceiver_Lane.IODieId = processor_map[related_key_str][3]
                related_processor_ports[#related_processor_ports + 1] = transceiver_Lane
            end
        end
    end
    return related_processor_ports
end

function m.get_simple_portproperties(system_id, paths, model)
    local ok, npu_objs, npu_port_objs, npu_name, npu_port_id, key_str
    local processor_map = {}
    -- NPU 路径 "/bmc/kepler/Systems/1/Processors/NPU/:ID"
    ok, npu_objs = pcall(mdb.get_sub_objects, bus, "/bmc/kepler/Systems/" .. system_id .. "/Processors/NPU/",
        'bmc.kepler.Systems.Processor')
    if ok then
        for _, npu_obj in pairs(npu_objs) do
            -- NPUPort 路径 "/bmc/kepler/Systems/1/Processors/NPU/:ID/Ports/:ID"
            ok, npu_port_objs = pcall(mdb.get_sub_objects, bus, npu_obj.path, 'bmc.kepler.Systems.Processor.Port', 2)
            if ok then
                for _, npu_port_obj in pairs(npu_port_objs) do
                    key_str = string.format("%s%s", npu_obj.Name, npu_port_obj.Id)
                    processor_map[key_str] = { npu_obj.Name, npu_port_obj.PortId, npu_port_obj.IODieId }
                end
            end
        end
    end

    local port_properties = cjson.json_object_new_array()
    for _, path in pairs(paths) do
        local basic_attribute_info = cjson.json_object_new_object()
        local ok, obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.NetworkPort')
        if ok and obj then
            basic_attribute_info.PhysicalPortNumber = obj.PortID + 1
            basic_attribute_info.LinkStatus = get_linkstatus(obj, model)
            basic_attribute_info.MediaType = get_media_type(obj.MediumType)
            basic_attribute_info.InterfaceType = obj.FunctionType
            basic_attribute_info.Name = obj.Name
            basic_attribute_info.RelatedProcessorPorts = m.get_port_related_processor_port(path, processor_map)
        end
        ok, obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.NetworkPort.FibreChannel')
        if ok and obj then
            basic_attribute_info.FC_ID = obj.FCId
        end
        port_properties[#port_properties + 1] = basic_attribute_info
    end
    return port_properties
end

function m.get_portid(paths, portnum)
    for _, path in pairs(paths) do
        local ok, obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.NetworkPort')
        if ok and obj and obj.PortID == portnum - 1 then
            obj = mdb.get_object(bus, path, 'bmc.kepler.Object.Properties')
            return obj.ObjectName
        end
    end
end

function m.get_multi_rootbdfs(objectid, pcie_paths, businfo)
    local pos = objectid[4]
    local rootbdfs = {}
    local name_rootbdfs = {}
    local name_list = {}
    local name

    for _, v in ipairs(pcie_paths) do
        if pos == mdb.get_object(bus, v, 'bmc.kepler.Object.Properties').ObjectIdentifier[4] then
            local sub_objects = mdb.get_sub_objects(bus, v .. '/PCIeFunctions/',
                'bmc.kepler.Systems.PCIeDevice.PCIeFunction')

            for _, obj in pairs(sub_objects) do
                name = obj.RelatedProcessorId ..
                '_' .. obj.RootBusNumber .. obj.RootDeviceNumber .. obj.RootFunctionNumber
                name_rootbdfs[name] = string.format("%04x:%02x:%02x.%x",
                    obj.SegmentNumber, obj.RootBusNumber, obj.RootDeviceNumber, obj.RootFunctionNumber)
                name_list[#name_list + 1] = name
            end

            table.sort(name_list)

            for _, func_name in ipairs(name_list) do
                if name_rootbdfs[func_name] == "0000:ff:ff.ff" then
                    rootbdfs[#rootbdfs + 1] = cjson.null
                else
                    rootbdfs[#rootbdfs + 1] = name_rootbdfs[func_name]
                end
            end

            if #rootbdfs == 0 then
                rootbdfs[1] = businfo
            end

            return rootbdfs
        end
    end

    rootbdfs[1] = businfo
    return rootbdfs
end

function m.get_pcie_socket_ids(objectid, pcie_paths)
    local pos = objectid[4]
    local socket_ids = {}

    for _, v in ipairs(pcie_paths) do
        if pos == mdb.get_object(bus, v, 'bmc.kepler.Object.Properties').ObjectIdentifier[4] then
            local sub_objects = mdb.get_sub_objects(bus, v .. '/PCIeFunctions/',
                'bmc.kepler.Systems.PCIeDevice.PCIeFunction')
            for _, obj in pairs(sub_objects) do
                -- 以下代码可以保证两个功能为同一个CPU时不会被加载第二次，同时避免因为键为0导致无法遍历
                socket_ids[obj.RelatedProcessorId + 1] = obj.RelatedProcessorId
            end
            return socket_ids
        end
    end

    return {}
end

local TRANSCEIVER_URL_PATTERN <const> = "/UI/Rest/System/NetworkAdapter/%s/Transceivers/%s"
function m.get_transceiver_urls(input, network_adapter)
    local result = cjson.json_object_new_array()
    for _, obj in pairs(input or {}) do
        if obj.Presence == 1 then
            result[#result + 1] = string.format(TRANSCEIVER_URL_PATTERN, network_adapter, obj.Id)
        end
    end
    table.sort(result, function(url_1, url_2)
        if string.match(url_1, '/([^/]+)$') and string.match(url_2, '/([^/]+)$') then
            local a = string.match(url_1, '/([^/]+)$')
            local b = string.match(url_2, '/([^/]+)$')
            return tonumber(a) < tonumber(b)
        end
        return url_1 < url_2
    end)
    return result
end

function m.get_transceivers_path(paths, id)
    for _, path in pairs(paths) do
        local ok, obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.OpticalModule')
        if ok and obj and obj.Id == tonumber(id) and obj.Presence == 1 then
            return path
        end
    end
    return ''
end

function m.get_lane_mappings(transceiver_lanes)
    local lane_mappings = cjson.json_object_new_array()
    for i, lane in pairs(transceiver_lanes) do
        local transceiver_Lane = cjson.json_object_new_object()
        transceiver_Lane.LaneId = i
        local related_transceiver_Lane = cjson.json_object_new_object()
        related_transceiver_Lane.TransceiverSilkText = lane[1]
        -- 默认值255 代表 不支持，传参null
        related_transceiver_Lane.TransceiverLaneId = lane[2] ~= 255 and lane[2] or nil
        transceiver_Lane.RelatedTransceiverLane = related_transceiver_Lane
        lane_mappings[#lane_mappings + 1] = transceiver_Lane
    end
    return lane_mappings
end

function m.get_related_processor_ports(system_id, processor_ports)
    local ok, npu_objs, npu_port_objs, npu_name, npu_port_id, key_str, related_key_str, transceiver_Lane
    local processor_map = {}
    -- NPU 路径 "/bmc/kepler/Systems/1/Processors/NPU/:ID"
    ok, npu_objs = pcall(mdb.get_sub_objects, bus, "/bmc/kepler/Systems/" .. system_id .. "/Processors/NPU/",
        'bmc.kepler.Systems.Processor')
    if ok then
        for _, npu_obj in pairs(npu_objs) do
            -- NPUPort 路径 "/bmc/kepler/Systems/1/Processors/NPU/:ID/Ports/:ID"
            ok, npu_port_objs = pcall(mdb.get_sub_objects, bus, npu_obj.path, 'bmc.kepler.Systems.Processor.Port', 2)
            if ok then
                for _, npu_port_obj in pairs(npu_port_objs) do
                    key_str = string.format("%s%s", npu_obj.Name, npu_port_obj.Id)
                    processor_map[key_str] = { npu_obj.Name, npu_port_obj.PortId, npu_port_obj.IODieId }
                end
            end
        end
    end

    local related_processor_ports = cjson.json_object_new_array()
    for _, processor_port in pairs(processor_ports) do
        related_key_str = string.format("%s%s", processor_port[1], processor_port[2])
        if processor_map[related_key_str] then
            transceiver_Lane = cjson.json_object_new_object()
            transceiver_Lane.ProcessorSilkText = processor_map[related_key_str][1]
            transceiver_Lane.PortId = processor_map[related_key_str][2]
            transceiver_Lane.IODieId = processor_map[related_key_str][3]
            related_processor_ports[#related_processor_ports + 1] = transceiver_Lane
        end
    end
    return related_processor_ports
end

function m.get_port_mappings(port_paths, trans_paths)
    local ok, obj
    local port_mappings = cjson.json_object_new_array()
    local trans_map = {}
    for _, path in pairs(trans_paths) do
        ok, obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.OpticalModule')
        trans_map[obj.SilkText] = obj.Id
    end
    for _, path in pairs(port_paths) do
        local port_map = cjson.json_object_new_object()
        ok, obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.NetworkPort')
        if ok and obj and obj.PortID then
            port_map.PortId = obj.PortID
        else
            goto continue
        end
        ok, obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.NetworkPort.RelatedItems')
        if ok and obj and obj.TransceiverLanes then
            local lane_map = cjson.json_object_new_array()
            for i, lane in pairs(obj.TransceiverLanes) do
                local transceiver_lane = cjson.json_object_new_object()
                transceiver_lane.PortLaneId = i
                local transceiver = cjson.json_object_new_object()
                transceiver.SilkText = lane[1]
                local id = tonumber(trans_map[lane[1]])
                transceiver.Id = id
                -- 默认值255 代表 不支持，传参null
                transceiver.LaneId = lane[2] ~= 255 and lane[2] or nil
                transceiver_lane.Transceiver = transceiver
                lane_map[#lane_map + 1] = transceiver_lane
            end
            port_map.LaneMap = lane_map
        else
            goto continue
        end
        port_mappings[#port_mappings + 1] = port_map
        ::continue::
    end
    return port_mappings
end

return m