-- 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 log = require 'mc.logging'
local mdb = require 'mc.mdb'
local skynet = require 'skynet'
local context = require 'mc.context'
local bios_factory = require 'factory.bios_factory'
local utils = require 'mc.utils'
local client = require 'bios.client'
local prop_def = require 'macros.property_def'

local BIOS_PATH<const> = '/bmc/kepler/UpdateService/FirmwareInventory/Bios'
local BIOS_INTERFACE<const> = 'bmc.kepler.UpdateService.FirmwareInfo'

local TEEOS_PATH<const> = '/bmc/kepler/UpdateService/FirmwareInventory/TeeOS'
local TEEOS_INTERFACE<const> = 'bmc.kepler.UpdateService.FirmwareInfo'

local firmware = class()

local function get_firmware_path(path, system_id)
    if system_id == prop_def.DEFAULT_SYSTEM_ID then
        return path
    end
    return path .. system_id
end

function firmware:ctor(bus, db, sysid)
    self.bus = bus
    self.db = db
    self.sysid = sysid
    self.bios_path = get_firmware_path(BIOS_PATH, sysid)
    self.teeos_path = get_firmware_path(TEEOS_PATH, sysid)
end

function firmware:init()
    -- 注册BIOS固件信息
    self:register_fw_info()
    self:register_teeos_info()
end

-- 向前兼容，当sysid为1时，先获取Bios_1的数据，如果没有则使用Bios的数据创建新数据
function firmware:get_bios_fw_info()
    local db_id = 'Bios'
    if self.sysid ~= prop_def.DEFAULT_SYSTEM_ID then
        db_id = 'Bios' .. self.sysid
    end
    local bios_info = self.db:select(self.db.BiosFwInfoTable)
        :where(self.db.BiosFwInfoTable.Id:eq(db_id)):first()
    if bios_info then
        return bios_info
    end
    bios_info = self.db.BiosFwInfoTable({
        Id = db_id,
        Name = 'Bios',
        Version = '000',
        BuildNum = '',
        ReleaseDate = '',
        LowestSupportedVersion = '',
        SoftwareId = 'BIOS-BIOS',
        Manufacturer = 'Huawei',
        Updateable = true,
        Location = 'U75',
        Capability = '5',
        State = 'Enabled',
        ActiveMode = '',
        ActiveModeSupported = true,
        ActiveModeEnabled = true
    })
    bios_info:save()
    return bios_info
end

function firmware:register_fw_info()
    -- 查询BIOS固件信息
    local bios_info = self:get_bios_fw_info()
    if not bios_info then
        log:error('get bios info failed')
        return
    end
    -- 注册BIOS固件信息到资源树
    skynet.fork_once(function()
        local retries = 0
        local ok, rsp
        repeat
            ok, rsp = client:PFirmwareInventoryFirmwareInventoryAdd(context.new(),
                {Id = bios_info.Id, Name = bios_info.Name, Version = bios_info.Version, BuildNum = bios_info.BuildNum,
                ReleaseDate = bios_info.ReleaseDate, LowestSupportedVersion = bios_info.LowestSupportedVersion,
                SoftwareId = bios_info.SoftwareId, Manufacturer = bios_info.Manufacturer,
                Location = bios_info.Location, State = 'Enabled', Severity = 'Informational'},
                bios_info.Updateable, bios_info.Capability, 20) -- bios固件包需预留20MB的tmp空间
            if not ok then
                retries = retries + 1
                skynet.sleep(100)
            end
        until ok or retries > 120

        if retries > 120 then
            log:error('[BIOSUpgrade] register Bios to firmware inventory fail, message:%s', self.sysid, rsp)
            return
        end
        log:notice('[BIOSUpgrade] register Bios_%s to firmware inventory success', self.sysid)
    end)
end

-- 向前兼容，当sysid为1时，先获取TeeOS_1的数据，如果没有则使用TeeOS的数据创建新数据
function firmware:create_teeos_db_info()
    local db_id = 'TeeOS'
    if self.sysid ~= prop_def.DEFAULT_SYSTEM_ID then
        db_id = 'TeeOS' .. self.sysid
    end
    local db_info = self.db:select(self.db.TeeOSFwInfoTable):
        where(self.db.TeeOSFwInfoTable.Id:eq(db_id)):first()
    if db_info then
        return db_info
    end
    db_info = self.db.TeeOSFwInfoTable({Id = db_id,
            Name = 'TeeOS',
            Version = '',
            BuildNum = '',
            ReleaseDate = '',
            LowestSupportedVersion = '',
            SoftwareId = 'TeeOS-TeeOS',
            Manufacturer = 'Huawei',
            Updateable = true,
            Location = 'U75',
            Capability = 5,
            State = 'Enabled',
            ActiveMode = '',
            ActiveModeSupported = true,
            ActiveModeEnabled = true})
    db_info:save()
    log:notice('[bios]create teeos firmware table, system id: %s', self.sysid)
    return db_info
end

function firmware:register_teeos_info()
    -- 注册BIOS固件信息到资源树
    skynet.fork_once(function()
        local bios_info = self:create_teeos_db_info()
        if not bios_info then
            log:error('create teeos db info failed')
            return
        end
        local retries = 0
        local ok, rsp
        repeat
            ok, rsp = client:PFirmwareInventoryFirmwareInventoryAdd(context.new(),
                {Id = bios_info.Id, Name = bios_info.Name, Version = bios_info.Version, BuildNum = bios_info.BuildNum,
                ReleaseDate = bios_info.ReleaseDate, LowestSupportedVersion = bios_info.LowestSupportedVersion,
                SoftwareId = bios_info.SoftwareId, Manufacturer = bios_info.Manufacturer,
                Location = bios_info.Location, State = 'Enabled', Severity = 'Informational'},
                bios_info.Updateable, bios_info.Capability, 20) -- bios固件包需预留20MB的tmp空间
            if not ok then
                retries = retries + 1
                skynet.sleep(100)
            end
        until ok or retries > 120

        if retries > 120 then
            log:error('[Teeos] register Teeos_%s to firmware inventory fail, message:%s', self.sysid, rsp)
            return
        end
        log:notice('[Teeos] register Teeos_%s to firmware inventory success', self.sysid)
    end)
end
function firmware:regist_signals()
    local bios_ser = bios_factory.get_service('bios_service')
    bios_ser.on_version_changed:on(function(version)
        log:notice('[bios]firmware update bios version(%s)', version)
        self:update_bios_version(version)
    end)
    log:notice('[bios]firmware regist version signal success')
end

function firmware:update_bios_version(version)
    local obj = mdb.get_cached_object(self.bus, self.bios_path, BIOS_INTERFACE)
    obj.Version = version
    local db_id = 'Bios'
    if self.sysid ~= prop_def.DEFAULT_SYSTEM_ID then
        db_id = 'Bios' .. self.sysid
    end
    local fw_table = self.db.BiosFwInfoTable({Id = db_id})
    if fw_table and fw_table.Version then
        fw_table.Version = version
        fw_table:save()
    end
end

function firmware:get_activate_mode()
    local obj = mdb.get_cached_object(self.bus, BIOS_PATH, BIOS_INTERFACE)
    local parameters = obj.Parameters
    if not parameters or not parameters.BiosActivatedScope then
        log:notice('[bios] get activate mode nil, system id: %s', self.sysid)
        return nil
    end
    return utils.split(parameters.BiosActivatedScope, ',')
end

function firmware:clear_activate_mode()
    local obj = mdb.get_cached_object(self.bus, BIOS_PATH, BIOS_INTERFACE)
    obj.Parameters = {}
    log:notice('[bios] clear activate mode(%s), system id: %s', obj.Parameters, self.sysid)
end

function firmware:is_online_force()
    local obj = mdb.get_cached_object(self.bus, self.bios_path, BIOS_INTERFACE)
    local parameters = obj.Parameters
    if not parameters or not parameters.IsOnlineForce then
        log:notice('[bios] get is_online_force nil, system id: %s', self.sysid)
        return false
    end
    log:notice('[bios] is_online_force mode, system id: %s', self.sysid)
    return true
end

function firmware:update_teeos_version(version)
    local obj = mdb.get_cached_object(self.bus, self.teeos_path, TEEOS_INTERFACE)
    obj.Version = version
    local db_id = 'TeeOS'
    if self.sysid ~= prop_def.DEFAULT_SYSTEM_ID then
        db_id = 'TeeOS' .. self.sysid
    end
    local fw_table = self.db.TeeOSFwInfoTable({Id = db_id})
    if fw_table and fw_table.Version then
        fw_table.Version = version
        fw_table:save()
        log:notice('[bios] update teeos version(%s) to db success, system id: %s', version, self.sysid)
    end
end

return firmware
