-- 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 log = require 'mc.logging'
local utils = require 'basic_cooling.cooling_utils'
local props = require 'basic_cooling.define.cooling_properties'
local requirements_mgmt = require 'basic_cooling.cooling_requirememts'
local libpid = require 'libpid'

local SZ_WORD = 2

local pid_p_strategy_mapping = {
    Fast = 1,
    Medium = 2,
    Slot = 3
}

local writeinfo_cmd = {
    type_target_temp_config = 1, -- 配置目标温度命令数据域内容
    type_fan_config = 2,         -- 配置风扇属性命令数据域内容
    type_realtime_temp = 3,      -- 配置实时温度命令数据域内容
    type_fan_speed = 4,          -- 配置实时风扇反馈转速命令数据域内容
    type_fan_alarm_speed = 5,    -- 配置实时风扇告警命令数据域内容
    type_envir_temp_config = 7,  -- 配置按照环境温度线性调速命令数据域内容
    type_pow_config = 8,         -- 配置功耗和目标温度对应关系命令数据域内容
    type_realtime_pow = 9,       -- 配置实时功耗的数据
    cmd_set_pid_config = 10,     -- 配置主备切换时的数据
    type_debug_info = 15,        -- 从PID调速模块获取风扇调试信息命令数据域内容
    type_sensor_name_info = 19,  -- 配置传感器名字数据域内容
    type_fan_speed_info = 20,    -- 配置风扇前后转速数据域内容
    type_pid_control_mode = 21,  -- 配置目标调速控制场景
    type_pid_strategy = 22       -- 配置目标温度点调速策略的PID策略
}

local cooling_pid_intf = {}

local function fill_table_byte(buff_t, offset, val)
    buff_t[offset] = val
    return offset + 1
end

local function fill_table_word(buff_t, offset, val)
    buff_t[offset] = val & 0xff
    buff_t[offset + 1] = (val >> 8) & 0xff
    return offset + 2
end

-- 封装writeinfo接口的10个命令头部的前6位
-- | lenLow | lenHigh | reversed | cmd | reserved cmd_lenLow | reserved cmd_lenHigh |
local function table_commmon_header(buff_t, cmd)
    local total_len = 0
    total_len = fill_table_word(buff_t, total_len, 0)
    total_len = fill_table_byte(buff_t, total_len, 0)
    total_len = fill_table_byte(buff_t, total_len, cmd)
    total_len = fill_table_word(buff_t, total_len, 0)
    return total_len
end

-- 更新cmd配置信息至cmd_buff
local function update_pid_cmd_info(cmd_buff, sub_cmd_buff)
    for k, v in pairs(sub_cmd_buff) do
        cmd_buff[k] = v
    end
end

-- 拼接调速设备组信息
local function add_device_group_to_pid(device_type, buff_t, policy_len, device_table, device_group)
    -- 未配置对应散热器件组
    if not device_group then
        log:error("Can not find config device group")
        return nil
    end

    policy_len = fill_table_byte(buff_t, policy_len, utils.size_of(device_group))
    local cooling_device_group = device_table[device_type]
    -- 未找到纳入管理的散热器件组
    if not cooling_device_group then
        log:error("Can not find cooling device group")
        return nil
    end

    for _, device_idx in pairs(device_group) do
        local device = cooling_device_group[device_idx]
        -- 未找到匹配的散热器件
        if not device then
            log:error('Add device group to pid failed, device group: [%s], device_id: %s',
                table.concat(device_group, ', '), device_idx)
            return nil
        end
        policy_len = fill_table_byte(buff_t, policy_len, device.Id)
        policy_len = fill_table_byte(buff_t, policy_len, device.Slot)
    end
    return policy_len
end

-- writeinfo 01 单条目标调速策略配置信息
local function add_target_policy_to_pid(objs, device_table, total_len, tmp_requirement)
    local buff_t = {}
    local device_type = props.COOLING_AIR
    local policy_len = total_len

    -- 配置需要Id与Idx唯一对应
    local req_base_id = tmp_requirement[props.REQ_BASE_ID]
    local area_id = objs.requirement_area_map[req_base_id]
    if not area_id then -- 没找到对应的area
        log:error('Can not find cooling area for requirement(id:%u)', req_base_id)
        return nil, total_len
    end

    local cooling_area = objs.cooling_areas[area_id]
    if not cooling_area then -- area对象为空
        log:error('Can not find cooling area for requirement(id:%u)', req_base_id)
        return nil, total_len
    end
    local device_group = cooling_area.FanIdxGroup
    local liquid_device_group = cooling_area.LiquidCoolingDeviceGroup
    -- 散热域无风扇和泵，调速策略不下发
    if #device_group == 0 and #liquid_device_group == 0 then
        return nil, total_len
    end
    if tmp_requirement.IsValid ~= 1 then -- 温度点不生效
        return nil, total_len
    end
    -- 非目标调速
    if not requirements_mgmt.is_target_temp_cooling(tmp_requirement) then
        return nil, total_len
    end

    policy_len = fill_table_byte(buff_t, policy_len, tmp_requirement[props.REQ_BASE_ID])
    policy_len = fill_table_byte(buff_t, policy_len, tmp_requirement[props.REQ_SLOT])
    policy_len = fill_table_byte(buff_t, policy_len, tmp_requirement.MaxAllowedTemperatureCelsius)
    policy_len = fill_table_byte(buff_t, policy_len, tmp_requirement.TargetTemperatureCelsius)
    policy_len = fill_table_byte(buff_t, policy_len, 0) --迟滞量
    if tmp_requirement.CoolingMedium == props.COOLING_LIQUID then
        device_group = cooling_area.LiquidCoolingDeviceGroup
        device_type = props.COOLING_LIQUID
    end

    policy_len = add_device_group_to_pid(device_type, buff_t, policy_len, device_table, device_group)
    if not policy_len then
        log:error('Add target temp policy(base_id:%s, slot:%s) to pid error',
            tmp_requirement[props.REQ_BASE_ID], tmp_requirement[props.REQ_SLOT])
        return nil, total_len
    end
    return buff_t, policy_len
end

-- write info 01 目标温度命令数据域内容
-- id slot 高温告警门限 目标温度 0 散热器件个数 [device id...] [device_slot]
function cooling_pid_intf.cooling_set_tempconfig_to_pid(self, objs, device_table, requirements)
    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_target_temp_config)
    local tempconfig_buff

    -- 多个requirement可能对应一个area
    for _, tmp_requirement in pairs(requirements) do
        tempconfig_buff, total_len = add_target_policy_to_pid(objs, device_table, total_len, tmp_requirement)
        if tempconfig_buff then
            update_pid_cmd_info(buff_t, tempconfig_buff)
        end
    end
    fill_table_word(buff_t, cmd_len_offset, total_len - 2)
    fill_table_word(buff_t, total_len_offset, total_len)
    local ret = self:CustomSetTargetTempConfig(total_len, buff_t)
    log:notice("Send target tempconfig to pid, cmd: " .. table.concat(buff_t, ' ', 0))
    if ret ~= 0 then
        log:error("Set target tempconfig to pid failed, ret=%d", ret)
    end
end

-- writeinfo 07 拼接环温策略控制信息
local function add_env_control_info_to_pid(buff_t, policy_len, policy)
    policy_len = fill_table_byte(buff_t, policy_len, utils.size_of(policy.TemperatureRangeLow))
    for idx, _ in pairs(policy.TemperatureRangeLow) do
        if not policy.TemperatureRangeLow[idx] or not policy.TemperatureRangeHigh[idx] or
            not policy.SpeedRangeLow[idx] or not policy.SpeedRangeHigh[idx] then
            return nil
        end
        policy_len = fill_table_byte(buff_t, policy_len, policy.TemperatureRangeLow[idx])
        policy_len = fill_table_byte(buff_t, policy_len, policy.TemperatureRangeHigh[idx])
        policy_len = fill_table_byte(buff_t, policy_len, policy.SpeedRangeLow[idx])
        policy_len = fill_table_byte(buff_t, policy_len, policy.SpeedRangeHigh[idx])
    end
    return policy_len
end

-- writeinfo 07 单条环境调速策略配置信息
local function add_env_policy_to_pid(area, requirement_obj, device_table, total_len, policy)
    local buff_t = {}
    local policy_len = total_len
    local device_type = props.COOLING_AIR

    if policy.IsValid == 0 then
        return nil, total_len
    end

    local device_group = area.FanIdxGroup
    policy_len = fill_table_byte(buff_t, policy_len, 0) -- reserved byte
    policy_len = fill_table_byte(buff_t, policy_len, policy.Hysteresis)
    policy_len = fill_table_byte(buff_t, policy_len, requirement_obj[props.REQ_BASE_ID])
    policy_len = fill_table_byte(buff_t, policy_len, requirement_obj[props.REQ_SLOT])

    if policy.CoolingMedium == props.COOLING_LIQUID then
        device_group = area.LiquidCoolingDeviceGroup
        device_type = props.COOLING_LIQUID
    end

    -- 添加散热组信息
    policy_len = add_device_group_to_pid(device_type, buff_t, policy_len, device_table, device_group)
    if not policy_len then
        log:error('Add policy(id:%u) device group to pid error', policy.PolicyIdx)
        return nil, total_len
    end

    -- 添加环温控制信息
    policy_len = add_env_control_info_to_pid(buff_t, policy_len, policy)
    if not policy_len then
        log:error('Add policy(id:%u) envtemp control info to pid error', policy.PolicyIdx)
        return nil, total_len
    end

    return buff_t, policy_len
end

-- writeinfo 07 向PID模块设置环境温度调速配置
function cooling_pid_intf.cooling_set_envtempconfig_to_pid(self, data, device_table, requirements, policys)
    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local policy_buff

    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_envir_temp_config)
    for _, requirement_obj in pairs(requirements) do
        -- 温度点不生效则跳过
        if requirement_obj[props.IS_VALID] == 0 then
            goto continue
        end
        local req_base_id = requirement_obj[props.REQ_BASE_ID]
        -- 通过BaseId去找到当前的调速域
        local area_id = data.requirement_area_map[req_base_id]
        if not area_id then
            log:notice('The CoolingRequirement(0x%x) can not match CoolingArea(RequirementIdx:%s)',
                requirement_obj[props.REQUIREMENT_ID], req_base_id)
            goto continue
        end
        -- requirement_area_map和cooling_areas表来源于同一个对象，不需要判空
        local area_obj = data.cooling_areas[area_id]
        for _, policy_id in pairs(area_obj[props.POLICY_IDX_GROUP]) do
            local policy_o = policys[policy_id]
            -- 漏配置policy或者area中多配置了policy索引
            if not policy_o then
                log:error('The CoolingPolicy(PolicyIdx:%s) is not exist', policy_id)
                goto continuepolicy
            end
            policy_buff, total_len = add_env_policy_to_pid(area_obj, requirement_obj, device_table, total_len, policy_o)
            if policy_buff then
                update_pid_cmd_info(buff_t, policy_buff)
            end
            ::continuepolicy::
        end
        ::continue::
    end

    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    log:notice("[cooling] send envtemp config to pid, cmd: " .. table.concat(buff_t, ' ', 0))
    local ret = self:CustomSetEnvTempConfig(total_len, buff_t)
    if ret ~= 0 then
        log:error("cooling set envtemp config to pid failed, ret=%d", ret)
    end
end

-- writeinfo 02向PID模块设置风扇配置
function cooling_pid_intf.cooling_set_device_config_to_pid(self, device_table, max_speed, min_pwm)
    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_fan_config)
    for device_type, device_group in pairs(device_table) do
        log:notice("[Cooling] Added config device type %s to PID", device_type)
        for _, per_device in pairs(device_group) do
            if per_device.Id ~= nil and per_device.Slot ~= nil then
                total_len = fill_table_byte(buff_t, total_len, per_device.Id)
                total_len = fill_table_byte(buff_t, total_len, per_device.Slot)
                total_len = fill_table_byte(buff_t, total_len, per_device.Id)
                total_len = fill_table_word(buff_t, total_len, max_speed)
                total_len = fill_table_byte(buff_t, total_len, min_pwm)
            end
        end
    end
    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    log:notice("[cooling] send device config to pid, cmd: " .. table.concat(buff_t, ' ', 0))
    local ret = self:CustomSetFanConfig(total_len, buff_t)
    if ret ~= 0 then
        log:error("cooling set device config to pid failed, ret=%d", ret)
    end
end

-- writeinfo 03命令下发所有温度点信息发送给pid模块
function cooling_pid_intf.cooling_send_temp_to_pid(self, c_requirement, monitoring_value)
    if monitoring_value >= 255 then
        return
    end
    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_realtime_temp)

    total_len = fill_table_word(buff_t, total_len, 0) -- reversed
    total_len = fill_table_byte(buff_t, total_len, c_requirement[props.REQ_BASE_ID])
    total_len = fill_table_byte(buff_t, total_len, c_requirement[props.REQ_SLOT])
    -- monitoring_value 为double类型，四舍五入后下发
    total_len = fill_table_byte(buff_t, total_len, math.floor(monitoring_value + 0.5))

    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    if log:getLevel() >= log.DEBUG then
        log:debug("[cooling] send temp to pid, cmd: " .. table.concat(buff_t, ' ', 0))
    end
    local ret = self:CustomSetRealTimeTemp(total_len, buff_t)
    if ret ~= 0 then
        log:error("cooling send temp to pid failed, ret=%d", ret)
    end
end

-- writeinfo 05命令.一次发送所有的风扇告警转速给PID
function cooling_pid_intf.cooling_tell_almspd_to_pid(self, fan_alarm)
    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_fan_alarm_speed)

    for fan_pos, alarm_speed in pairs(fan_alarm) do
        total_len = fill_table_word(buff_t, total_len, fan_pos)
        total_len = fill_table_byte(buff_t, total_len, alarm_speed)
    end
    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    if log:getLevel() >= log.DEBUG then
        log:debug("[cooling] tell alarm speed to pid, cmd: " .. table.concat(buff_t, ' ', 0))
    end
    local ret = self:CustomSetFanAlarmSpeed(total_len, buff_t)
    if ret ~= 0 then
        log:error("cooling send temp to pid failed, ret=%d", ret)
    end
end

-- writeinfo 19命令. 发送所由requirement的SensorName和Fan前后Speed同步给PID
function cooling_pid_intf.cooling_sensor_name_to_pid(self, requirements)
    if not requirements then
        return
    end

    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_sensor_name_info)
    local total_requirment_len = total_len
    total_len = fill_table_word(buff_t, total_len, utils.size_of(requirements))
    local requirements_num = 0
    for _, single_requirement in pairs(requirements) do
        if single_requirement[props.IS_VALID] == 0 then
            goto continue
        end
        if type(single_requirement.SensorName) ~= "string" then
            goto continue
        end
        local sensor_name_len = string.len(single_requirement.SensorName)
        if sensor_name_len == 0 then
           goto continue
        end
        total_len = fill_table_byte(buff_t, total_len, single_requirement[props.REQ_BASE_ID])
        total_len = fill_table_byte(buff_t, total_len, single_requirement[props.REQ_SLOT])
        total_len = fill_table_byte(buff_t, total_len, sensor_name_len)
        for i = 1, sensor_name_len do
            total_len = fill_table_byte(buff_t, total_len, string.byte(string.sub(single_requirement.SensorName, i, i)))
        end
        requirements_num = requirements_num + 1
        ::continue::
    end
    if requirements_num == 0 then
        return
    end
    fill_table_word(buff_t, total_requirment_len, requirements_num)
    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    local ret = self:CustomSetSensorName(total_len, buff_t)
    if ret ~= 0 then
        log:error("cooling send sensor name to pid failed, ret=%s", ret)
    end
end

-- writeinfo 20命令. 添加风扇Speed
function cooling_pid_intf.cooling_fan_speed_to_pid(self, fan_devices)
    if not fan_devices then
        return
    end

    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_fan_speed_info)
    total_len = fill_table_byte(buff_t, total_len, utils.size_of(fan_devices))
    for _, device in pairs(fan_devices) do
        total_len = fill_table_byte(buff_t, total_len, device.Id)
        total_len = fill_table_word(buff_t, total_len, device.FrontSpeed)
        total_len = fill_table_word(buff_t, total_len, device.RearSpeed)
    end
    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    local ret = self:CustomSetFanSpeed(total_len, buff_t)
    if ret ~= 0 then
        log:error("cooling fan speed to pid failed, ret=%d", ret)
    end
end

function cooling_pid_intf.cooling_send_pid_control_mode(pid_control_mode)
    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_pid_control_mode)
    total_len = fill_table_byte(buff_t, total_len, pid_control_mode)
    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    local ret = libpid.WriteInfo(total_len, buff_t)
    log:notice("[cooling] send pid control mode to pid, cmd: " .. table.concat(buff_t, ' ', 0))
    if ret ~= 0 then
        log:error('cooling send pid control mode failed, ret = %s', ret)
    end
end

function cooling_pid_intf.cooling_send_pid_strategy(requirements)
    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_pid_strategy)
    local total_requirment_len = total_len
    total_len = fill_table_word(buff_t, total_len, utils.size_of(requirements))
    local requirements_num = 0
    local pid_p_strategy
    for _, single_requirement in pairs(requirements) do
        if single_requirement[props.IS_VALID] == 0 then
            goto continue
        end
        pid_p_strategy = pid_p_strategy_mapping[single_requirement[props.PID_P_STRATEGY]]
        if not pid_p_strategy then
            goto continue
        end
        total_len = fill_table_byte(buff_t, total_len, single_requirement[props.REQ_BASE_ID])
        total_len = fill_table_byte(buff_t, total_len, single_requirement[props.REQ_SLOT])
        total_len = fill_table_byte(buff_t, total_len, pid_p_strategy)
        total_len = fill_table_byte(buff_t, total_len, single_requirement[props.PID_I_ADJUST_TIME])
        total_len = fill_table_byte(buff_t, total_len, 0) -- 保留字段
        requirements_num = requirements_num + 1
        ::continue::
    end
    if requirements_num == 0 then
        return
    end
    fill_table_word(buff_t, total_requirment_len, requirements_num)
    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    local ret = libpid.WriteInfo(total_len, buff_t)
    log:notice("[cooling] send pid strategy pid, cmd: " .. table.concat(buff_t, ' ', 0))
    if ret ~= 0 then
        log:error('cooling send pid strategy failed, ret = %s', ret)
    end
end

function cooling_pid_intf.tell_fanspd_to_pid(device_table)
    local buff_t = {}
    local total_len_offset = 0
    local cmd_len_offset = 4
    local total_len = table_commmon_header(buff_t, writeinfo_cmd.type_fan_speed)
    for device_type, device_group in pairs(device_table) do
        if log:getLevel() >= log.DEBUG then
            log:debug("[Cooling] Added config device type %s to PID", device_type)
        end
        for _, per_device in pairs(device_group) do
            if per_device.Id ~= nil and per_device.Slot ~= nil then
                total_len = fill_table_byte(buff_t, total_len, per_device.Id)
                total_len = fill_table_byte(buff_t, total_len, per_device.Slot)
                total_len = fill_table_byte(buff_t, total_len, per_device.HardwarePWM)
            end
        end
    end
    fill_table_word(buff_t, total_len_offset, total_len)
    fill_table_word(buff_t, cmd_len_offset, total_len - SZ_WORD)
    if log:getLevel() >= log.DEBUG then
        log:debug("[cooling] send device actual pwm, cmd: " .. table.concat(buff_t, ' ', 0))
    end
    local ret = libpid.WriteInfo(total_len, buff_t)
    if ret ~= 0 then
        log:error("cooling device actual pwm, failed, ret = %s", ret)
    end
end

return cooling_pid_intf --返回注册的这个module
