-- 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.

-- Description: 子件管理
local log = require 'mc.logging'
local cmn = require 'common'
local mdb = require 'mc.mdb'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local context = require 'mc.context'
local fw_manager = require 'fw_manager'

local FWINVENTORY_PATH = '/bmc/kepler/UpdateService/FirmwareInventory'
local FWINVENTORY_INTERFACE = 'bmc.kepler.UpdateService.FirmwareInventory'
local FWINFO_INTERFACE = 'bmc.kepler.UpdateService.FirmwareInfo'

--- @class 子件对象
local sub_comp_object = {}
sub_comp_object.__index = sub_comp_object

function sub_comp_object.new(index, data)
    return setmetatable({
        Index = index,
        Type = data.type,
        No = data.no,
        Vendor = data.vendor,
        SKU = data.sku,
        MajorVersion = data.major_version,
        MinorVersion = data.minor_version,
        Revision = data.revision,
        Delay = data.delay
    }, sub_comp_object)
end

function sub_comp_object:set_ref_mcu(obj)
    self.RefMcu = obj
end

function sub_comp_object:set_major_version(major_version)
    self.MajorVersion = major_version
end

function sub_comp_object:set_minor_version(minor_version)
    self.MinorVersion = minor_version
end

function sub_comp_object:set_revision(revision)
    self.Revision = revision
end

function sub_comp_object:ge_component_major_version()
    local major_version
    if self.MajorVersion ~= "" and self.MinorVersion ~= "" then
        major_version = tonumber(self.MajorVersion)
    elseif self.RefMcu ~= nil then
        local var_major_version , _ ,_ = self.RefMcu.interface:get_version(self)
        major_version = var_major_version
    end

    return major_version
end

function sub_comp_object:update_detail_info()
    if not self.RefMcu then
        log:error('can not get ref mcu')
        return
    end
    local detail = self.RefMcu:get_firmeware_info(self.Index)
    if not detail then
        log:error('get sub component detail failed, index is %s', self.Index)
        return
    end
    self.Vendor = detail['vendor']
    self.SKU = detail['sku']
    self.Delay = detail['delay']
    log:notice('update firmware info, vendor is %s, sku is %s, index:%s, delay is %s',
        detail['vendor'], detail['sku'], self.Index, self.Delay)
end

function sub_comp_object:get_component_version()
    local version = ''
    local major_version, minor_version, revision
    if self.MajorVersion ~= "" and self.MinorVersion ~= "" then
        major_version = tonumber(self.MajorVersion)
        minor_version = tonumber(self.MinorVersion)
        revision = tonumber(self.Revision)
    elseif self.RefMcu ~= nil then
        major_version, minor_version, revision = self.RefMcu.interface:get_version(self)
    end

    if major_version ~= nil and major_version ~= nil then
        version = string.format('%u.%02u', major_version, minor_version)
    end

    if revision ~= nil then
        version = string.format('%s.%02u', version, revision)
    end

    return version
end

local function get_version_list(mcu_object)
    local version_list = {}
    for _, item in ipairs(mcu_object.mcu.SubCompList) do
        if item.Type == MCU_ENUMS.SUB_COMPONENT_TYPE.T_VRD then
            local major_version = item.MajorVersion == 255 and '-' or tostring(item.MajorVersion)
            table.insert(version_list, major_version)
        end
    end
    return version_list
end

local function register_process(bus, id, name, version, software_id)
    cmn.skynet.fork(function()
        local ok, obj = pcall(mdb.get_object, bus, FWINVENTORY_PATH, FWINVENTORY_INTERFACE)
        local times = 120 -- 重试120s, 防止资源树未加载
        while not ok and times > 0 do
            ok, obj = pcall(mdb.get_object, bus, FWINVENTORY_PATH, FWINVENTORY_INTERFACE)
            cmn.skynet.sleep(100)
            times = times - 1
        end
        if not ok then
            log:error('[McuUpgrade] Get FirmwareInventory object failed. err: %s', obj)
            return
        end
        log:info('[McuUpgrade] Register firmware info.')

        local param = {
            Id = id,
            Name = name,
            Version = version,
            BuildNum = '',
            ReleaseDate = '',
            LowestSupportedVersion = '',
            SoftwareId = software_id or '',
            Manufacturer = 'Huawei',
            Location = '',
            State = 'Enabled',
            Severity = 'Informational'
        }
        obj:Add(context.new(), param, true, 1, 90)
    end)
end

function sub_comp_object:register_component_firmware_info(bus)
    local id = string.format('%s_%u', MCU_ENUMS.SUB_COMPONENT_TYPE_TABLE[self.Type], self.No)
    local name = string.format('%s_%u', MCU_ENUMS.SUB_COMPONENT_TYPE_TABLE[self.Type], self.No)
    local version = self:get_component_version()
    register_process(bus, id, name, version, self.RefMcu and self.RefMcu.SoftwareId)
end

function sub_comp_object.register_vrd_firmware_info(mcu_object, bus)
    local component_version_list = get_version_list(mcu_object)
    if #component_version_list == 0 then
        return
    end
    local id = string.format('%s_%s_%s', 'VRD', mcu_object.mcu.BoardType, mcu_object.position)
    local name = fw_manager:get_device_name_by_position(mcu_object.mcu.BoardType, mcu_object.position) .. ' VRD'
    local version = table.concat(component_version_list, '.')
    -- 取MCU的SoftwareId的board_name部分组成VRD的SoftwareId
    local software_id = mcu_object.mcu.SoftwareId and 'VRD-' .. string.sub(mcu_object.mcu.SoftwareId, 5) or ''
    register_process(bus, id, name, version, software_id)
end

local function update_process(bus, firmware_path, firmware_version)
    cmn.skynet.fork(function()
        local ok, obj = pcall(mdb.get_cached_object, bus, firmware_path, FWINFO_INTERFACE)
        if not ok then
            log:error('[McuUpgrade] Get McuFirmware object failed. err: %s', obj)
            return
        end
        log:info('[McuUpgrade] update firmware version.')
        obj.Version = firmware_version
    end)
end

function sub_comp_object:update_firmware_version(bus)
    local path = FWINVENTORY_PATH .. "/" ..
        string.format('%s_%u', MCU_ENUMS.SUB_COMPONENT_TYPE_TABLE[self.Type], self.No)
    local version = self:get_component_version()
    update_process(bus, path, version)
end

function sub_comp_object.update_vrd_firmware_version(bus, mcu_object)
    local path = FWINVENTORY_PATH .. "/" ..
        string.format('%s_%s_%s', 'VRD', mcu_object.mcu.BoardType, mcu_object.position)
    local component_version_list = get_version_list(mcu_object)
    if #component_version_list == 0 then
        return
    end
    mcu_object:update_sub_component_info_list(component_version_list[1])
    mcu_object.vrd_info_changed:emit(mcu_object.sub_component_info_list)
    local version = table.concat(component_version_list, '.')
    update_process(bus, path, version)
end

function sub_comp_object.get_mdb_vrd_firmware_version(bus, mcu_object)
    local id = string.format('%s_%s_%s', 'VRD', mcu_object.mcu.BoardType, mcu_object.position)
    local path = FWINVENTORY_PATH .. "/" .. id
    local ok, obj = pcall(mdb.get_cached_object, bus, path, FWINFO_INTERFACE)
    if not ok then
        log:error('[McuUpgrade] Get McuFirmware object failed. err: %s', obj)
        return
    end
    local version = obj.Version
    return version
end

return sub_comp_object