-- 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 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 log = require 'mc.logging'
local power_mgmt_utils = require 'power_mgmt_utils'
local psu_def = require 'macros.psu_def'
local fw_def = require 'macros.fw_def'
local base_messages = require 'messages.base'

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:ctor(bus)
    self.bus = bus
    self.psu_obj = c_psu_object.collection.objects
    self.upgrade_flag = 0
    self.psu_slot = {}
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)
        log:notice("psm_get_black_box_info psu_id %s start", psu_id)
        if psu_id and psu_obj.ps_id ~= psu_id then
            return
        end
        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_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 = #self.psu_slot
    c_psu_object.collection:fold(function (_, obj)
        if not psu_objs[obj.ps_id] and obj:is_healthy() and obj.WorkMode ~= 'StandbySpare' then
            -- 健康非备用的电源（休眠的电源显示备用）
            ps_num = ps_num + 1
        end
    end)
    return ps_num * 2 >= 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

-- set_power_sleep_mode 设置睡眠模式
function psu_service:set_power_sleep_mode(slot_id, deep_sleep_enable)
    -- 当前场景是否支持休眠，先按整机正常工作的电源数判断
    if deep_sleep_enable ~= 'Normal' and not self:check_ps_num_exclude_ps({[slot_id] = 1}) then
        return E_FAILED
    end
    local psu_obj = c_psu_object.collection:find({SlotNumber = slot_id})
    if self.upgrade_flag ~= 1 and psu_obj then
        return psu_obj:set_sleep_mode(deep_sleep_enable)
    end
    return E_FAILED
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)
        psu_obj:psm_monitor_start()
    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:init()
end

return singleton(psu_service)