-- 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 cjson = require 'cjson'
local m = {}

-- 处理器资源树接口
local CPU_MDB_INTF <const> = "bmc.kepler.Systems.Processor"
local PROCESSOR_CPU_MDB_INTF <const> = "bmc.kepler.Systems.Processor.CPU"
local PROCESSOR_CPU_METRICS_MDB_INTF <const> = "bmc.kepler.Systems.Processor.ProcessorMetrics"
local MEM_PATHS <const> = "/bmc/kepler/Systems/%s/Memory"
local MEM_MDB_INTF <const> = "bmc.kepler.Systems.Memory"
local mdb_service = require 'mc.mdb.mdb_service'
local PCIECARD_INTERFACE<const> = 'bmc.kepler.Systems.PCIeDevices.PCIeCard'
local PROPERTY_INTERFACE<const> = 'bmc.kepler.Object.Properties'
-- GetPath函数返回的无效路径
local INVALID_PATH<const> = ''

-- multihost获取在位处理器的个数
function m.get_presence_count(input)
    local count = 0
    for _, path in ipairs(input) do
        for key, realpath in ipairs(path) do
            local processor_obj = mdb.get_object(bus, realpath, CPU_MDB_INTF)
            if processor_obj.Presence == 1 then
                count = count + 1
            end
        end
    end
    return count
end

-- multihost获取处理器的个数
function m.get_cpu_count(input)
    local count = 0
    for _, path in ipairs(input) do
        for key, realpath in ipairs(path) do
            local processor_obj = mdb.get_object(bus, realpath, CPU_MDB_INTF)
            count = count + 1
        end
    end
    return count
end

local function get_hbm_path(mem_paths, cpuid)
    local res_path = ''
    for _, path in ipairs(mem_paths) do
        local ok, obj = pcall(mdb.get_object, bus, path, MEM_MDB_INTF)
        if ok and obj.CpuId == cpuid and string.find(obj.MemoryDeviceType, 'DRAM') then
            return path
        end
    end
    return res_path
end

local function format_identification(process_id, process_id_str)
    if process_id_str then
        return process_id_str
    end
    if not process_id then
        return nil
    end
    local str = string.format('%016X', process_id)
    local identification = ''
    for i = #str, 1, -2 do
        identification = identification .. string.sub(str, i - 1, i)
        if i ~= 2 then
            identification = identification .. '-'
        end
    end
    return identification
end

-- multihost获取所有cpu
function m.get_processor(input)
    local res = {}
    local data
    for _, path in ipairs(input) do
        for key, realpath in ipairs(path) do
            data = {}
            local obj = mdb.get_object(bus, realpath, PROCESSOR_CPU_MDB_INTF)
            data.FrequencyMHz = obj.CurrentSpeedMHz
            data.DeviceLocator = obj.DeviceLocator
            data.L1CacheKiB = obj.L1Cache
            data.L2CacheKiB = obj.L2Cache
            data.L3CacheKiB = obj.L3Cache
            data.Identification = format_identification(obj.ProcessorID, obj.ProcessorIDString)
            data.TotalCores = obj.TotalCores
            data.TotalThreads = obj.TotalThreads
            data.TotalEnabledThreads = obj.TotalEnabledThreads
            data.TotalEnabledCores = obj.TotalEnabledCores
            data.OtherParameters = obj.Characteristics
            data.EnabledSetting = true

            local processor_obj = mdb.get_object(bus, realpath, CPU_MDB_INTF)
            data.Manufacturer = processor_obj.Manufacturer
            data.Model = processor_obj.Model
            data.PartNumber = processor_obj.PartNumber
            data.SerialNumber = processor_obj.SN
            if processor_obj.Presence == 1 then
                data.State = "Present"
            else
                data.State = "Absent"
            end
            data.Position = processor_obj.Position
            local sys_id = processor_obj.SystemId
            local _, mems = pcall(mdb_service.get_sub_paths, bus, string.format(MEM_PATHS, sys_id), 1, {})
            if mems and mems.SubPaths then
                local hbm_path = get_hbm_path(mems.SubPaths, obj.PhysicalId)
                local _, hbm_obj = pcall(mdb.get_object, bus, hbm_path, MEM_MDB_INTF)
                data.MemoryOnChip = {}
                data.MemoryOnChip.CapacityMiB = hbm_obj and hbm_obj.CapacityMiB or cjson.null
                data.MemoryOnChip.DataWidthBits = hbm_obj and hbm_obj.DataWidthBits or cjson.null
                data.MemoryOnChip.OperatingSpeedsMHz = hbm_obj and hbm_obj.OperatingSpeedMhz or cjson.null
            end
            table.insert(res, data)
        end
    end
    table.sort(res, function(a , b)
        return (tonumber(string.match(a.DeviceLocator, 'CPU(%d+)')) or 0) <
            (tonumber(string.match(b.DeviceLocator, 'CPU(%d+)')) or 0)
    end)
    return res
end

function m.get_processor_usagepercent(input)
    local res = {}
    local data
    local realpath
    local obj
    local systemId
    for _, path in ipairs(input) do
        realpath = path[1]
        systemId = tonumber(string.match(realpath, '/bmc/kepler/Systems/(%d+)'))
        data = {}
        obj = mdb.get_object(bus, realpath, PROCESSOR_CPU_METRICS_MDB_INTF)
        if obj.BandwidthPercent >= 0 and obj.BandwidthPercent <= 100 then
            data.BandwidthPercent = obj.BandwidthPercent
        else
            data.BandwidthPercent = cjson.null
        end
        data.SystemId = systemId
        table.insert(res, data)
    end
    return res
end

function m.get_processor_thresholdpercent(input)
    local res = {}
    local data
    local realpath
    local obj
    local systemId
    for _, path in ipairs(input) do
        data = {}
        realpath = path[1]
        systemId = tonumber(string.match(realpath, '/bmc/kepler/Systems/(%d+)'))
        obj = mdb.get_object(bus, realpath, PROCESSOR_CPU_METRICS_MDB_INTF)
        data.BandwidthThresholdPercent = obj.BandwidthThresholdPercent
        data.SystemId = systemId
        table.insert(res, data)
    end
    return res
end

-- 根据npu对象查找同级position的pcie卡对象
local function get_pcie_card(npu_path)
    local npu_obj = mdb.get_object(bus, npu_path, PROPERTY_INTERFACE)
    local npu_position = npu_obj.ObjectIdentifier[4]
    local pcie_card_path = "/bmc/kepler/Systems/1/PCIeDevices/PCIeCards"
    local pcie_cards = mdb.get_sub_objects(bus, pcie_card_path, PROPERTY_INTERFACE)
    local target_pcie_card
    local ok
    for card_path, card in pairs(pcie_cards) do
        if card.ObjectIdentifier[4] == npu_position then
            ok, target_pcie_card = pcall(mdb.get_object, bus, card_path, PCIECARD_INTERFACE)
            if ok then
                return target_pcie_card
            end
        end
    end
end

function m.filter_npu_list(npu_path_list)
    local res = {}
    for _, npu_path in pairs(npu_path_list) do
        if not get_pcie_card(npu_path) then
            table.insert(res, npu_path)
        end
    end
    return res
end

return m
