-- 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 bios_factory = require 'factory.bios_factory'
local log = require 'mc.logging'
local context = require 'pojo.context'
local boot_def = require 'macros.boot_def'
local s_unpack = string.unpack
local ipmi = require 'ipmi'
local prop_handler = class()

local START_OPTION<const> = 1
local START_OPTION_FALG<const> = 2
local BIOS_PRINT_FALG<const> = 3
local BIOS_BOOT_MODE_SW<const> = 4
local BIOS_BOOT_MODE<const> = 5
local REGISTRY_VERSION<const> = 6
local BIOS_JSON_FLAG<const> = 7
local SYSTEM_STARTUP_STATE<const> = 8
local START_OPTION_FALG_EXT<const> = 9
local BIOS_BOOT_MODE_IPMI_SW<const> = 10

local TRANS_TO_BMC<const> = 1
local TRANS_TO_IPMI<const> = 2

local PROPERTY_NAME_INDEX<const> = 1
local PROPERTY_SERVICE_INDEX<const> = 2

local function binary_to_num(binary_data)
    if #binary_data ~= 1 then
        return nil
    end
    return s_unpack("I1", binary_data)
end

local function num_to_binary(num)
    return string.char(num)
end

local function binary_to_boolean(binary_data)
    if #binary_data ~= 1 then
        return nil
    end
    local data = s_unpack("I1", binary_data)
    if data == 0 then
        return false
    elseif data == 1 then
        return true
    end
    return nil
end

local function boolean_to_binary(support)
    if not support then
        return string.char(0)
    end
    return string.char(1)
end

local function trans_string(data)
    return data
end

local function boot_target_to_binary(target)
    local option = boot_def.start_option_tbl[target]
    if not option then
        return ''
    end
    return string.char(option)
end

local function boot_target_enabled_to_binary(target_enabled)
    local option = boot_def.start_option_flag_tbl[target_enabled]
    if not option then
        return ''
    end
    return string.char(option)
end

local function mode_to_binary(mode)
    if mode == "UEFI" then
        return string.char(1)
    end
    return string.char(0)
end

-- 属性列表
local property_tbl = {
    [START_OPTION] = {"BootSourceOverrideTarget", "boot_service"},
    [START_OPTION_FALG] = {"BootSourceOverrideEnabled", "boot_service"},
    [BIOS_PRINT_FALG] = {"BiosLogPrintEnabled", "bios_service"},
    [BIOS_BOOT_MODE_SW] = {"BootModeSupport", "boot_service"},
    [BIOS_BOOT_MODE] = {"BootSourceOverrideMode", "boot_service"},
    [REGISTRY_VERSION] = {"RegistryVersion", "bios_service"},
    [BIOS_JSON_FLAG] = {"BiosJsonFlag", "bios_service"},
    [SYSTEM_STARTUP_STATE] = {"SystemStartupState", "bios_service"},
    [START_OPTION_FALG_EXT] = {"BootSourceOverrideEnabled", "boot_service"},
    [BIOS_BOOT_MODE_IPMI_SW] = {"BootModeIpmiSettable", "boot_service"}
}

-- a.ipmi转换为bmc存储方式
-- b.bmc存储方式转化为ipmi格式
local data_trans_fun = {
    [START_OPTION] = {binary_to_num, boot_target_to_binary},
    [START_OPTION_FALG] = {binary_to_num, boot_target_enabled_to_binary},
    [BIOS_PRINT_FALG] = {binary_to_num, num_to_binary},
    [BIOS_BOOT_MODE_SW] = {binary_to_boolean, boolean_to_binary},
    [BIOS_BOOT_MODE] = {binary_to_num, mode_to_binary},
    [REGISTRY_VERSION] = {nil, trans_string},
    [BIOS_JSON_FLAG] = {nil, num_to_binary},
    [SYSTEM_STARTUP_STATE] = {binary_to_num, num_to_binary},
    [START_OPTION_FALG_EXT] = {binary_to_num, boot_target_enabled_to_binary},
    [BIOS_BOOT_MODE_IPMI_SW] = {binary_to_boolean, boolean_to_binary}
}

local function get_service(name)
    return bios_factory.get_service(name)
end

-- 设置属性的处理函数
local set_funs = {
    [START_OPTION] = function(...)
        return get_service("boot_options_service"):set_start_option(...)
    end,

    [START_OPTION_FALG] = function(...)
        return get_service("boot_options_service"):set_start_option_flag(...)
    end,

    [BIOS_PRINT_FALG] = function(...)
        return get_service("bios_service"):set_print_flag(...)
    end,

    [BIOS_BOOT_MODE_SW] = function(...)
        return get_service("boot_service"):set_boot_mode_switch(...)
    end,

    [BIOS_BOOT_MODE] = function(...)
        return get_service("boot_options_service"):set_boot_mode(...)
    end,

    [REGISTRY_VERSION] = nil,

    [BIOS_JSON_FLAG] = nil,

    [SYSTEM_STARTUP_STATE] = function(...)
        return get_service("bios_service"):set_systems_state(...)
    end,

    [START_OPTION_FALG_EXT] = function(...)
        return get_service("boot_options_service"):set_start_option_flag(...)
    end,

    [BIOS_BOOT_MODE_IPMI_SW] = function(...)
        return get_service("boot_service"):set_mode_ipmi_switch(...)
    end
}

-- 对外接口:根据属性名,设置属性值,并且会校验
function prop_handler.set(prop, value, ctx)
    local property = property_tbl[prop]
    local system_id = ctx.HostId or 1
    if not property then
        log:error('[bios]set bios boot info: property invalid')
        return nil
    end

    local property_name = property[PROPERTY_NAME_INDEX]
    local set_prop_fun = set_funs[prop]
    if not property_name or not set_prop_fun then
        log:error('[bios] set boot info: prop invalid or set forbidden')
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set BIOS info failed")
        return false
    end
    local trans_to_bmc_fun = data_trans_fun[prop][TRANS_TO_BMC]
    local bmc_val = value
    if trans_to_bmc_fun then
        bmc_val = trans_to_bmc_fun(value)
    end
    log:notice('[bios]prop_handler set prop system_id:%s, prop:%s, value:%s',
        system_id, property_name, bmc_val)
    local ok, _ = pcall(function()
        set_prop_fun(context.new(ctx), bmc_val, system_id)
    end)
    if not ok then
        return false
    end

    return true
end

-- 对外接口:根据属性名,查找属性值
function prop_handler.get(prop, system_id)
    local property = property_tbl[prop]
    if not property then
        log:error('[bios]get bios boot info: property invalid')
        return nil
    end
    local property_name = property[PROPERTY_NAME_INDEX]
    local service = bios_factory.get_service(property[PROPERTY_SERVICE_INDEX])
    if not property_name or not service then
        log:error('[bios]get bios boot info: property name invalid')
        return nil
    end

    local value = service:get_prop(property_name, system_id)
    if value == nil then
        return ''
    end

    log:notice('[bios]prop_handler get prop system_id:%s, prop:%s, value:%s',
        system_id, property_name, value)
    local trans_to_ipmi_fun = data_trans_fun[prop][TRANS_TO_IPMI]
    local bios_val = value
    if trans_to_ipmi_fun then
        bios_val = trans_to_ipmi_fun(value)
    end 
    return bios_val
end

return prop_handler