-- 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 json = require 'cjson'
local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local boot_object = require 'pojo.boot_object'
local bios_factory = require 'factory.bios_factory'
local prop_def = require 'macros.property_def'
local obj_def = require 'macros.object_def'
local boot_def = require 'macros.boot_def'
local setting_service = require 'service.setting_service'
local bs_util = require 'util.base_util'
local base_messages = require 'messages.base'
local RET_OK < const > = 0
local RET_ERR < const > = -1
local import_export_eng = require 'handler.export_import_engine'
local context = require 'mc.context'
local system_info_object = require 'pojo.system_info_object'
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local defs = require 'domain.system_info.defs'
local clsmgmt = require 'mc.class_mgnt'
local skynet = require 'skynet'

local boot_service = class()

function boot_service:ctor(db, local_db)
    self.db = db
    self.local_db = local_db
    self.boot_object_collection = {}
    self.export_data_collection = {}
    self.system_info_collection = {}
end

function boot_service:add_object(system_id, obj)
    pcall(self.add_system_info_object, self, system_id)
    if self.boot_object_collection[system_id] then
        return
    end
    self.boot_object_collection[system_id] = boot_object.new(obj, self.db, system_id)
end

function boot_service:delete_object(system_id)
    local boot_obj = self:get_obj(system_id)
    if not boot_obj then
        return
    end
    clsmgmt('BootOptions'):remove(boot_obj:get_obj())
    self.boot_object_collection[system_id] = nil
end

function boot_service:add_system_info_object(system_id)
    if self.system_info_collection[system_id] then
        return
    end
    self.system_info_collection[system_id] = system_info_object.new(self.local_db, system_id)
end

function boot_service:get_system_info_obj(system_id)
    return self.system_info_collection[system_id or 1]
end

function boot_service:set_boot_prop(prop, value, system_id)
    local boot_obj = self:get_obj(system_id)
    if not prop or not boot_obj then
        log:error('boot_service:set_bios_prop param invalid.')
        return RET_ERR
    end

    boot_obj:set_prop(prop, value)
    return RET_OK
end

function boot_service:get_obj(system_id)
    return self.boot_object_collection[system_id or 1]
end

function boot_service:set_boot_mode_trans(...)
    return bios_factory.get_service("boot_options_service"):set_boot_mode(...)
end

function boot_service:set_start_option_trans(...)
    return bios_factory.get_service("boot_options_service"):set_start_option(...)
end

function boot_service:set_start_option_flag_trans(...)
    return bios_factory.get_service("boot_options_service"):set_start_option_flag(...)
end

function boot_service:set_start_option_flag_ext_trans(...)
    return bios_factory.get_service("boot_options_service"):set_start_option_flag(...)
end

-- 配置导入导出


local export_list = {
    ['StartOption'] = 'BootSourceOverrideTarget',
    ['StartOptionFlag'] = 'BootSourceOverrideEnabled',
    ['StartOptionFlagExt'] = 'BootSourceOverrideEnabled',
    ['BiosBootMode'] = 'BootSourceOverrideMode',
    ['BiosBootModeSw'] = 'BootModeSupport',
    ['BiosBootModeSwEnable'] = 'BootModeIpmiSettable'
}
function boot_service:export(system_id)
    return import_export_eng.export_sevice_cfg(export_list, self, system_id)
end

function boot_service:set_prop(prop, value, system_id)
    return self:set_boot_prop(prop, value, system_id)
end

function boot_service:get_boot_prop(prop, system_id)
    local boot_obj = self:get_obj(system_id)
    if not prop or not boot_obj then
        log:error('boot_service:get_bios_prop prop(%s) system id(%s) fail.', prop, system_id)
        return nil
    end

    return boot_obj:get_prop(prop)
end

function boot_service:get_prop(prop, system_id)
    return self:get_boot_prop(prop, system_id)
end

local start_option_flag_string_tbl = {
    ["Disabled"] = boot_def.EFFECTIVE_NONE,
    ["Once"] = boot_def.EFFECTIVE_ONCE,
    ["Continuous"] = boot_def.EFFECTIVE_FOREVER
}

function boot_service:get_start_option_flag(system_id)
    local boot_obj = self:get_obj(system_id)
    if not boot_obj then
        log:error('boot_service:get_start_option_flag param invalid system id(%s).', system_id)
        return
    end
    local times = boot_obj:get_prop(obj_def.BOOT_SOURCE_OVERRIDE_ENABLED)
    return start_option_flag_string_tbl[times]
end

-- 启动顺序数组构建
local function regist_boot_order(boot_order_json)
    local key_tbl = {"BootTypeOrder0", "BootTypeOrder1", "BootTypeOrder2", "BootTypeOrder3"}
    local ret_tbl = {}
    for _, value in pairs(key_tbl) do
        table.insert(ret_tbl, boot_order_json[value])
    end

    return ret_tbl
end

local function set_file_change(bios_obj)
    pcall(function()
        bios_obj:set_file_change(prop_def.BIOS_SETTING_FILE_CHANGED, 0)
        bios_obj:set_ineffective()
    end)
end


local function set_boot_priority_error_info(ctx, error_info)
    log:error("set_boot_priority: %s.", error_info)
    local system_id = bs_util.get_system_id(ctx)
    bs_util.record_operation(ctx, system_id, "Failed to issue BIOS Setup configuration")
end

function boot_service:clear_forever_option(ctx, system_id)
    local boot_options_ser = bios_factory.get_service('boot_options_service')
    if not boot_options_ser then
        return
    end
    local order_table = self:get_prop("BootOrder", system_id)
    if not order_table or not next(order_table) then
        return
    end
    local boot_order_first = order_table[1]
    local start_option_flag = self:get_prop("BootSourceOverrideEnabled", system_id)
    local start_option = self:get_prop("BootSourceOverrideTarget", system_id)
    local start_option_order = boot_def.start_option_to_order[start_option]
    local boot_option_policy = self:get_prop("BootOptionPolicy")
    if boot_option_policy == 1 and start_option_flag == 'Continuous' and
        start_option_order ~= boot_order_first then
        self:set_prop("PreviousBootSourceOverrideTarget", '', system_id)
        boot_options_ser:set_start_option_ext(ctx, "None", system_id)
        boot_options_ser:set_start_option_flag_ext(ctx, "Disabled", system_id)
    end
end

function boot_service:update_order_from_file(system_id, file_name)
    local boot_obj = self:get_obj(system_id)
    if boot_obj then
        boot_obj:update_order_from_file(file_name)
    end
    local ok, err = pcall(self.clear_forever_option, self, context.new(), system_id)
    if not ok then
        log:error('[bios] clear forever option failed, err : %s', err)
    end
end

function boot_service:set_boot_priority(ctx, boot_priority, system_id)
    local bios_ser = bios_factory.get_service('bios_service')
    local bios_obj = bios_ser:get_obj(system_id)
    local boot_obj = self:get_obj(system_id)
    if not boot_priority or not bios_obj or not boot_obj then
        set_boot_priority_error_info(ctx, string.format('param invalid, system id is %s', system_id))
        error(base_messages.InternalError())
    end
    local boot_priority_json = json.decode(boot_priority)
    if not boot_priority_json then
        set_boot_priority_error_info(ctx, "get json fail")
        error(base_messages.MalformedJSON())
    end
    local setting_ser = setting_service.new(boot_priority_json)
    if not setting_ser then
        set_boot_priority_error_info(ctx, "get setting service fail")
        error(base_messages.InternalError())
    end
    local ok, res = pcall(function()
        setting_ser:check_boot_order_format()
    end)
    if not ok then
        set_boot_priority_error_info(ctx, "check_boot_order_format fail")
        error(res)
    end
    local valid = bios_obj:write_order_to_json(boot_priority_json)
    if not valid then
        set_boot_priority_error_info(ctx, "write_order_to_json fail")
        error(base_messages.InternalError())
    end
    local ret = boot_obj:set_prop("BootOrder", regist_boot_order(boot_priority_json))
    if ret == RET_ERR then -- 更新资源树失败
        set_boot_priority_error_info(ctx, "update BootOrder property faild")
        error(base_messages.InternalError())
    end
    set_file_change(bios_obj)
    -- 更新web全量配置菜单
    skynet.fork(function()
        skynet.sleep(100)
        ok, res = pcall(function()
            bios_obj:update_denpendency()
        end)
        if ok then
            log:notice('[bios]update denpendency success after set boot priority')
        else
            log:error('[bios]update denpendency fail after set boot priority %s', res)
        end
    end)
    bs_util.record_operation(ctx, system_id,
        "BIOS Setup configuration issued successfully and will take effect upon the next startup")
    return RET_OK, "", ""
end

local start_option_flag_tbl < const > = {
    [boot_def.EFFECTIVE_NONE] = "Disabled",
    [boot_def.EFFECTIVE_ONCE] = "Once",
    [boot_def.EFFECTIVE_FOREVER] = "Continuous"
}

function boot_service:set_start_option_flag(effective_times, system_id)
    local boot_obj = self:get_obj(system_id)
    if not boot_obj then
        log:error("set_start_option_flag:system id(%s) invalid.", system_id)
        return false
    end
    local start_flag_string = start_option_flag_tbl[effective_times]
    if not start_flag_string then
        log:error("boot_service: effective_times (" .. effective_times .. ") invalid.")
        return false
    end

    local prop = obj_def.BOOT_SOURCE_OVERRIDE_ENABLED
    local ret = boot_obj:set_prop(prop, start_flag_string)
    if ret == RET_ERR then
        log:error("set_boot_options_property: update %s property faild.", prop)
        return false
    end

    return true
end

-- 设置启动项、启动项生效次数
function boot_service:set_boot_options_property(flag, boot_dev, system_id)
    local boot_obj = self:get_obj(system_id)
    if not flag or not boot_dev or not boot_obj then
        log:error("set_boot_options_property:param invalid, system id(%s).", system_id)
        return false
    end
    if flag == boot_def.BMC_DISABLE then
        local start_flag_string = start_option_flag_tbl[flag]
        if not start_flag_string then
            log:error("set_boot_options_property: " .. flag .." invalid.")
            return false
        end
        local prop = obj_def.BOOT_SOURCE_OVERRIDE_ENABLED
        local ret = boot_obj:set_prop(prop, start_flag_string)
        if ret == RET_ERR then
            log:error("set_boot_options_property: update %s property faild.", prop)
            return false
        end
    end
    
    local start_option_string = boot_def.start_option_string_tbl[boot_dev]
    if not start_option_string then
        log:error("set_boot_options_property: boot option(" .. boot_dev .. ") invalid.")
        return false
    end
    local prop = obj_def.BOOT_SOURCE_OVERRIDE_TARGET
    local ret = boot_obj:set_prop(prop, start_option_string)
    if ret == RET_ERR then
        log:error("set_boot_options_property: update %s property faild.", prop)
        return false
    end

    return true
end

-- 设置启动模式开关: 0 关、1 开
function boot_service:set_boot_mode_switch(ctx, mode_switch, system_id)
    log:info("[bios]set_boot_mode_sw start.")
    local obj = self:get_obj(system_id)
    if mode_switch == nil or not obj then
        log:error("[bios]set_boot_mode_sw fail: switch invalid or system id(%s) invalid.", system_id)
        bs_util.record_operation(ctx, system_id, "Failed to set Boot Mode Configuration Over IPMI")
        error(base_messages.InternalError())
    end

    local switch = true
    local switch_str = "ON"
    if not mode_switch then
        switch = false
        switch_str = "OFF"
    end

    local ret = obj:set_prop(obj_def.BIOS_BOOT_MODE_SUPPORT, switch)
    if ret == RET_ERR then
        log:error("[bios] set_boot_mode_sw: set_boot_prop fail.")
        bs_util.record_operation(ctx, system_id, "Failed to set Boot Mode Configuration Over IPMI")
        error(base_messages.InternalError())
    end

    bs_util.record_operation(ctx, system_id,
        string.format("Set Boot Mode Configuration Over IPMI to %s successfully", switch_str))
    return prop_def.RESPONSE_OK
end

-- 设置启动模式开关: 0 关、1 开
function boot_service:set_mode_ipmi_switch(ctx, mode_switch, system_id)
    local obj = self:get_obj(system_id)
    if mode_switch == nil or not obj then
        log:error("[bios]set mode ipmi switch fail: switch invalid or system id(%s) invalid.", system_id)
        bs_util.record_operation(ctx, system_id, "Failed to set Boot Mode Switch Enable Over IPMI")
        error(base_messages.InternalError())
    end

    local switch = true
    local switch_str = "ON"
    if not mode_switch then
        switch = false
        switch_str = "OFF"
    end

    local ret = obj:set_prop("BootModeIpmiSettable", switch)
    if ret == RET_ERR then
        log:error("[bios]set mode ipmi switch fail: set prop fail.")
        bs_util.record_operation(ctx, system_id, "Failed to set Boot Mode Switch Enable Over IPMI")
        error(base_messages.InternalError())
    end

    bs_util.record_operation(ctx, system_id,
        string.format("Set Boot Mode Switch Enable Over IPMI to %s successfully", switch_str))
    return prop_def.RESPONSE_OK
end

function boot_service:collect_json_file(path, system_id)
end

function boot_service:get_dump_info(system_id)
    local obj = self:get_obj(system_id)
    if obj then
        return obj:get_dump_info()
    end
end

local prop_map = {
    ['BiosBootModeSw'] = boot_service.set_boot_mode_switch,
    ['BiosBootModeSwEnable'] = boot_service.set_mode_ipmi_switch,
    ['BiosBootMode'] = boot_service.set_boot_mode_trans,
    ['StartOption'] = boot_service.set_start_option_trans,
    ['StartOptionFlag'] = boot_service.set_start_option_flag_trans,
    ['StartOptionFlagExt'] = boot_service.set_start_option_flag_ext_trans,
}

function boot_service:import(ctx, obj, system_id)
    if not self:get_obj(system_id) then
        bs_util.record_operation(ctx, system_id, "Failed to set Boot Option Info")
        return
    end
    import_export_eng.import_sevice_cfg(ctx, obj, self, "BootOption", prop_map, system_id)
end

function boot_service:get_system_id(ctx)
    if not ctx then
        return 1
    end
    return ctx.HostId or 1
end

function boot_service:clear_system_info(system_id)
    local system_info_obj = self:get_system_info_obj(system_id)
    if not system_info_obj then
        log:error('boot_service: system_info_object is nil, system id is: %s', system_id)
        return
    end
    system_info_obj:clear_system_info()
end

function boot_service:validate_system_info_selector(para_selector)
    if not defs.ParameterSelector[para_selector] then
        return false
    else
        return true
    end
end

function boot_service:get_system_info_parameters(req, ctx, para_selector)
    local system_id = self:get_system_id(ctx)
    local set_selector = req.SetSelector
    local block_selector = req.BlockSelector
    if not set_selector or not block_selector then
        log:error('boot_service: selector is nil, system id is: %s', system_id)
        return comp_code.ReqDataLenInvalid, prop_def.ParameterRevision, ''
    end
    local system_info_obj = self:get_system_info_obj(system_id)
    if not system_info_obj then
        log:error('boot_service: system_info_object is nil, system id is: %s', system_id)
        return comp_code.DataNotAvailable, prop_def.ParameterRevision, ''
    end
    if not self:validate_system_info_selector(para_selector) then
        log:error('boot_service: para_selector is invalid, para_selector: %s' ..
            ', system id is: %s', para_selector, system_id)
        return comp_code.InvalidCommand, prop_def.ParameterRevision, ''
    end
    return system_info_obj:get_info(para_selector, set_selector)
end

function boot_service:set_system_info_parameters(req, ctx, para_selector)
    local system_id = self:get_system_id(ctx)
    local info = req.Info
    if not info then
        log:error('boot_service: selector is nil, system id is: %s', system_id)
        return comp_code.ReqDataLenInvalid
    end
    local system_info_obj = self:get_system_info_obj(system_id)
    if not system_info_obj then
        log:error('boot_service: system_info_object is nil, system id is: %s', system_id)
        return comp_code.DataNotAvailable
    end
    if not self:validate_system_info_selector(para_selector) then
        log:error('boot_service: para_selector is invalid, para_selector: %s' ..
            ', system id is: %s', para_selector, system_id)
        return comp_code.InvalidCommand
    end
    return system_info_obj:set_info(para_selector, info, ctx)
end

return singleton(boot_service)
