-- 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 props = require 'basic_cooling.define.cooling_properties'
local cooling_enums = require 'basic_cooling.define.cooling_enums'
local custom_msg = require 'messages.custom'
local utils = require 'basic_cooling.cooling_utils'
require "thermal_mgmt.json_types.CoolingConfig"
local cooling_mgmt = require 'basic_cooling.cooling_mgmt'
local fans_config = require 'basic_cooling.cooling_config.fans_config'
local air_config = require 'basic_cooling.cooling_config.air_config'
local privilege = require 'mc.privilege'
local cooling_fans = require 'basic_cooling.cooling_device.cooling_fans'
local cooling_policys = require 'basic_cooling.cooling_policys'
local cooling_requirements = require 'basic_cooling.cooling_requirememts'
local f_enum = require 'class.types.types'
local mc_utils = require 'mc.utils'
local singleton = require 'mc.singleton'

local cooling_rpc = class()

function cooling_rpc:ctor(service, db, data)
    self.service = service
    self.db = db
    self.data = data
    self.fans_instance = cooling_fans.get_instance()
    self.fans_config_instance = fans_config:get_instance()
    self.air_config_instance = air_config.get_instance()
    self.policys_instance = cooling_policys.get_instance()
    self.mgmt_instance = cooling_mgmt.get_instance()
    self.cooling_requirements_instance = cooling_requirements.get_instance()
end

function cooling_rpc:init()
    self:register_rpc()
end

function cooling_rpc:register_rpc()
    self.service:ImplCoolingConfigCoolingConfigSetSmartCoolingMode(function(obj, ctx, ...)
        return self:SetSmartCoolingMode(obj, ctx, ...)
    end)
    self.service:ImplCoolingConfigCoolingConfigSetMedium(function(obj, ctx, ...)
        return self:SetMedium(obj, ctx, ...)
    end)
    self.service:ImplCoolingConfigCoolingConfigSetManualLevel(function(obj, ctx, ...)
        return self:SetManualLevel(obj, ctx, ...)
    end)
    self.service:ImplCoolingConfigCoolingConfigSetCtrlMode(function(obj, ctx, ...)
        return self:SetCtrlMode(obj, ctx, ...)
    end)
    self.service:ImplCoolingConfigCoolingConfigGetCustomTargetTemperatures(function(obj, ctx, ...)
        return self:GetCustomTargetTemperatures(obj, ctx, ...)
    end)
    self.service:ImplCoolingPoliciesCoolingPoliciesGetSpecificPWM(function(obj, ctx, ...)
        return self.cooling_requirements_instance:GetSpecificPWM(...)
    end)
    self.service:ImplCoolingConfigCoolingConfigSetMinPWM(function(obj, ctx, ...)
        return self:SetMinPWM(obj, ctx, ...)
    end)
    self.service:ImplCoolingConfigCoolingConfigGetComponentThermalZone(function(obj, ctx, ...)
        return self:GetComponentThermalZone(obj, ctx, ...)
    end)
    self.service:ImplAirCoolingConfigAirCoolingConfigSetCtrlMode(function(obj, ctx, ...)
        return self:SetCtrlMode(obj, ctx, ...)
    end)
    self.service:ImplAirCoolingConfigAirCoolingConfigSetCtrlModePersist(function(obj, ctx, ...)
        return self:SetCtrlModePersist(obj, ctx, ...)
    end)
    self.service:ImplAirCoolingConfigAirCoolingConfigSetManualSpeedPercent(function(obj, ctx, ...)
        return self:SetManualLevel(obj, ctx, ...)
    end)
    self.service:ImplCoolingConfigCoolingConfigSetMinAllowedFanSpeedEnabled(function(obj, ctx, ...)
        return self:SetMinFanSpeedEnabled(obj, ctx, ...)
    end)
end

function cooling_rpc:SetSmartCoolingMode(obj, ctx, mode)
    local cooling_modes = cooling_enums.smart_cooling_mode
    local smartmode_dsp = {
        [cooling_modes.COOLING_ENERGY_SAVING_MODE] = 'energy saving', -- 节能模式
        [cooling_modes.COOLING_LOW_NOISE_MODE] = 'low noise', -- 低噪声模式
        [cooling_modes.COOLING_HIGH_PERFORMANCE_MODE] = 'high performance', -- 高性能模式
        [cooling_modes.COOLING_CUSTOM_MODE] = 'custom', -- 用户自定义模式
        [cooling_modes.COOLING_LIQUID_MODE] = 'liquid cooling' -- 液冷模式
    }

    local ok, err_info = self.fans_config_instance:set_smartcooling_mode(mode, cooling_enums.smart_cooling_changed.BOTH)
    if ok then
        utils.op(ctx, "Set smart cooling mode of thermal enforce policy to %s successfully", smartmode_dsp[mode])
    else
        utils.op(ctx, "Set smart cooling mode of thermal enforce policy to %s failed", smartmode_dsp[mode])
        error(err_info)
    end
end

function cooling_rpc:SetMedium(obj, ctx, medium)
    local ok, err_info = self.fans_config_instance:set_medium(medium)
    if ok then
        utils.op(ctx, "Set cooling medium to (%s) successfully", medium)
    else
        utils.op(ctx, "Set cooling medium to (%s) failed", medium)
        error(err_info)
    end
end

function cooling_rpc:SetManualLevel(obj, ctx, fan_id, level)
    local config_obj = self.fans_config_instance:get_obj()
    local ctrl_mode_persist_type = self.air_config_instance:get_ctrl_mode_persist_type()

    if fan_id ~= 0xff and not self.fans_instance.objs[fan_id] then
        log:error('The fan(%s) is not exist', fan_id)
        utils.op(ctx, 'Set fan(%u) level to (%u) failed', fan_id, level)
        error(custom_msg.PropertyValueOutOfRange(fan_id, '%UnitId'))
    end
    if level < config_obj[props.LEVEL_RANGE][1] or level > config_obj[props.LEVEL_RANGE][2] then
        log:error("The fan level(%s) is out of range, expect %s to %s",
            level, config_obj[props.LEVEL_RANGE][1], config_obj[props.LEVEL_RANGE][2])
        utils.op(ctx, 'Set fan(%u) level to (%u) failed', fan_id, level)
        error(custom_msg.PropertyValueOutOfRange(level, '%Level'))
    end

    if config_obj[props.FAN_CTL_ENABLE] ~= 'Enabled' then
        utils.op(ctx, "Redirect cmd to the other board to set fan level")
        return
    end
    local ok
    if fan_id == 0xff then
        ok = self.fans_config_instance:set_manual_level(level)
        if not ok then
            utils.op(ctx, "Set fan level to (%d) failed", level)
        else
            utils.op(ctx, "Set fan level to (%d) successfully", level)
        end
    else -- 手动模式下设置单个转速，将立即下发生效
        if not self.fans_instance:set_single_cooling_device_manual_level(fan_id, level, ctrl_mode_persist_type)
            or not self.mgmt_instance:set_fan_level_hw(fan_id, level) then
            log:error("Set fan(%d) level to (%d) failed", fan_id, level)
            utils.op(ctx, "Set fan(%d) level to (%d) failed", fan_id, level)
        else
            utils.op(ctx, "Set fan(%d) level to (%d) successfully", fan_id, level)
        end
    end
end

-- 设置为自动模式的时候,不能设置手动模式超时时间,Conflict抛出在北向
function cooling_rpc:SetCtrlMode(obj, ctx, mode, timeout)
    -- 风扇默认超时时间
    local FAN_DEFAULT_OUTTIME<const> = 30
    -- 手动模式最大超时时间
    local MAX_FAN_TIMEOUT<const> = 100000000
    local config_obj = self.fans_config_instance:get_obj()
    local mixed_mode_supported = config_obj[props.MIXED_MODE_SUPPORTED]
    if (mode ~= cooling_enums.modes.Manual and mode ~= cooling_enums.modes.Auto and
       mode ~= cooling_enums.modes.Mixed) or (not mixed_mode_supported and mode == cooling_enums.modes.Mixed) then
        utils.op(ctx, "Failed to set fan control mode to %s", mode)
        error(custom_msg.ValueOutOfRange('%CtrlMode'))
    end
    if not timeout then
        timeout = FAN_DEFAULT_OUTTIME
    elseif timeout == 0 then
        timeout = MAX_FAN_TIMEOUT
    elseif timeout > MAX_FAN_TIMEOUT then
        utils.op(ctx, "Failed to set fan control mode to %s", mode)
        error(custom_msg.PropertyValueOutOfRange(timeout, '%Timeout'))
    end
    if config_obj[props.FAN_CTL_ENABLE] ~= 'Enabled' then
        utils.op(ctx, "Redirect cmd to the other board to set fan mode")
        return
    end
    -- 如果是手动将当前config里面的手动模式转速属性设置给每个风扇
    self.mgmt_instance:set_fan_ctrl_mode(mode, timeout)
    if mode == cooling_enums.modes.Manual then
        utils.op(ctx, "Set fan mode to (manual) and its expiry time to (%u) seconds successfully", timeout)
    elseif mode == cooling_enums.modes.Mixed then
        utils.op(ctx, "Set fan mode to (mixed) successfully")
    else 
        utils.op(ctx, "Set fan mode to (auto) successfully")
    end
end

function cooling_rpc:SetCtrlModePersist(obj, ctx, mode, ctrl_mode_persist_type, timeout)
    if not self.air_config_instance.object_from_sr then
        utils.op(ctx, "Failed to set fan control mode persist, currently not supported")
        error("Failed to set fan control mode persist, currently not supported")
    end
    -- 风扇默认超时时间
    local FAN_DEFAULT_OUTTIME<const> = 30
    -- 手动模式最大超时时间
    local MAX_FAN_TIMEOUT<const> = 100000000
    local config_obj = self.fans_config_instance:get_obj()
    local mixed_mode_supported = config_obj[props.MIXED_MODE_SUPPORTED]
    -- 持久化类型校验
    if ctrl_mode_persist_type ~= cooling_enums.persist_type.Memory and 
        ctrl_mode_persist_type ~= cooling_enums.persist_type.ResetPer and
        ctrl_mode_persist_type ~= cooling_enums.persist_type.PoweroffPer then
        utils.op(ctx, "Failed to set fan control mode to %s, persist type is %s", mode, ctrl_mode_persist_type)
        error(custom_msg.ValueOutOfRange('%CtrlModePersistType'))
    end
    -- 风扇模式校验
    if (mode ~= cooling_enums.modes.Manual and mode ~= cooling_enums.modes.Auto and
       mode ~= cooling_enums.modes.Mixed) or (not mixed_mode_supported and mode == cooling_enums.modes.Mixed) then
        utils.op(ctx, "Failed to set fan control mode to %s", mode)
        error(custom_msg.ValueOutOfRange('%CtrlMode'))
    end
    -- 超时时间校验
    if ctrl_mode_persist_type ~= cooling_enums.persist_type.Memory then
        timeout = MAX_FAN_TIMEOUT
    elseif not timeout then
        timeout = FAN_DEFAULT_OUTTIME
    elseif timeout == 0 then
        timeout = MAX_FAN_TIMEOUT
    elseif timeout > MAX_FAN_TIMEOUT then
        utils.op(ctx, "Failed to set fan control mode to %s, timeout is %s", mode, timeout)
        error(custom_msg.PropertyValueOutOfRange(timeout, '%Timeout'))
    end
    if config_obj[props.FAN_CTL_ENABLE] ~= 'Enabled' then
        utils.op(ctx, "Redirect cmd to the other board to set fan mode")
        return
    end
    -- 如果是手动将当前config里面的手动模式转速属性设置给每个风扇
    self.mgmt_instance:set_fan_ctrl_mode_persist_type(ctrl_mode_persist_type)
    self.mgmt_instance:set_fan_ctrl_mode_persist(mode, timeout, ctrl_mode_persist_type)
    if mode == cooling_enums.modes.Manual then
        utils.op(ctx, "Set fan mode to (manual) and its expiry time to (%u) seconds successfully, persist type is %s",
            timeout, ctrl_mode_persist_type)
    elseif mode == cooling_enums.modes.Mixed then
        utils.op(ctx, "Set fan mode to (mixed) successfully persist type is %s", ctrl_mode_persist_type)
    else
        utils.op(ctx, "Set fan mode to (auto) successfully persist type is %s", ctrl_mode_persist_type)
    end
end

-- 获取自定义目标温度点
function cooling_rpc:GetCustomTargetTemperatures(obj, ctx)
    return self.cooling_requirements_instance:get_custom_target_temperatures()
end

function cooling_rpc:SetMinPWM(obj, ctx, DeviceType, DeviceArray, DeviceSpeedArray)
    local ok, err_info =
        pcall(self.mgmt_instance.set_rpc_pwm_list, self.mgmt_instance, DeviceType, DeviceArray, DeviceSpeedArray)
    if not ok then
        if log:getLevel() >= log.DEBUG then
            log:debug('Set %s min cooling pwm failed', DeviceType)
        end
        error(err_info)
    end
    local device_arr = {string.byte(DeviceArray, 1, #DeviceArray)}
    local speed_arr = {string.byte(DeviceSpeedArray, 1, #DeviceSpeedArray)}
    if log:getLevel() >= log.DEBUG then
        log:debug('Set %s cooling pwm successfully, device array[%s], speed array[%s]',
            DeviceType, table.concat(device_arr, ' '), table.concat(speed_arr, ' '))
    end
end

local temperature_map = {
    ['CPU'] = f_enum.TemperatureType.Cpu:value(),
    ['NPU'] = f_enum.TemperatureType.NPUAiCore:value(),
    ['MEMORY'] = f_enum.TemperatureType.Memory:value()
}

function cooling_rpc:GetComponentThermalZone(obj, ctx, ComponentType)
    if not temperature_map[ComponentType] then
        error(custom_msg.ValueOutOfRange('%ComponentType'))
    end
    local pump_ids = self.cooling_requirements_instance:get_pump_idx_group()
    local requirement_objs = self.cooling_requirements_instance:get_objs_by_temp_type(temperature_map[ComponentType])
    local ret = {}
    local slot, fan_ids
    local str_pattern = ComponentType .. "(%d+)"
    for _, requirement_obj in pairs(requirement_objs or {}) do
        slot = tonumber(string.match(requirement_obj.SensorName, str_pattern)) or
            tonumber(string.match(requirement_obj.SensorName, "%d+"))
        if not slot then
            goto continue
        end
        fan_ids = self.cooling_requirements_instance:get_fan_idx_group_by_areaId(requirement_obj[props.REQ_BASE_ID])
        local item = {}
        item.Slot = slot
        item.FanIds = mc_utils.table_copy(fan_ids)
        item.PumpIds = pump_ids
        table.insert(ret, item)
        ::continue::
    end
    return ret
end

function cooling_rpc:SetMinFanSpeedEnabled(obj, ctx, value)
    -- 内部组件调用不记录操作日志
    self.fans_config_instance:set_min_speed_enabled(value)
    local pwm = self.fans_config_instance:get_min_speed_percent()
    log:notice('Set min fan speed percent enbaled to %s, min pwm:%s', value, pwm)
    utils.op(ctx, "Set min fan speed percent enbaled to %s", value)
end

return singleton(cooling_rpc)