-- 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 mdb = require 'mc.mdb'
local log = require 'mc.logging'
local context = require 'mc.context'
local cmn = require 'common'
local fw_manager = require 'fw_manager'
local smc_interface = require 'mcu.upgrade.smc_interface'
local smbus_interface = require 'mcu.upgrade.smbus_interface'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local upgrade_service_handle = require 'mcu.upgrade.upgrade_service.upgrade_service_handle'

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

--- @class Mcu对象
local mcu_object = class()

function mcu_object:ctor(object, position, vrd_info_changed)
    object.Id = "MCU_" .. object.BoardType .. "_" .. position
    object.MajorVersion = ""
    object.MinorVersion = ""
    object.Revision = ""
    object.SubCompList = {}

    self.system_id = 1
    self.device_name = fw_manager:get_device_name_by_position(object.BoardType, position)
    self.name = self.device_name .. ' MCU'
    local ok, sys_id = pcall(object.get_system_id, object)
    if not ok then
        log:error("get_system_id failed, err: %s", sys_id)
    else
        self.system_id = sys_id
    end
    self.mcu = object
    self.position = position
    if object.Protocol == "" or object.Protocol == MCU_ENUMS.SMBUS_CHANNEL then
        self.interface =smbus_interface.new(object.RefChip, MCU_ENUMS.SMBUS_CHANNEL, object.Address)
    else
        self.interface =smc_interface.new(object.RefChip, object.Protocol, object.Address)
    end
    self.vrd_load = false
    self.vrd_info_changed = vrd_info_changed
end

function mcu_object:init()
end

function mcu_object:get_board_type()
    return self.mcu.BoardType
end

function mcu_object:get_vrd_info()
    return self.sub_component_info_list
end

function mcu_object:get_mcu_version()
    local version =''
    local major_version, minor_version, revision

    if self.mcu.MajorVersion ~= "" and self.mcu.MinorVersion ~= "" then
        major_version = tonumber(self.mcu.MajorVersion)
        minor_version = tonumber(self.mcu.MinorVersion)
        revision = tonumber(self.mcu.Revision)
    else
        local subcomp
        if next(self.mcu.SubCompList) then
            subcomp = self.mcu.SubCompList[1]
        end
        major_version, minor_version, revision = self.interface:get_version(subcomp)
    end

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

    return version

end

function mcu_object:get_id()
    return self.mcu.Id
end

function mcu_object:get_software_id()
    return self.mcu.SoftwareId
end

function mcu_object:get_position()
    return self.position
end

function mcu_object:get_uid()
    return self.mcu.UID
end

function mcu_object:get_system_id()
    return self.system_id
end

function mcu_object:get_refchip()
    return self.mcu.RefChip
end

function mcu_object:get_protocol()
    return self.mcu.Protocol
end

function mcu_object:set_mcu_major_version(major_version)
    self.mcu.MajorVersion = tostring(major_version)
end

function mcu_object:set_mcu_minor_version(minor_version)
    self.mcu.MinorVersion = tostring(minor_version)
end

function mcu_object:set_mcu_revision(revision)
    self.mcu.Revision = tostring(revision)
end

function mcu_object:insert_subcomp_detail(subcomp)
    table.insert(self.mcu.SubCompList, subcomp)
end

function mcu_object:update_version_by_mcu(bus)
    if upgrade_service_handle.get_instance():is_upgrading_or_activing() then
        return
    end
    local subcomp, major_version, minor_version, revision
    if next(self.mcu.SubCompList) then
        subcomp = self.mcu.SubCompList[1]
    end
    major_version, minor_version, revision = self.interface:get_version(subcomp)
    if major_version and minor_version and revision then
        self:set_mcu_major_version(major_version)
        self:set_mcu_minor_version(minor_version)
        self:set_mcu_revision(revision)
        self:update_firmware_version(bus)
    end
end

-- 每60s更新一次mcu的版本号，解决带内升级版本号不更新问题
function mcu_object:update_version(bus)
    cmn.skynet.fork(function ()
        while true do
            cmn.skynet.sleep(6000)
            self:update_version_by_mcu(bus)
        end
    end)
end

function mcu_object:repeat_get_version()
    local version = ''
    local timeout = 30
    repeat
        version = self:get_mcu_version()
        cmn.skynet.sleep(100)
        timeout = timeout - 1
    until version ~= '' or timeout < 0
    return version
end

--- @function 向FirmwareInventory注册MCU版本信息
function mcu_object:register_mcu_firmware_inventory(bus)
    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 fail')
        return
    end

    local version = self:repeat_get_version()

    local param = {
        Id = self.mcu.Id,
        Name = fw_manager:get_device_name_by_position(self.mcu.BoardType, self.position) .. ' MCU',
        Version = version,
        BuildNum = '',
        ReleaseDate = '',
        LowestSupportedVersion = '',
        SoftwareId = self.mcu.SoftwareId or '',
        Manufacturer = 'Huawei',
        Location = '',
        State = 'Enabled',
        Severity = 'Informational'
    }
    obj:Add(context.new(), param, true, 1, 10)  -- MCU固件升级包最大为10MB
    log:notice('[McuUpgrade] register MCU version to FirmwareInventory done, %s', self.mcu.Id)
end

function mcu_object:is_vrd_load()
    return self.vrd_load
end

function mcu_object:update_sub_component_info_list(version)
    version = version == '-' and '255' or version
    for _, v in ipairs(self.sub_component_info_list) do
        v['major_version'] = version
    end
end

--- @function 更新FirmwareInventory
function mcu_object:update_firmware_version(bus)
    cmn.skynet.fork(function()
        local path = FWINVENTORY_PATH .. "/" .. self.mcu.Id
        local ok, obj = pcall(mdb.get_cached_object, bus, path, FWINFO_INTERFACE)
        if not ok then
            log:error('[McuUpgrade] get firmware object fail, path: %s', path)
            return
        end
        local version = self:repeat_get_version()
        if obj.Version ~= version then
            obj.Version = version
            log:notice('[McuUpgrade] updated firmware version %s to FirmwareInventory, id: %s',
                obj.Version, self.mcu.Id)
        end
    end)
end

-- @function 向FirmwareInventory注册MCU以及MCU管理的子件
function mcu_object:register_firmware_inventory(bus)
end

function mcu_object:register_firmware_info(bus)
    cmn.skynet.fork(function ()
        self:register_firmware_inventory(bus)
    end)
end

return mcu_object