-- 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 log = require 'mc.logging'
local c_psu_object = require 'device.psu'
local c_power_configuration = require 'device.power_configuration'
local class = require 'mc.class'
local singleton = require 'mc.singleton'
local skynet = require 'skynet'
local utils = require 'mc.utils'
local power_mgmt_utils = require 'power_mgmt_utils'
local power_supplies = require 'power_supplies_service'
local psu_def = require 'macros.psu_def'
local fw_def = require 'macros.fw_def'
local custom_msg = require 'messages.custom'
local base_msg = require 'messages.base'
local enums = require 'macros.power_mgmt_enums'
local c_psu_slot_object = require 'device.psu_slot'

local E_OK <const> = nil -- 函数执行成功返回nil
local E_FAILED <const> = '' -- 空错误信息

local MAX_PSU_FAN_SPEED_RPM<const> = 30000 -- 默认psu风扇最大转速为30000
local MAX_PSU_FAN_LEVEL_PERCENT<const> = 100

local psu_service = class()

function psu_service:add_psu_object(class_name, object)
    if self[utils.camel_to_snake(class_name)] then
        table.insert(self[utils.camel_to_snake(class_name)], object)
    end
end

function psu_service:delete_psu_slot_object(class_name, object)
    local tmp_psu_slot = {}
    c_psu_slot_object.collection:fold(function (_, psu_slot_obj)
        table.insert(tmp_psu_slot, psu_slot_obj)
    end)
    self.psu_slot = tmp_psu_slot
    log:notice('Refresh psu slot count to %s', #self.psu_slot)
end

function psu_service:ctor(bus)
    self.bus = bus
    self.psu_obj = c_psu_object.collection.objects
    self.upgrade_flag = 0
    self.psu_slot = {}
    self.power_latch = 0 --0：未锁存， 1：锁存
    self.psu_reset_flag = psu_def.CAN_RESET
end

-- 依次下发每个电源风扇转速
function psu_service:set_psus_fan_min_pwm(speed_percent)
    speed_percent = speed_percent > MAX_PSU_FAN_LEVEL_PERCENT and MAX_PSU_FAN_LEVEL_PERCENT or speed_percent
    local write_ret = nil
    local exp_speed_rpm = speed_percent * MAX_PSU_FAN_SPEED_RPM // 100
    local retry_times = 3
    local func = function (psu_obj)
        if self.upgrade_flag == 1 then
            return
        end
        for _ = 1, retry_times, 1 do
            write_ret = psu_obj:set_psu_fan_rpm(exp_speed_rpm)
            -- 写入成功则退出当前电源
            if not write_ret then
                return
            end
        end
        log:error('Set psu(%s) fan speed rpm to %s failed', psu_obj.ps_id, exp_speed_rpm)
    end
    -- 下发转速
    c_psu_object.collection:fold(function (_, obj)
        func(obj)
    end)
end

-- psm_get_black_box_info 获取电源黑匣子信息
function psu_service:psm_get_black_box_info(psu_id)
    local black_box_info = {}
    log:notice("get_black_box_info start")
    self:psm_monitor_stop()
    local ret, black_box_max_length, black_box_data
    c_psu_object.collection:fold(function (_, psu_obj)
        if psu_id and psu_obj.ps_id ~= psu_id then
            return
        end
        log:notice("psm_get_black_box_info psu_id %s start", psu_obj.ps_id)
        ret, black_box_max_length, black_box_data = psu_obj:get_black_box_data()
        if ret == E_OK then
            black_box_info[psu_obj.ps_id] = {
                serial_number = psu_obj.SerialNumber,
                black_box_data = black_box_data,
                black_box_max_length = black_box_max_length
            }
        end
        skynet.sleep(10)
        log:info('SerialNumber %s black_box_max_length %s', psu_obj.SerialNumber, black_box_max_length)
        log:notice("psm_get_black_box_info psu_id %s end", psu_obj.ps_id)
    end)
    self:psm_monitor_start()
    log:notice("get_black_box_info end")
    return black_box_info
end

function psu_service:check_ps_num_exclude_ps(psu_objs)
    if not psu_objs then
        psu_objs = {}
    end
    -- psu_objs为map,key为ps_id
    if type(psu_objs) ~= 'table' then
        return false
    end
    -- 默认认为电源配置为N+N冗余，有N个电源就能保证整机的正常工作
    -- 整机最大电源数目为psu_slot数
    local ps_num = 0
    local tmp_supposed_ps_num = 0
    local power_configuration = c_power_configuration.collection:find({})
    if not power_configuration then
        -- 未配置PowerConfiguration对象，默认认为电源配置为N+N冗余
        tmp_supposed_ps_num = math.ceil(#self.psu_slot / 2)
    else
        -- 配置了PowerConfiguration对象，获取电源升级时需要的最小在位的健康电源个数
        tmp_supposed_ps_num = power_configuration:get_min_allowed_psu_count(#self.psu_slot)
    end
    c_psu_object.collection:fold(function (_, obj)
        -- not psu_objs[obj.ps_id]代表非正在升级的电源，obj.IsPartOfSysPower代表参与整机功耗计算的电源
        -- 健康非备用的电源（休眠的电源显示备用）
        if not psu_objs[obj.ps_id] and obj:is_healthy() and obj.WorkMode ~= 'StandbySpare'
            and obj.IsPartOfSysPower then
            ps_num = ps_num + 1
        end
    end)
    log:notice("tmp_supposed_ps_num %s, ps_num %s", tmp_supposed_ps_num, ps_num)
    return ps_num >= tmp_supposed_ps_num
end

-- set_power_work_mode 设置工作模式
function psu_service:set_power_work_mode(slot_id, work_mode)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if self.upgrade_flag ~= 1 and psu_obj then
        return psu_obj:set_work_mode(work_mode)
    end
    return E_FAILED
end

function psu_service:is_support_cabinet_sleep_mode(ps_id)
    local input_power_watts_consumption = 0 -- 所有非休眠模式电源的输入功耗总和
    local input_rated_power_watts_list = {} -- 所有剩余非休眠模式额定功耗列表
    c_psu_object.collection:fold(function (_, psu_obj)
        if psu_obj:is_healthy() then -- 正常的电源进行计算
            if psu_obj.ps_id ~= ps_id then
                table.insert(input_rated_power_watts_list, psu_obj.Rate)
            end
            input_power_watts_consumption = input_power_watts_consumption + psu_obj.InputPowerWatts
        end
    end)
    if input_power_watts_consumption == 0 or not next(input_rated_power_watts_list) or
        #input_rated_power_watts_list <= enums.MIN_NUM_OF_PSU_FOR_POWER_SUPPLY then
        return false
    end
    -- 剩余非休眠模式额定功耗列表中去掉2个最大的额定功耗
    table.sort(input_rated_power_watts_list, function(a, b)
        return a > b
    end)
    table.remove(input_rated_power_watts_list, 1)
    table.remove(input_rated_power_watts_list, 1)
    local input_rated_power_watts_consumption = power_mgmt_utils.get_instance():sum(input_rated_power_watts_list)
    if input_rated_power_watts_consumption and input_rated_power_watts_consumption >= input_power_watts_consumption then
        return true
    end
    return false
end

-- 检查是否存在其他的正常电源
function psu_service:has_other_healthy_psu(ps_id)
    local has_other_healthy_psu_flag = false
    c_psu_object.collection:fold(function(_, psu_obj)
        if psu_obj.ps_id ~= ps_id then
            if psu_obj:is_healthy() then
                log:notice('find other psu%d, is healthy', psu_obj.ps_id)
                has_other_healthy_psu_flag = true
            else
                log:notice('find other psu%d, is not healthy', psu_obj.ps_id)
            end
        end
    end)

    return has_other_healthy_psu_flag
end

-- set_power_sleep_mode 设置睡眠模式
function psu_service:set_power_sleep_mode(slot_id, deep_sleep_enable)
    if deep_sleep_enable ~= enums.SLEEP_MODE.DEEP_SLEEP_STR and deep_sleep_enable ~= enums.SLEEP_MODE.NORMAL_STR then
        return enums.SLEEP_MODE_RELATED_ERROR.PROPERTY_VALUE_OUT_OF_RANGE
    end
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if not psu_obj then
        return enums.SLEEP_MODE_RELATED_ERROR.PROPERTY_VALUE_OUT_OF_RANGE
    end
    local software_type = power_mgmt_utils.get_instance():get_chassis_type()
    local is_support_set_sleep_mode = true
    if not software_type then
        log:error('Get software type failed')
        return enums.SLEEP_MODE_RELATED_ERROR.OPERATION_FAILED
    elseif software_type == enums.SOFTWARE_TYPE.CABINET then
        if deep_sleep_enable == psu_obj.SleepMode then
            return E_OK
        end
        is_support_set_sleep_mode = deep_sleep_enable == enums.SLEEP_MODE.NORMAL_STR and true or
            self:is_support_cabinet_sleep_mode()
    else
        -- 通用场景是否支持休眠，先按整机正常工作的电源数判断
        if deep_sleep_enable ~= 'Normal' and not self:check_ps_num_exclude_ps({[slot_id] = 1}) then
            is_support_set_sleep_mode = false
        end
    end
    if not is_support_set_sleep_mode then
        return enums.SLEEP_MODE_RELATED_ERROR.ACTION_NOT_SUPPORTED
    end
    if self.upgrade_flag ~= 1 then
        return psu_obj:set_sleep_mode(deep_sleep_enable)
    end
    return enums.SLEEP_MODE_RELATED_ERROR.OPERATION_FAILED
end

function psu_service:set_power_reset_type(slot_id, reset_type)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    local ret
    if psu_obj then
        if reset_type == "ForceOff" and not self:is_support_cabinet_sleep_mode(slot_id) then
            return custom_msg.ResetOperationNotAllowed('%ResetType')
        end
        if reset_type == "ForceRestart" and 
            (not self:has_other_healthy_psu(slot_id) or self.psu_reset_flag == psu_def.CAN_NOT_RESET) then
            return custom_msg.ResetOperationNotAllowed('%ResetType')
        end
        ret = psu_obj:reset(reset_type)
        
        if ret == E_OK then
            -- ret不为空说明复位成功，60秒内禁止再复位PSU，防止同时复位多个PSU导致环境掉电
            log:notice('psu_reset_flag is set to CAN_NOT_RESET')
            self.psu_reset_flag = psu_def.CAN_NOT_RESET
            skynet.fork(function()
                skynet.sleep(6000)
                self.psu_reset_flag = psu_def.CAN_RESET
                log:notice('psu_reset_flag is set to CAN_RESET')
            end)
        end
        return ret
    else
        return base_msg.InternalError()
    end
end

function psu_service:set_psu_alarm_latch(slot_id, switch_status)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if psu_obj then
        return psu_obj:set_alarm_latch(switch_status)
    else
        return base_msg.InternalError()
    end
end

function psu_service:set_power_supply_circuit(slot_id, circuit)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if psu_obj then
        return psu_obj:set_power_supply_circuit(circuit)
    else
        return base_msg.InternalError()
    end
end

function psu_service:set_power_capacitor_enable(slot_id, enable)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if psu_obj then
        return psu_obj:set_power_capacitor_enable(enable)
    else
        return base_msg.InternalError()
    end
end

function psu_service:set_output_limit_point_watts(slot_id, output_power_limit_watts)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if psu_obj then 
            return psu_obj:set_output_limit_point_watts(output_power_limit_watts)
    end
    return base_msg.InternalError()
end

function psu_service:set_vout_debounce_milliseconds(slot_id, debounce_milliseconds)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if psu_obj then
        return psu_obj:set_vout_debounce_milliseconds(debounce_milliseconds)
    end
    return base_msg.InternalError()
end

function psu_service:set_depth_of_discharge_volts(slot_id, depth_of_discharge_volts)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if psu_obj then
        return psu_obj:set_depth_of_discharge_volts(depth_of_discharge_volts)
    end
    return base_msg.InternalError()
end

function psu_service:psm_monitor_stop()
    log:info("psm_monitor_stop")
    c_psu_object.collection:fold(function (_, psu_obj)
        psu_obj:psm_monitor_stop()
    end)
end

function psu_service:psm_monitor_start()
    log:info("psm_monitor_start")
    c_psu_object.collection:fold(function (_, psu_obj)
        skynet.fork_once(function ()
            psu_obj:psm_monitor_start()
        end)
    end)
end

function psu_service:get_psu_object(intf_obj)
    return c_psu_object.collection:find(function(object)
        return object.path == intf_obj.path
    end)
end

function psu_service:get_mfr_reg_value(slot_id)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if psu_obj then
        local mfr_status, err_msg = psu_obj:get('specific')
        if err_msg == E_OK then
            return mfr_status
        else
            log:error(err_msg)
        end
    end
    return nil
end

function psu_service:get_psu_property(slot_id, prop)
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if not psu_obj then
        return
    end
    return psu_obj[prop]
end

local function dump_table_info(fp_w, depth, key, value)
    local dump_msg = string.format('%s| %s:', string.rep("-", 2 * depth), key)
    if depth == 0 then
        local info_str = string.format('%s\n', dump_msg)
        fp_w:write(info_str)
        for _, prop_name in pairs(value:get_all_prop_names()) do
            dump_table_info(fp_w, depth + 1, prop_name, value[prop_name])
        end
        return
    end
    if depth > 3 then
        return
    end
    local value_type = type(value)
    local value_str = ''
    if value_type ~= 'table' then
        if value_type == 'string' or value_type == 'number' or value_type == 'boolean' then
            value_str = tostring(value)
            local info_str = string.format('%s       %s\n', dump_msg, value_str)
            fp_w:write(info_str)
        end
    else
        if value['removed_handles'] ~= nil then
            return
        end
        local info_str = string.format('%s\n', dump_msg)
        fp_w:write(info_str)
        for sub_key, sub_value in pairs(value) do
            dump_table_info(fp_w, depth + 1, sub_key, sub_value)
        end
    end
end

function psu_service:dump_obj_infos(fp_w)
    fp_w:write('\n\n')
    c_psu_object.collection:fold(function (_, psu_obj)
        fp_w:write(string.format('ps%d info\n', psu_obj.ps_id))
        dump_table_info(fp_w, 0, psu_obj.ObjectName, psu_obj)
        skynet.sleep(500)
    end)
    fp_w:write('\n\n')
end

--支持功率锁存的板子在这个任务刷新功率
function psu_service:set_power_latch()
    local ret = E_OK
    local exit_flag = 0
    while true do
        skynet.sleep(100) --一秒锁存一次，槽位号读取功率后自动解锁存
        --遍历电源，第一个在位电源设置功率锁存
        c_psu_object.collection:fold(function (_, psu_obj)
            if self.power_latch == 1 then
                return
            end
            self.power_latch = 1
            ret = psu_obj:set_psu_power_latch() --只需要下发功率锁存，电源读取功率后会自动解锁存
            if ret == E_FAILED then
                exit_flag = 1 --正常功率多次不会返回错误，接口返回错误说明不支持锁存，任务退出
            end
        end)
        self.power_latch = 0
        if exit_flag == 1 then
            return
        end
    end
end

-- 定时检查每个电源双输入供电故障恢复回切时间
function psu_service:set_retransfer_delay_seconds()
    local software_type = power_mgmt_utils.get_instance():get_chassis_type()
    if software_type ~= enums.SOFTWARE_TYPE.CABINET then
        log:notice('software_type is %s, not support retransfer delay seconds', software_type)
        return
    end

    log:notice('set_retransfer_delay_seconds task is start.')
    while true do
        skynet.sleep(6000)
        local func = function(psu_obj)
            local ok, second = pcall(function ()
                return psu_obj:get_retransfer_delay_seconds()
            end)
            if not ok then
                log:debug('Get psu(%s) retransfer delay seconds failed', psu_obj.ps_id)
                goto continue
            end
            local cur_second = power_supplies.get_instance():get_retransfer_delay_seconds()
            if second ~= cur_second then
                psu_obj:set_retransfer_delay_seconds(cur_second)
            end

            ::continue::
        end

        c_psu_object.collection:fold(function(_, obj)
            func(obj)
        end)
    end
end                                                                                                                                                                                            

function psu_service:init()
    skynet.fork(
        function()
            self:set_power_latch()
        end
    )

    skynet.fork(
        function()
            self:set_retransfer_delay_seconds()
        end
    )
end

return singleton(psu_service)