-- 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 boot_flag = require "pojo.boot.boot_flag"
local boot_flag_valid = require "pojo.boot.boot_flag_valid"
local boot_info_acknowledge = require "pojo.boot.boot_info_acknowledge"
local boot_initiator_info = require "pojo.boot.boot_initiator_info"
local boot_initiator_mailbox = require "pojo.boot.boot_initiator_mailbox"
local boot_partition_scan = require "pojo.boot.boot_partition_scan"
local boot_partition_selector = require "pojo.boot.boot_partition_selector"
local boot_progress = require "pojo.boot.boot_progress"
local boot_valid = require "pojo.boot.boot_valid"
local poweroff_timeout = require "pojo.boot.poweroff_timeout"
local poweron_delay = require "pojo.boot.poweron_delay"
local boot_def = require "macros.boot_def"
local prop_def = require "macros.property_def"
local log = require 'mc.logging'
local bs_util = require 'util.base_util'
local vos = require 'utils.vos'
local file = require 'utils.file'
local utils = require 'mc.utils'
local utils_core = require 'utils.core'
local skynet = require 'skynet'

local boot_options_object = class()

-- 向前兼容，当system_id为1时，先获取BootOption_1的数据，如果没有则使用BootOption的数据创建新数据
local function get_boot_options_info_tbl(db, system_id)
    local db_id = 'BootOption'
    if system_id ~= prop_def.DEFAULT_SYSTEM_ID then
        db_id = 'BootOption_' .. system_id
    end
    local db_info = db:select(db.BiosBootOptionTable)
        :where(db.BiosBootOptionTable.Id:eq(db_id)):first()
    if db_info then
        return db_info
    end
    local new_db_info = db.BiosBootOptionTable({Id = db_id,
        ValidFlag = '/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0',
        SetInProgress = '',
        ServicePartitionSelector = '',
        ServicePartitionScan = '',
        BootFlagValidBitClearing = '',
        BootInfoAcknowledge = '',
        BootFlags = '/32/0/0/0/0',
        BootInitiatorInfo = '',
        BootInitiatorMailbox = '',
        WriteProtect = ''})
    new_db_info:save()
    return new_db_info
end

local function registry_boot_option_tbl(self)
    local db_obj = self.db_obj
    local system_id = self.system_id
    local cfg_tbl = {}

    local boot_flag_cfg = boot_flag.new(db_obj, system_id)
    local boot_valid_cfg = boot_valid.new(db_obj, system_id)
    local boot_flag_valid_cfg = boot_flag_valid.new(db_obj, system_id)
    local boot_info_acknowledge_cfg = boot_info_acknowledge.new(db_obj, system_id)
    local boot_initiator_info_cfg = boot_initiator_info.new(db_obj, system_id)
    local boot_initiator_mailbox_cfg = boot_initiator_mailbox.new(db_obj, system_id)
    local boot_partition_scan_cfg = boot_partition_scan.new(db_obj, system_id)
    local boot_partition_selector_cfg = boot_partition_selector.new(db_obj, system_id)
    local boot_progress_cfg = boot_progress.new(db_obj, system_id)
    local power_off_timeout = poweroff_timeout.new()
    local power_on_delay = poweron_delay.new()

    cfg_tbl[boot_def.BIOS_BOOT_FLAGS_CMD] = boot_flag_cfg
    cfg_tbl[boot_def.BIOS_BOOT_NUM] = boot_valid_cfg
    cfg_tbl[boot_def.BIOS_BOOT_PROGRESS_CMD] = boot_progress_cfg
    cfg_tbl[boot_def.BIOS_BOOT_PARTITION_SEL_CMD] = boot_partition_selector_cfg
    cfg_tbl[boot_def.BIOS_BOOT_PARTITION_SCAN_CMD] = boot_partition_scan_cfg
    cfg_tbl[boot_def.BIOS_BOOT_FLAG_VALID_CMD] = boot_flag_valid_cfg
    cfg_tbl[boot_def.BIOS_BOOT_ACKNOWLEDGE_CMD] = boot_info_acknowledge_cfg
    cfg_tbl[boot_def.BIOS_BOOT_INITIATOR_INFO_CMD] = boot_initiator_info_cfg
    cfg_tbl[boot_def.BIOS_BOOT_INITIATOR_MAILBOX_CMD] = boot_initiator_mailbox_cfg
    cfg_tbl[boot_def.POWER_OFF_TIMEOUT] = power_off_timeout
    cfg_tbl[boot_def.POWER_ON_DELAY] = power_on_delay

    self.cfg_tbl = cfg_tbl
end

local JSON_FILES<const> = {
    new_secureboot_file = 'new_secureboot.json',
    current_secureboot_file = 'current_secureboot.json'
}

-- 向前兼容，当system id为1时，首先查看新路径下文件是否存在，如果不存在查看老路径下是否存在
-- 若存在，则将老路径下文件拷贝到新路径下，老路径文件不删除，避免版本回退丢失文件
local function process_json_file(self)
    local old_path
    for k, v in pairs(JSON_FILES) do
        old_path = prop_def.BIOS_CONFIG_PATH .. '/' .. v
        if not vos.get_file_accessible(self[k]) and vos.get_file_accessible(old_path) then
            file.copy_file_s(old_path, self[k])
        end
    end
end

function boot_options_object:ctor(obj, db)
    self.system_id = obj.SystemId
    self.db_obj = get_boot_options_info_tbl(db, obj.SystemId)

    -- 构造boot_option
    registry_boot_option_tbl(self)
    self.new_secureboot_file = bs_util.get_conf_path(self.system_id, prop_def.NEW_SECUREBOOT_FILE)
    self.current_secureboot_file = bs_util.get_conf_path(self.system_id, prop_def.CURRENT_SECUREBOOT_FILE)
    bs_util.new_file(self.new_secureboot_file)
    bs_util.new_file(self.current_secureboot_file)
    utils_core.chmod_s(self.new_secureboot_file, utils.S_IRUSR | utils.S_IWUSR)
    utils_core.chmod_s(self.current_secureboot_file, utils.S_IRUSR | utils.S_IWUSR)
end

function boot_options_object:get_path(name)
    return self[name]
end

function boot_options_object:get_cfg_function(selector)
    local cfg_tbl = self.cfg_tbl
    if not cfg_tbl then
        return nil
    end

    return cfg_tbl[selector]
end

function boot_options_object:set_boot_mode(mode)
    local boot_flag_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_FLAGS_CMD)
    if not boot_flag_cfg then
        return false
    end

    return boot_flag_cfg:set_boot_mode(mode)
end

function boot_options_object:set_boot_option_valid(selector, valid)
    local boot_valid_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_NUM)
    if not boot_valid_cfg then
        return false
    end

    return boot_valid_cfg:set_boot_option_valid(selector, valid)
end

function boot_options_object:set_start_option(start_option, current_effctive_times)
    local boot_valid_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_FLAGS_CMD)
    if not boot_valid_cfg then
        return false
    end

    return boot_valid_cfg:set_start_option(start_option, current_effctive_times)
end

function boot_options_object:set_effective_time(effective_times)
    local boot_valid_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_FLAGS_CMD)
    if not boot_valid_cfg then
        return false
    end

    return boot_valid_cfg:set_effective_time(effective_times)
end

function boot_options_object:get_bmc_enable()
    local boot_valid_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_FLAGS_CMD)
    if not boot_valid_cfg then
        return boot_def.BMC_DISABLE
    end

    return boot_valid_cfg:get_bmc_enable()
end

-- 根据selector获取配置内容
function boot_options_object:get_option_info(selector, config_data)
    local option_cfg = self:get_cfg_function(selector)
    if not option_cfg then
        log:error("boot_options_object: get_option_info(%d) invalid.", selector)
        return nil
    end
    return option_cfg:get_info(config_data, self.system_id)
end

function boot_options_object:get_boot_option_valid(selector)
    local option_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_NUM)
    if not option_cfg then
        return boot_def.BIOS_BOOT_OPTION_INVALID
    end

    return option_cfg:get_boot_option_valid(selector)
end

function boot_options_object:parse_ipmi_data(selector, config_data, ctx)
    local option_cfg = self:get_cfg_function(selector)
    if not option_cfg then
        log:error("boot_options_object: parse_ipmi_data(%d) invalid.", selector)
        return boot_def.COMP_CODE_INVALID_CMD
    end

    return option_cfg:set_info(config_data, ctx)
end

function boot_options_object:get_boot_message()
    local option_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_FLAGS_CMD)
    if not option_cfg then
        log:error("boot_options_object: get_boot_message, boot flag has not cfg_function.")
        return nil
    end

    return option_cfg:get_boot_message()
end

function boot_options_object:get_auto_clear_valid()
    local option_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_FLAG_VALID_CMD)
    if not option_cfg then
        log:error("boot_options_object: get_auto_clear_valid, boot flag has not cfg_function.")
        return nil
    end
    return option_cfg:auto_clear_valid()
end

function boot_options_object:save_ipmi_data(params_info)
    local option_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_FLAGS_CMD)
    if not option_cfg then
        log:error("boot_options_object: save_ipmi_data, boot flag has not cfg_function.")
        return nil
    end
    return option_cfg:save_ipmi_data(params_info)
end

function boot_options_object:update_boot_info(params_info)
    local option_cfg = self:get_cfg_function(boot_def.BIOS_BOOT_FLAGS_CMD)
    if not option_cfg then
        log:error("boot_options_object: update_boot_info, boot flag has not cfg_function.")
        return nil
    end
    return option_cfg:update_boot_info(params_info)
end

function boot_options_object:get_dump_info()
    local log_info = {}
    table.insert(log_info, '------Bios Boot Options Begin------\r\n')
    for _, option_fun in pairs(self.cfg_tbl) do
        local info = option_fun:log_dump()
        if info then
            table.insert(log_info, info)
            table.insert(log_info, '\r\n')
        end
        skynet.sleep(10)
    end

    return table.concat(log_info)
end

return boot_options_object