-- Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
-- 
-- this file licensed under the 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 custom_msg = require 'messages.custom'
local base_msg = require 'messages.base'
local ipmi_msg = require 'ipmi.ipmi_message'

local cooling_mgmt = require 'basic_cooling.cooling_mgmt'
local cooling_enums = require 'basic_cooling.define.cooling_enums'
local utils = require 'basic_cooling.cooling_utils'
local props = require 'basic_cooling.define.cooling_properties'
local cooling_policys = require 'basic_cooling.cooling_policys'
local cooling_fans = require 'basic_cooling.cooling_device.cooling_fans'
local cooling_pumps = require 'basic_cooling.cooling_device.cooling_pumps'
local fans_config = require 'basic_cooling.cooling_config.fans_config'
local pumps_config = require 'basic_cooling.cooling_config.pumps_config'
local f_enum = require 'class.types.types'
local cooling_requirements = require 'basic_cooling.cooling_requirememts'
local enums = require 'ipmi.enums'
local channel_type = enums.ChannelType

local cooling_ipmi = class()

local IPMI_SUCCESS_CODE = 0
local MANUFACTURE_ID = 0x0007DB
local PICMG_IDENTIFIER = 0x00
local ENERGY_SAVING = 0x10
local LOWNOISE = 0x11
local HIGHPERFORMANCE = 0x12
local CUSTOM = 0x13
local LIQUIDCOOLING = 0x14
local smart_mode = {
    [ENERGY_SAVING] = 'EnergySaving', -- 节能模式
    [LOWNOISE] = 'LowNoise', -- 低噪声模式
    [HIGHPERFORMANCE] = 'HighPerformance', -- 高性能模式
    [CUSTOM] = 'Custom', -- 用户自定义模式
    [LIQUIDCOOLING] = 'LiquidCooling' -- 液冷模式
}
local AUTO = 0
local MANUAL = 1
local MIXED = 2
local ctrl_mode = {
    [AUTO] = 'Auto',
    [MANUAL] = 'Manual',
    [MIXED] = 'Mixed'
}

local MIN_INLET_TEMP_MPC_SUPPORT<const> = 23
local MAX_INLET_TEMP_MPC_SUPPORT<const> = 28
local MPC_THERMAL_RESISTANCE_MODEL_INFO_LEN<const> = 30
local MAX_TIMEOUT <const> = 0xffffffff
local MANUFACTURE_COOLING_CHANGE<const> = 5
local NORMAL_COOLING_CHANGE<const> = 1

local function table_to_hex_str(tab)
    local max_id = 0
    local hex_str = ""
    -- 传入的table可能是不连续的
    for k, _ in pairs(tab) do
        max_id = k > max_id and k or max_id
    end
    for i = 1, max_id, 1 do
        if tab[i] then
            hex_str = hex_str .. string.char(tab[i])
        end
    end
    return hex_str
end


function cooling_ipmi:ctor(db, data)
    self.db = db
    self.data = data
    self.fans_config_instance = fans_config:get_instance()
    self.fans_instance = cooling_fans.get_instance()
    self.pumps_config_instance = pumps_config.get_instance()
    self.pumps_instance = cooling_pumps.get_instance()
    self.policys_instance = cooling_policys.get_instance()
    self.requirements_instance = cooling_requirements.get_instance()
    self.mgmt_instance = cooling_mgmt.get_instance()
end

function cooling_ipmi.get_idx_by_mode(mode)
    for i , m in pairs(smart_mode) do
        if mode == m then
            return i
        end
    end
end

function cooling_ipmi.get_mode_by_idx(idx)
    return smart_mode[idx]
end

local function get_timeout_by_add_info(addinfo, max_len)
    local slen = string.len(addinfo)
    local timeout = 0
    if slen > max_len then
        return nil
    end
    -- 小端排列，超时时间最大4位
    for i = 4, 1, -1 do
        if slen >= i then
            timeout = timeout * 256 + string.byte(addinfo, i)
        end
    end
    -- 超时时间设置0表示设置最大超时时间
    if timeout == 0 then
        timeout = MAX_TIMEOUT
    end

    return timeout
end

function cooling_ipmi:SetFanCtlMode(req, ctx)
    local mixed_mode_supported = self.fans_config_instance:get_mixed_supported() -- 对A+X场景做校验
    local mode = ctrl_mode[req.Mode]
    if not mixed_mode_supported and mode == cooling_enums.modes.Mixed then
        log:error('Current fan ctrl mode: %s is not a valid input under A+X scenario', mode)
        utils.ipmi_op(ctx, 'Set fan mode failed')
        error(custom_msg.PropertyValueOutOfRange(mode, 'CtrlMode'))
    end

    if not mode then
        log:error("The fan ctrl mode(%s) is invalid", req.Mode)
        utils.ipmi_op(ctx, 'Set fan mode failed')
        error(custom_msg.IPMIInvalidFieldRequest())
    end
    -- AddInfo长度为5位时候，前4位为超时时间，第5位为指定生效散热单元
    local timeout = get_timeout_by_add_info(req.AddInfo, 5)
    if not timeout then
        log:error('Get timeout failed')
        utils.ipmi_op(ctx, 'Set fan mode failed')
        error(custom_msg.IPMIInvalidFieldRequest())
    end

    local ok = self.mgmt_instance:set_fan_ctrl_mode(mode, timeout)

    if mode == cooling_enums.modes.Manual then
        if not ok then
            utils.ipmi_op(ctx, "Set fan mode to (manual) and its expiry time to (%u) seconds failed", timeout)
            error(custom_msg.IPMIInvalidFieldRequest())
        end
        utils.ipmi_op(ctx, "Set fan mode to (manual) and its expiry time to (%u) seconds successfully", timeout)
    elseif mixed_mode_supported and mode == cooling_enums.modes.Mixed then
        if not ok then
            utils.ipmi_op(ctx, "Set fan mode to (mixed) failed")
            error(custom_msg.IPMIInvalidFieldRequest())
        end
        utils.ipmi_op(ctx, "Set fan mode to (mixed) successfully")
    else
        if not ok then
            utils.ipmi_op(ctx, "Set fan mode to (auto) failed")
            error(custom_msg.IPMIInvalidFieldRequest())
        end
        utils.ipmi_op(ctx, "Set fan mode to (auto) successfully")
    end

    return ipmi_msg.SetFanCtlModeRsp.new(0, MANUFACTURE_ID) -- 返回码0成功
end

function cooling_ipmi:GetFanCtlMode(req, ctx)
    local fan_crl_mode = self.fans_config_instance:get_ctrl_mode()
    local timeout = self.fans_config_instance:get_manual_timeout()
    local rsp = ipmi_msg.GetFanCtlModeRsp.new()
    rsp.CompletionCode = IPMI_SUCCESS_CODE
    rsp.ManufactureId = MANUFACTURE_ID

    if not fan_crl_mode or not timeout then
        log:error('Get fan ctrl mode and timeout failed')
        error(custom_msg.IPMIInvalidFieldRequest())
    end

    if fan_crl_mode == cooling_enums.modes.Auto then
        rsp.Mode = AUTO
        rsp.Timeout = ''
    elseif fan_crl_mode == cooling_enums.modes.Mixed then
        rsp.Mode = MIXED
        rsp.Timeout = ''
    else
        rsp.Mode = MANUAL
        rsp.Timeout = string.pack('I', timeout)
    end
    return rsp
end

function cooling_ipmi:SetFanLevel(req, ctx)
    if #req.LocalCtllEnable > 1 then
        log:error('Local ctrl enabled length(%s) is invalid', #req.LocalCtllEnable)
        utils.ipmi_op(ctx, 'Set fan level failed')
        error(custom_msg.IPMIRequestLengthInvalid())
    end
    if req.FruId > 0 then
        log:error('Fru id(%s) is invalid', req.FruId)
        utils.ipmi_op(ctx, 'Set fan level failed')
        error(custom_msg.IPMIInvalidFieldRequest())
    end
    local level_range = self.fans_config_instance:get_level_range()
    if req.FanLevel < level_range[1] or req.FanLevel > level_range[2] then
        log:error("Level(%s) of fan is out of range, the correct range is %d to %d",
            req.FanLevel, level_range[1], level_range[2])
        utils.ipmi_op(ctx, 'Set fan level failed')
        error(custom_msg.IPMIOutOfRange())
    end

    local ok = self.fans_config_instance:set_manual_level(req.FanLevel)
    if not ok then
        utils.ipmi_op(ctx, "Set fan level to (%u) failed", req.FanLevel)
        error(custom_msg.IPMIInvalidFieldRequest())
    end
    utils.ipmi_op(ctx, "Set fan level to (%u) successfully", req.FanLevel)
    return ipmi_msg.SetFanLevelRsp.new(0, PICMG_IDENTIFIER)
end

function cooling_ipmi:GetFanLevel(req, ctx)
    local fru_id = req.FruId
    if fru_id > 0 then
        error(custom_msg.IPMIInvalidFieldRequest())
    end
    local fan_level = self.fans_config_instance:get_manual_level()

    return ipmi_msg.GetFanLevelRsp.new(0, PICMG_IDENTIFIER, fan_level)
end

function cooling_ipmi:check_cooling_mode(req, ctx)
    local config_obj = self.fans_config_instance:get_obj()
    if not config_obj then
        log:error('cooling', "[Cooling] get config object failed")
        utils.ipmi_op(ctx, "Set smart cooling mode failed")
        error(custom_msg.IPMICommandCannotExecute())
    end

    if req.PolicyValue >= 0 and req.PolicyValue <= 2 then -- 0到2的散热模式暂不支持
        log:error("Set smart cooling mode 0x%02x is not support", req.PolicyValue)
        utils.ipmi_op(ctx, "Set smart cooling mode failed")
        error(custom_msg.IPMIOutOfRange())
    end
    if config_obj[props.COOLING_MEDIUM] == cooling_enums.cooling_mediums.AIR_COOLING then -- 风冷模式仅支持0x10到0x13
        if req.PolicyValue > CUSTOM or req.PolicyValue < ENERGY_SAVING then
            log:error("Failed with unknown air Smart Cooling mode 0x%02x", req.PolicyValue)
            utils.ipmi_op(ctx, "Set thermal level of thermal enforce policy failed")
            error(custom_msg.IPMIOutOfRange())
        end
    else
        if req.PolicyValue > LIQUIDCOOLING or req.PolicyValue < CUSTOM then
            log:error("Failed with unknown liquid Smart Cooling mode 0x%02x", req.PolicyValue)
            utils.ipmi_op(ctx, "Set thermal level of thermal enforce policy failed")
            error(custom_msg.IPMIOutOfRange())
        end
    end
    if config_obj.SmartCoolingState ~= cooling_enums.smart_cooling_state.ENABLED then -- SmartCooling使能判断
        log:error("Smart cooling mode is disable")
        utils.ipmi_op(ctx, "Set smart cooling mode of thermal enforce policy failed")
        error(custom_msg.IPMICommandCannotExecute())
    end
    local is_manufacture = pcall(require, 'thermal_mgmt_manufacture') -- 判断是否为装备包
    if not is_manufacture and req.PolicyOption == MANUFACTURE_COOLING_CHANGE then
        log:error('Dft Ipmi cmd must send in Dft package')
        utils.ipmi_op(ctx, "Set Dft smart cooling mode failed")
        error(custom_msg.IPMIInvalidFieldRequest())
    end
end

-- IPMI设置散热模式
function cooling_ipmi:SetCoolingMode(req, ctx)
    local smartmode_dsp = {
        [ENERGY_SAVING] = 'energy saving', -- 节能模式
        [LOWNOISE] = 'low noise', -- 低噪声模式
        [HIGHPERFORMANCE] = 'high performance', -- 高性能模式
        [CUSTOM] = 'custom', -- 用户自定义模式
        [LIQUIDCOOLING] = 'liquid cooling' -- 液冷模式
    }
    -- 校验传入参数是否有效
    self:check_cooling_mode(req, ctx)
    local ok, err_info
    if req.PolicyOption == NORMAL_COOLING_CHANGE then
        log:notice('Normal ipmi cmd change smart_cooling_mode')
        ok, err_info = self.fans_config_instance:set_smartcooling_mode(smart_mode[req.PolicyValue],
            cooling_enums.smart_cooling_changed.BOTH)
    elseif req.PolicyOption == MANUFACTURE_COOLING_CHANGE then -- 过滤装备的ipmi命令
        log:notice('Dft ipmi cmd change smart_cooling_mode')
        ok, err_info = self.fans_config_instance:set_smartcooling_mode(smart_mode[req.PolicyValue],
            cooling_enums.smart_cooling_changed.BASIC)
    else
        error(custom_msg.IPMIInvalidCommand())
    end
    if not ok then
        utils.ipmi_op(
            ctx, "Set Smart Cooling mode of thermal enforce policy to %s failed", smartmode_dsp[req.PolicyValue])
        error(custom_msg.IPMIInvalidFieldRequest())
    end
    utils.ipmi_op(
        ctx, "Set Smart Cooling mode of thermal enforce policy to %s successfully", smartmode_dsp[req.PolicyValue])
    return ipmi_msg.SetCoolingModeRsp.new(0, MANUFACTURE_ID) -- 返回码0成功
end

-- 获取SmartCoolingMode属性值
local function GetSmartCoolingMode(config_obj, ctx)
    local mode_index = cooling_ipmi.get_idx_by_mode(config_obj.SmartCoolingMode)

    if mode_index then
        if mode_index >= ENERGY_SAVING and mode_index <= LIQUIDCOOLING then
            mode_index = 2 --这几种模式统一返回2
        end
        return ipmi_msg.GetCoolingModeRsp.new(0, MANUFACTURE_ID, 1, mode_index) -- 返回PolicyOption为1
    end
    log:error('cooling', "Smart cooling mode is a nil value")
    utils.ipmi_op(ctx, "Get smart cooling mode failed")
    error(custom_msg.IPMIInvalidFieldRequest())
end

-- IPMI获取散热模式
function cooling_ipmi:GetCoolingMode(req, ctx)
    local config_obj = self.fans_config_instance:get_obj()

    if config_obj[props.COOLING_WAY] == cooling_enums.cooling_way.PASSIVE then -- 如果是被动散热的-E9000刀片,则需要返回不支持
        log:error("Current cooling way %u is not support", config_obj[props.COOLING_WAY])
        error(custom_msg.IPMICommandCannotExecute())
    end
    if #req.PolicyOption == 0 then -- 0长度， 兼容旧的接口：只有4个参数,缺少末尾的PolicyOption
        return GetSmartCoolingMode(config_obj, ctx)
    end
    local policy_option = string.format('%u', string.unpack('B', req.PolicyOption))
    -- 兼容旧的ipmi命令 1-fanspeed_enforce
    if policy_option == '1' then
        return GetSmartCoolingMode(config_obj, ctx)
    else
        log:error("Current policy option %u is not support", policy_option)
        error(custom_msg.IPMIInvalidCommand())
    end
end

-- Get Fan Speed Properties
function cooling_ipmi:GetFanSpdProps(req, ctx)
    local config_obj = self.fans_config_instance:get_obj()

    if req.FruId > 0 then
        error(custom_msg.IPMIOutOfRange())
    end

    local cooling_way = config_obj[props.COOLING_WAY]
    if cooling_way == cooling_enums.cooling_way.PROACTIVE then
        local rsp = ipmi_msg.GetFanSpdPropsRsp.new()
        rsp.CompletionCode = IPMI_SUCCESS_CODE
        rsp.PicmgIdentifier = PICMG_IDENTIFIER 
        rsp.MinimalSpd  = config_obj[props.LEVEL_RANGE][1]
        rsp.MaximumSpd  = 100 -- 最大转速为默认值100
        rsp.DefaultSpd  = config_obj[props.LEVEL_RANGE][1]
        rsp.DefaultData = 0x80
        return rsp
    else
        error(custom_msg.IPMIInvalidCommand())
    end
end

-- 获取入风口调速策略
function cooling_ipmi:GetInletPolicy(req, ctx)
    local inlet_policy_obj = self.policys_instance:get_obj_by_policy_type(f_enum.PolicyType.InletCustom:value())
    if not inlet_policy_obj then
        log:error('Obj( inlet_policy ) is nil')
        error(base_msg.InternalError())
    end

    local rsp = ipmi_msg.GetInletPolicyRsp.new()
    rsp.CompletionCode = IPMI_SUCCESS_CODE
    rsp.ManufactureId  = MANUFACTURE_ID
    rsp.EnvArrLen      = #inlet_policy_obj[props.POLICY_TEMP_RANGE_LOW] - 1
    rsp.EnvSpdArr      = ''
    for i = 2, #inlet_policy_obj[props.POLICY_TEMP_RANGE_LOW] do
        rsp.EnvSpdArr =
            string.format('%s%s', rsp.EnvSpdArr, string.char(inlet_policy_obj[props.POLICY_TEMP_RANGE_LOW][i]))
    end
    for i = 1, #inlet_policy_obj[props.POLICY_SPEED_RANGE_LOW] do
        rsp.EnvSpdArr =
            string.format('%s%s', rsp.EnvSpdArr, string.char(inlet_policy_obj[props.POLICY_SPEED_RANGE_LOW][i]))
    end

    return rsp
end

-- 设置入风口调速策略
function cooling_ipmi:SetInletPolicy(req, ctx)
    local inlet_policy_obj = self.policys_instance:get_obj_by_policy_type(f_enum.PolicyType.InletCustom:value())
    if not inlet_policy_obj then
        log:error('Obj(%s) is nil', (not inlet_policy_obj and ' inlet_policy ' or ''))
        utils.ipmi_op(ctx, 'Set inlet temperature adjustment policy failed')
        error(base_msg.InternalError())
    end

    -- EnvArrLen标识温度数组长度，速度数组比温度数组长度大1
    if req.EnvArrLen * 2 + 1 ~= #req.EnvSpdArr then
        log:error('Data len is invalid, env arr len:%u, env_speed arr len:%u', req.EnvArrLen, #req.EnvSpdArr)
        utils.ipmi_op(ctx, 'Set inlet temperature adjustment policy failed')
        error(custom_msg.IPMIRequestLengthInvalid())
    end

    local temp_arr_s = string.sub(req.EnvSpdArr, 1, req.EnvArrLen)
    local spd_arr_s = string.sub(req.EnvSpdArr, req.EnvArrLen + 1)

    local ok, err_info =
        cooling_policys.set_cooling_policy(inlet_policy_obj, self.db, temp_arr_s, spd_arr_s)
    if not ok then
        utils.ipmi_op(ctx, 'Set inlet temperature adjustment policy failed')
        error(err_info)
    end

    utils.ipmi_op(ctx, 'Set inlet temperature adjustment policy successfully, temperature array[%s], speed array[%s]',
        table.concat({string.byte(temp_arr_s, 1, #temp_arr_s)}, ' '),
        table.concat({string.byte(spd_arr_s, 1, #spd_arr_s)}, ' '))
    return ipmi_msg.SetInletPolicyRsp.new(0, MANUFACTURE_ID)
end

function cooling_ipmi:GetInitCoolingUnitLevel(req, ctx)
    if req.ParamLen ~= 0 then
        log:error('Param len(%u) is invalid, correct length: 0', req.ParamLen)
        error(custom_msg.IPMIOutOfRange())
    end

    local config_obj = self.fans_config_instance:get_obj()
    if not config_obj then
        log:error('Config obj is nil')
        error(base_msg.InternalError())
    end

    local rsp = ipmi_msg.GetInitCoolingUnitLevelRsp.new()
    rsp.CompletionCode = IPMI_SUCCESS_CODE
    rsp.ManufactureId  = MANUFACTURE_ID
    rsp.Level = config_obj[props.INIT_LEVEL_IN_STARTUP]
    return rsp
end

function cooling_ipmi:SetInitCoolingUnitLevel(req, ctx)
    local MIN_INIT_LEVEL_IN_STARTUP<const> = 50
    local MAX_INIT_LEVEL_IN_STARTUP<const> = 100

    if req.ParamLen ~= 1 then
        log:error('Param len(%u) is invalid, correct length: 1', req.ParamLen)
        utils.ipmi_op(ctx, "Set the default initial fan speed ratio failed")
        error(custom_msg.IPMIOutOfRange())
    end

    if req.Level < MIN_INIT_LEVEL_IN_STARTUP or req.Level > MAX_INIT_LEVEL_IN_STARTUP then
        log:error('The init level(%u) is out of range, the correct range is %u to %u', req.Level,
            MIN_INIT_LEVEL_IN_STARTUP, MAX_INIT_LEVEL_IN_STARTUP)
        utils.ipmi_op(ctx, "Set the default initial fan speed ratio failed")
        error(custom_msg.IPMIOutOfRange())
    end

    local config_obj = self.fans_config_instance:get_obj()
    if not config_obj then
        log:error('Config obj is nil')
        utils.ipmi_op(ctx, "Set the default initial fan speed ratio to %u%% failed", req.Level)
        error(base_msg.InternalError())
    end

    config_obj[props.INIT_LEVEL_IN_STARTUP] = req.Level

    utils.ipmi_op(ctx, "Set the default initial fan speed ratio to %u%% successfully", req.Level)

    local rsp = ipmi_msg.SetInitCoolingUnitLevelRsp.new()
    rsp.CompletionCode = IPMI_SUCCESS_CODE
    rsp.ManufactureId  = MANUFACTURE_ID
    return rsp
end

-- 获取液冷设备的控制模式
function cooling_ipmi:GetCoolingDeviceMode(req, ctx)
    local crl_mode
    local timeout
    -- 设备类型0为风扇 1为泵
    if req.DeviceType == 0 then
        crl_mode = self.fans_config_instance:get_ctrl_mode()
        timeout = self.fans_config_instance:get_manual_timeout()
    elseif req.DeviceType == 1 then
        crl_mode = self.pumps_config_instance:get_ctrl_mode()
        timeout = self.pumps_config_instance:get_manual_timeout()
    else
        log:error("Device type(%s) is invalid, expect 0 or 1", req.DeviceType)
        error(custom_msg.IPMIOutOfRange())
    end
    local device_type = req.DeviceType == 0 and 'fan' or 'pump'
    -- 对象未分发或者不支持的机型不会分发对应的config对象
    if not crl_mode or not timeout then
        log:error('Get %s ctrl mode and timeout failed', device_type)
        error(custom_msg.IPMIInvalidFieldRequest())
    end

    local rsp = ipmi_msg.GetCoolingDeviceModeRsp.new()
    rsp.CompletionCode = IPMI_SUCCESS_CODE
    rsp.ManufactureId = MANUFACTURE_ID
    if crl_mode == cooling_enums.modes.Auto then
        rsp.Mode = AUTO
        rsp.Timeout = ''
    elseif crl_mode == cooling_enums.modes.Mixed then
        rsp.Mode = MIXED
        rsp.Timeout = ''
    else
        rsp.Mode = MANUAL
        rsp.Timeout = string.pack('I', timeout)
    end
    return rsp
end

-- 获取散热设备转速二进制表
local get_device_level_func_table = {
    [0] = function(self, device_id, fan_num, pump_num) -- 获取所有风扇的手动转速
        local level_tab = self.fans_instance:get_discontinuous_cooling_device_manual_level()
        return table_to_hex_str(level_tab)
    end,
    [1] = function(self, device_id, fan_num, pump_num) -- 获取所有风扇的实际转速
        local level_tab = self.fans_instance:get_discontinuous_cooling_device_actual_level()
        return table_to_hex_str(level_tab)
    end,
    [2] = function(self, device_id, fan_num, pump_num) -- 获取单个风扇的手动转速
        local level = self.fans_instance:get_single_cooling_device_manual_level(device_id)
        if not level then
            log:error('Fan id(%s) is out of range, expect 1 to %s', device_id, fan_num)
            error(custom_msg.IPMIOutOfRange())
        end
        return string.char(level)
    end,
    [3] = function(self, device_id, fan_num, pump_num) -- 获取单个风扇的实际转速
        local level = self.fans_instance:get_single_cooling_device_actual_level(device_id)
        if not level then
            log:error('Fan id(%s) is out of range, expect 1 to %s', device_id, fan_num)
            error(custom_msg.IPMIOutOfRange())
        end
        return string.char(level)
    end,
    [4] = function(self, device_id, fan_num, pump_num) -- 获取所有泵的手动转速
        local level_tab = self.pumps_instance:get_discontinuous_cooling_device_manual_level()
        return table_to_hex_str(level_tab)
    end,
    [5] = function(self, device_id, fan_num, pump_num) -- 获取所有泵的实际转速
        local level_tab = self.pumps_instance:get_discontinuous_cooling_device_actual_level()
        return table_to_hex_str(level_tab)
    end,
    [6] = function(self, device_id, fan_num, pump_num) -- 获取单个泵的手动转速
        local level = self.pumps_instance:get_single_cooling_device_manual_level(device_id)
        if not level then
            log:error('Pump id(%s) is out of range, expect 1 to %s', device_id, pump_num)
            error(custom_msg.IPMIOutOfRange())
        end
        return string.char(level)
    end,
    [7] = function(self, device_id, fan_num, pump_num) -- 获取单个泵的实际转速
        local level = self.pumps_instance:get_single_cooling_device_actual_level(device_id)
        if not level then
            log:error('Pump id(%s) is out of range, expect 1 to %s', device_id, pump_num)
            error(custom_msg.IPMIOutOfRange())
        end
        return string.char(level)
    end
}

-- 获取散热设备的转速
function cooling_ipmi:GetCoolingDeviceLevel(req, ctx)
    local fan_num = self.fans_instance:get_obj_count()
    local pump_num = self.pumps_instance:get_obj_count()
    -- 设备类型0为风扇 1为泵
    if req.DeviceType ~= 0 and req.DeviceType ~= 1 then
        log:error("Device type(%s) is invalid, expect 0 or 1", req.DeviceType)
        error(custom_msg.IPMIOutOfRange())
    end
    -- 0为手动模式下发转速 1为实际转速
    if req.Mode ~= 0 and req.Mode ~= 1 then
        log:error("Mode(%s) is out of range, expect 0 or 1", req.Mode)
        error(custom_msg.IPMIOutOfRange())
    end

    local rsp = ipmi_msg.GetCoolingDeviceLevelRsp.new()
    rsp.CompletionCode = IPMI_SUCCESS_CODE
    rsp.ManufactureId = MANUFACTURE_ID
    if req.DeviceType == 0 and req.DeviceId == 0xff then
        rsp.DeviceNum = fan_num -- 返回的器件个数
    elseif req.DeviceType == 1 and req.DeviceId == 0xff then
        rsp.DeviceNum = pump_num
    else
        rsp.DeviceNum = 1
    end

    local binary_device_num = 1
    if req.DeviceId == 0xff then
        binary_device_num = 0
    end
    -- DeviceType 0和1分别表示风扇和泵
    -- binary_device_num 0和1分别表示所有散热器件和单个器件
    -- Mode 0和1分别表示手动模式期望转速和器件实际转（流）速
    local binaryStr = tostring(req.DeviceType) .. tostring(binary_device_num) .. tostring(req.Mode)
    local types = tonumber(binaryStr, 2)

    for key, func in pairs(get_device_level_func_table) do
        if key == types then
            rsp.Level = func(self, req.DeviceId, fan_num, pump_num)
        end
    end
    return rsp
end

-- 设置散热设备的散热模式
function cooling_ipmi:SetCoolingDeviceMode(req, ctx)
    if req.DeviceType ~= 0 and req.DeviceType ~= 1 then
        log:error("Device type(%s) is invalid, expect 0 or 1", req.DeviceType)
        utils.ipmi_op(ctx, 'Set cooling device mode failed')
        error(custom_msg.IPMIInvalidFieldRequest())
    end

    local device_type_str = req.DeviceType == 0 and 'fan' or 'pump'
    local mode = ctrl_mode[req.Mode]
    local mixed_mode_supported = self.fans_config_instance:get_mixed_supported() -- 对A+X场景做校验
    if not mixed_mode_supported and mode == cooling_enums.modes.Mixed then
        log:error('Current fan ctrl mode: %s is not a valid input under A+X scenario', mode)
        utils.ipmi_op(ctx, 'Set cooling device mode failed')
        error(custom_msg.PropertyValueOutOfRange(mode, 'CtrlMode'))
    end 
    if not mode then
        log:error("The %s ctrl mode(%s) is invalid", device_type_str, req.Mode)
        utils.ipmi_op(ctx, 'Set %s mode failed', device_type_str)
        error(custom_msg.IPMIInvalidFieldRequest())
    end
    -- 解析手动模式超时时间，仅支持超时时间最大4位
    local timeout = get_timeout_by_add_info(req.Timeout, 4)
    if not timeout then
        log:error('Get timeout failed')
        utils.ipmi_op(ctx, 'Set %s mode failed', device_type_str)
        error(custom_msg.IPMIInvalidFieldRequest())
    end
    local ok
    if req.DeviceType == 0 then
        ok = self.mgmt_instance:set_fan_ctrl_mode(mode, timeout)
    else
        ok = self.pumps_config_instance:set_pump_ctrl_mode(mode, timeout) -- 不支持泵设置时返回false
    end

    if mode == cooling_enums.modes.Auto then
        utils.ipmi_op(ctx, 'Set %s mode to (auto) %s', device_type_str, ok and 'successfully' or 'failed')
    elseif mixed_mode_supported and mode == cooling_enums.modes.Mixed then
        utils.ipmi_op(ctx, 'Set %s mode to (mixed) %s', device_type_str, ok and 'successfully' or 'failed')
    else
        utils.ipmi_op(ctx, "Set %s mode to (manual) and its expiry time to (%u) seconds %s",
            device_type_str, timeout, ok and 'successfully' or 'failed')
    end
    if not ok then
        log:error('Set %s mode failed', device_type_str)
        error(custom_msg.IPMIInvalidFieldRequest()) -- 不支持的散热设备模式设置
    end
    return ipmi_msg.SetFanCtlModeRsp.new(0, MANUFACTURE_ID)
end

-- 设置液冷设备转速二进制表
local set_device_level_func_table = {
    [0] = function(self, ctx, device_id, level) -- 设置所有风扇手动转速
        local l_range = self.fans_config_instance:get_level_range()
        if level < l_range[1] or level > l_range[2] then
            log:error("Level(%s) of fan is out of range, the correct range is %d to %d", level, l_range[1], l_range[2])
            utils.ipmi_op(ctx, "Set fan level to (%d) failed", level)
            error(custom_msg.IPMIOutOfRange())
        end
        local ok = self.fans_config_instance:set_manual_level(level)
        if not ok then
            utils.ipmi_op(ctx, "Set fan level to (%d) failed", level)
            error(custom_msg.IPMIInvalidFieldRequest())
        else
            utils.ipmi_op(ctx, "Set fan level to (%d) successfully", level)
        end
    end,
    [1] = function(self, ctx, device_id, level) -- 设置单个风扇手动转速
        local l_range = self.fans_config_instance:get_level_range()
        if level < l_range[1] or level > l_range[2] then
            log:error("Level(%s) of fan is out of range, the correct range is %d to %d", level, l_range[1], l_range[2])
            utils.ipmi_op(ctx, "Set fan level to (%d) failed", level)
            error(custom_msg.IPMIOutOfRange())
        end
        -- 单个风扇转速仅支持在手动模式下设置
        -- Taishan1280的2个风扇共用pwm，主调速线程中延迟下发转速无法判断用户下发的先后顺序，所以得立即下发生效
        local fan_ctrl_mode = self.fans_config_instance:get_ctrl_mode()
        if fan_ctrl_mode ~= cooling_enums.modes.Manual then
            log:error('Set fan(%s) level(%s) failed, current mode: %s', device_id, level, fan_ctrl_mode)
            utils.ipmi_op(ctx, "Set fan(%d) level to (%d) failed", device_id, level)
            error(custom_msg.IPMIInvalidFieldRequest()) -- 仅在手动模式能设置单个风扇转速
        end
        if not self.fans_instance:set_single_cooling_device_manual_level(device_id, level) or
            not self.mgmt_instance:set_fan_level_hw(device_id, level) then
            log:error("Set fan(%d) level to (%d) failed", device_id, level)
            utils.ipmi_op(ctx, "Set fan(%d) level to (%d) failed", device_id, level)
            error(custom_msg.IPMIOutOfRange()) -- 风扇id超出范围
        else
            utils.ipmi_op(ctx, "Set fan(%d) level to (%d) successfully", device_id, level)
        end
    end,
    [2] = function(self, ctx, device_id, level) -- 设置所有泵手动转速
        local l_range = self.pumps_config_instance:get_level_range()
        if not self.pumps_config_instance:pump_ctrl_supported() then -- 不支持泵
            log:error('Pump ctrl is not supported')
            utils.ipmi_op(ctx, "Set pump level to (%d) failed", level)
            error(custom_msg.IPMIInvalidFieldRequest())
        end
        if level < l_range[1] or level > l_range[2] then
            log:error("Level(%s) of pump is out of range, the correct range is %d to %d", level, l_range[1], l_range[2])
            utils.ipmi_op(ctx, "Set pump level to (%d) failed", level)
            error(custom_msg.IPMIOutOfRange())
        end
        local ok = self.pumps_config_instance:set_manual_level(level)
        if not ok then
            log:error('Set pump manual level to (%s) failed', level)
            utils.ipmi_op(ctx, 'Set pump level to (%d) failed', level)
            error(custom_msg.IPMIInvalidFieldRequest()) -- 设置属性预期是直接成功但是失败
        end
        utils.ipmi_op(ctx, 'Set pump level to (%d) successfully', level)
    end,
    [3] = function(self, ctx, device_id, level) -- 设置单个泵手动转速
        local l_range = self.pumps_config_instance:get_level_range()
        if not self.pumps_config_instance:pump_ctrl_supported() then -- 不支持泵
            log:error('Pump ctrl is not supported')
            utils.ipmi_op(ctx, "Set pump(%d) level to (%d) failed", device_id, level)
            error(custom_msg.IPMIInvalidFieldRequest())
        end
        if level < l_range[1] or level > l_range[2] then
            log:error("Level(%s) of pump is out of range, the correct range is %d to %d", level, l_range[1], l_range[2])
            utils.ipmi_op(ctx, 'Set pump(%d) level to (%d) failed', device_id, level)
            error(custom_msg.IPMIOutOfRange())
        end
        local ok = self.pumps_instance:set_single_cooling_device_manual_level(device_id, level)
        if not ok then
            log:error('Set pump(%s) manual level to (%s) failed', device_id, level)
            utils.ipmi_op(ctx, 'Set pump(%d) level to (%d) failed', device_id, level)
            error(custom_msg.IPMIOutOfRange()) -- 泵id超出范围
        end
        utils.ipmi_op(ctx, 'Set pump(%d) level to (%d) successfully', device_id, level)
    end
}

-- 设置散热设备的散热等级
function cooling_ipmi:SetCoolingDeviceLevel(req, ctx)
    -- DeviceType 0和1分别表示风扇和泵
    if req.DeviceType ~= 0 and req.DeviceType ~= 1 then
        log:error("Device type(%s) is invalid, expect 0 or 1", req.DeviceType)
        error(custom_msg.IPMIOutOfRange())
    end
    -- binary_device_num 0和1分别表示所有散热器件和单个器件
    local binary_device_num = 1
    if req.DeviceId == 0xff then
        binary_device_num = 0
    end
    -- 根据DeviceType和binary_device_num构造二进制表
    local binaryStr = tostring(req.DeviceType) .. tostring(binary_device_num)
    local types = tonumber(binaryStr, 2)
    for key, func in pairs(set_device_level_func_table) do
        if key == types then
            func(self, ctx, req.DeviceId, req.Level)
        end
    end
    return ipmi_msg.SetCoolingDeviceLevelRsp.new(0, MANUFACTURE_ID)
end

return cooling_ipmi