-- 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 m = {}
local log = require 'mc.logging'
local context = require 'mc.context'
local cjson = require 'cjson'

local TPCM_MICROCOMPONENT_PATH <const> = '/bmc/kepler/tpcm/MicroComponent'
local MDB_SERVICE_PATH <const> = '/bmc/kepler/MdbService'
local MDB_INTF <const> = 'bmc.kepler.Mdb'
local COMPONENT_MDB_INTF <const> = 'bmc.kepler.Systems.TPCM.Component'
local MEASURED_MDB_INTF <const> = 'bmc.kepler.Managers.TPCM.Measured'
local FRUCTRL_MDB_INTF <const> = 'bmc.kepler.Systems.FruCtrl'
local VIRTUALMACHINE_PATH <const> = '/bmc/kepler/Systems/1/VirtualMachines'
local VIRTUALMACHINES_TPCM_MDB_INTF <const> = 'bmc.kepler.Systems.VirtualMachines.TPCM'

function m.get_version_str(version)
    local t = {}
    for i = 0, 3 do
        t[i + 1] = (version & ((0xff) << (i*8))) >> (i*8)
    end
    return string.format("%d.%d.%d.%d", t[1], t[2], t[3], t[4])
end

local TCM_ALL_MEMBER_MAP = {
    [0x1b4e] = "国民技术",
    [0x0] = "未知厂商"
}

function m.get_tcm_manufacturer(manufacturer_id)
    if TCM_ALL_MEMBER_MAP[manufacturer_id] == nil then
        return TCM_ALL_MEMBER_MAP[0];
    end

    return TCM_ALL_MEMBER_MAP[manufacturer_id];
end

function m.check_power_state(system_id)
    local path = '/bmc/kepler/Systems/'.. tostring(system_id) ..'/FruCtrl'
    local ok, frulist = pcall(mdb.get_sub_objects, bus, path, FRUCTRL_MDB_INTF)
    if not ok then
        log:error("FruCtrl is not configured")
        return false
    end
    for _, fruobj in pairs(frulist) do
        if fruobj.PowerState ~= 'ON' then
            error(base_messages.ActionNotSupported('ResetComponentStandardDigest'))
        end
    end
    return true
end

local function get_component_detail(path)
    local detail = cjson.json_object_new_object()
    local component_obj = mdb.get_object(bus, path, COMPONENT_MDB_INTF)
    local measured_obj = mdb.get_object(bus, path, MEASURED_MDB_INTF)

    detail["Name"] = measured_obj.Name
    detail["ActualDigest"] = measured_obj.ActualDigest
    detail["ActualIdentifier"] = string.format('%s:%s', component_obj.ActualModel,
        component_obj.ActualUniqueIdentifier)
    detail["StandardDigest"] = measured_obj.StandardDigest
    detail["StandardIdentifier"] = string.format('%s:%s', component_obj.StandardModel,
        component_obj.StandardUniqueIdentifier)
    detail["Status"] = component_obj.Status
    local compare_result = measured_obj.ComparedResult
    return detail, compare_result
end

local function get_component_info(component_name, component_infos)
    for _, comp in ipairs(component_infos) do
        if comp.Name == component_name then
            return comp
        end
    end
    return nil
end

function m.get_components_info(component_paths)
    local component_infos = cjson.json_object_new_array()
    for _, path in ipairs(component_paths) do
        local detail, compare_result = get_component_detail(path)
        local component_name = string.match(path, "/TPCM/Measured/(%w+)/")
        local system_id = tonumber(string.match(path, '/Systems/(%d+)/'))
        local component_info = get_component_info(component_name, component_infos)
        if not component_info then
            component_info = cjson.json_object_new_object()
            component_info.Name = component_name
            component_info.SystemId = system_id
            component_info.ComparedResult = 0
            component_info.Details = cjson.json_object_new_array()
            component_infos[#component_infos + 1] = component_info
        end
        if compare_result ~= 0 then
            component_info.ComparedResult = 1
        end
        component_info.Details[#component_info.Details + 1] = detail
    end
    return component_infos
end


function m.get_tpcm_supported(tcm_precense)
    -- 若TCM不在位，则返回false
    if not tcm_precense then
        return false
    end

    local ok, obj = pcall(mdb.get_object, bus, MDB_SERVICE_PATH, MDB_INTF)
    if not ok then
        log:info('Invalid Mdb Path, err: %s', obj)
        return false
    end

    -- TCM在位场景，则需要检查TPCM是否支持
    local ret, valid = pcall(function()
        return obj:IsValidPath(context.get_context(), TPCM_MICROCOMPONENT_PATH)
    end)
    if not ret then
        log:info('Invalid Mdb Method, err: %s', valid)
        return false
    end

    return valid.Result
end

function m.get_virtual_machines_info()
    local count = 0
    local virtual_machines_infos = {}
    local ok, vms_tpcm_obj = pcall(mdb.get_object, bus, VIRTUALMACHINE_PATH, VIRTUALMACHINES_TPCM_MDB_INTF)
    if not ok then
        log:error('[TPCM] Get % mdb object failed, err: %s', VIRTUALMACHINE_PATH, vms_tpcm_obj)
        return {count, virtual_machines_infos}
    end

    local system_id = 1
    local ret, rsp = pcall(function()
        return vms_tpcm_obj:GetVMMeasurements(context.get_context(), system_id)
    end)
    if not ret then
        log:error('[TPCM] Get virtual machines measurement failed, err: %s', rsp)
        return {count, virtual_machines_infos}
    end

    local decode_ok, vm_info
    for _, vm_json_str in ipairs(rsp.VMMeasurements) do
        decode_ok, vm_info = pcall(cjson.decode, vm_json_str)
        if not decode_ok then
            log:error('[TPCM] Decode virtual machine measurement json failed, err: %s', vm_info)
        else
            count = count + 1
            virtual_machines_infos[count] = vm_info
        end
    end

    return {count, virtual_machines_infos}
end

return m
