-- 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.

-- 天成超级节点 qb900和qb900max
-- 不支持设置冷休眠，设置电源主备模式，设置风扇转速
-- 轮询间隔时间原来3倍

local class = require 'mc.class'
local utils = require 'device_tree.adapters.power_mgmt.utils'
local utils_core = require 'utils.core'
local skynet = require 'skynet'
local ctx = require 'mc.context'
local log = require 'mc.logging'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local protocol = require 'device_tree.adapters.power_mgmt.protocol'
local fw_def = require 'device_tree.adapters.power_mgmt.protocol.upgrade.fw_def'
local match_rule = org_freedesktop_dbus.MatchRule
local debounce = require 'mc.debounce.debounce'
local enums = require 'macros.power_mgmt_enums'

local REMOTE_POWER_MONITOR_SIG_PATH<const> = '/bmc/kepler/power_mgmt/psu'

local TIME_SERVICE_INTERVAL<const> = 1000 * 60 * 5
local IS_NOT_HEALTH<const> = 0xff
local HEALTH_EVENT_READ_FAIL_DEBOUNCE_COUNT <const> = 20
local E_OK = nil -- 函数执行成功返回nil
local E_FAILED = '' -- 空错误信息
local HEALTH_UPDATE_FAIL <const> = 0
local POWER_WATTS_UPDATE_FAIL <const> = 1
local PLUGIN_DEBOUNCE <const> = 2
-- 电源通讯异常初始值
local COMMUNICATION_STATUS_DEFAULT <const> = -1
local STATUS_WORD <const> = 0
local STATUS_VOUT <const> = 1
local STATUS_IOUT <const> = 2
local STATUS_INPUT <const> = 3
local STATUS_TEMPERATURE <const> = 4
local STATUS_CML <const> = 5
local INVALID_STATUS <const> = -1

local function get_pbit(val)
    return 1 << val
end

return function (obj)
    local psu_slot = class(obj)
    -- powerconverter
    function psu_slot:ctor(protocol)
        self.protocol = protocol
        self.remote_power_monitor_task = false
        self.read_fail_count = 0
        self.remote_powe_signal = false
        self.last_read_interval = 10
        self.read_interval = 100 * 3
        self.monitor_tasks = {}
        self.comm_status = {
            [HEALTH_UPDATE_FAIL] = COMMUNICATION_STATUS_DEFAULT,
            [POWER_WATTS_UPDATE_FAIL] = COMMUNICATION_STATUS_DEFAULT,
            [PLUGIN_DEBOUNCE] = debounce[debounce.DEBOUNCED_CONT_BIN].new(3, 1, 0)
        }
        self.last_health_status = {
            [STATUS_WORD] = 0,
            [STATUS_VOUT] = 0,
            [STATUS_IOUT] = 0,
            [STATUS_INPUT] = 0,
            [STATUS_TEMPERATURE] = 0,
            [STATUS_CML] = 0
        }
        self.health_status = {
            [STATUS_WORD] = 0,
            [STATUS_VOUT] = 0,
            [STATUS_IOUT] = 0,
            [STATUS_INPUT] = 0,
            [STATUS_TEMPERATURE] = 0,
            [STATUS_CML] = 0,
            failed_flag = false
        }
        self.health_status_debounce = {
            [STATUS_WORD] = debounce[debounce.DEBOUNCED_CONT].new(3, INVALID_STATUS),
            [STATUS_VOUT] = debounce[debounce.DEBOUNCED_CONT].new(3, INVALID_STATUS),
            [STATUS_IOUT] = debounce[debounce.DEBOUNCED_CONT].new(3, INVALID_STATUS),
            [STATUS_INPUT] = debounce[debounce.DEBOUNCED_CONT].new(3, INVALID_STATUS),
            [STATUS_TEMPERATURE] = debounce[debounce.DEBOUNCED_CONT].new(3, INVALID_STATUS),
            [STATUS_CML] = debounce[debounce.DEBOUNCED_CONT].new(3, INVALID_STATUS)
        }
    end

    function psu_slot:is_present()
        return self.Presence == 1
    end

    function psu_slot:stop_monitor_tasks()
        local ts = self.monitor_tasks
        self.monitor_tasks = {}
        for _, task in pairs(ts) do
            task:stop()
        end
    end

    function psu_slot:psm_monitor_stop()
        self:stop_monitor_tasks()
        self:stop_remote_power_monitor()
    end

    function psu_slot:fetch_power_supply_info()
        self:get_power_supply_info('manufacturer')
        self:get_power_supply_info('model')
        self:get_power_supply_info('production_date')
        self:get_power_supply_info('product_version')
        self:get_power_supply_info('firmware_version')
        self:get_power_supply_info('serial_number')
        self:get_power_supply_info('part_number')
    end

    function psu_slot:remote_power_monitor()
        if self.remote_power_monitor_task and self.read_interval == self.last_read_interval then
            return true
        end
        local chip = self.PsuChip[enums.PSU_CHIP_OBJECT.BLOCKIO]
        local ok, resp = pcall(function ()
            local args = skynet.packstring(self.ps_id, REMOTE_POWER_MONITOR_SIG_PATH .. self.ps_id, self.read_interval)
            return skynet.unpack(chip:PluginRequest(ctx.new(), 'power_mgmt', "start_powerconverter_power_monitor", args))
        end)
        if not ok then
            local debounced_val = self.comm_status[PLUGIN_DEBOUNCE]:get_debounced_val(1)
            if debounced_val == 1 then
                self:process_communication_status(POWER_WATTS_UPDATE_FAIL, 1)
            end
            return
        end
        self.comm_status[PLUGIN_DEBOUNCE]:get_debounced_val(0)
        log:notice('start psu power monitor remote task, ps_id=%s, task_id=%s', self.ps_id, resp)
        self.last_read_interval = self.read_interval
        self.remote_power_monitor_task = resp
    end

    function psu_slot:start_to_receive_remote_power()
        local sig = match_rule.signal('UpdateProps'):with_path_namespace(REMOTE_POWER_MONITOR_SIG_PATH .. self.ps_id)
        self.remote_powe_signal = self.bus:match(sig, function(message)
            local _, need_update_vlaue = message:read('a{ss}a{sv}')
            for prop, val in pairs(need_update_vlaue) do
                if prop == 'communication_status' then
                    self:process_communication_status(POWER_WATTS_UPDATE_FAIL, val)
                else
                    log:debug("update %s to %s", prop, val)
                    self:dal_refresh_property(prop, val)
                    self.scan_status = self.scan_status | get_pbit(1)
                    self:dal_refresh_property('scan_status', self.scan_status)
                end
            end
        end)
    end

    function psu_slot:stop_remote_power_monitor()
        log:notice('stop psu power monitor remote task, ps_id=%s, monitor task_id=%s',
            self.ps_id, self.remote_power_monitor_task)
        local args = skynet.packstring(self.remote_power_monitor_task)
        self.PsuChip[enums.PSU_CHIP_OBJECT.BLOCKIO]:PluginRequest(ctx.new(), 'power_mgmt', "stop_powerconverter_power_monitor", args)

        self.remote_power_monitor_task = false
    end

    function psu_slot:process_communication_status(err_type, flag)
        self.comm_status[err_type] = flag
        if err_type == POWER_WATTS_UPDATE_FAIL and flag == 1 then
            -- 电源通讯丢失，设置电源输入功耗扫描状态位为0
            self.scan_status = self.scan_status & ~get_pbit(1)
            self:dal_refresh_property('scan_status', self.scan_status)
            self:dal_refresh_property('input_power_watts', 0)
        end
        if self.comm_status[HEALTH_UPDATE_FAIL] == COMMUNICATION_STATUS_DEFAULT or
            self.comm_status[POWER_WATTS_UPDATE_FAIL] == COMMUNICATION_STATUS_DEFAULT then
            return
        end

        if self:is_present() and self.comm_status[HEALTH_UPDATE_FAIL] == 1 and
            self.comm_status[POWER_WATTS_UPDATE_FAIL] == 1 then
            log:debug('update communication_status to 1')
            self:dal_refresh_property('communication_status', 1)
        else
            log:debug('update communication_status to 0')
            self:dal_refresh_property('communication_status', 0)
        end
    end

    function psu_slot:record_maintenance_log(health_event)
        if health_event.input_voltage_fault ~= self.last_health_status[STATUS_INPUT] then
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'PSU%s input voltage fault status changed from %s to %s', self.ps_id,
                self.last_health_status[STATUS_INPUT], health_event.input_voltage_fault)
            self.last_health_status[STATUS_INPUT] = health_event.input_voltage_fault
        end
        if health_event.output_voltage_fault ~= self.last_health_status[STATUS_VOUT] then
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'PSU%s output voltage fault status changed from %s to %s', self.ps_id,
                self.last_health_status[STATUS_VOUT], health_event.output_voltage_fault)
            self.last_health_status[STATUS_VOUT] = health_event.output_voltage_fault
        end
        if health_event.temper_fault ~= self.last_health_status[STATUS_TEMPERATURE] then
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'PSU%s temper fault status changed from %s to %s', self.ps_id,
                self.last_health_status[STATUS_TEMPERATURE], health_event.temper_fault)
            self.last_health_status[STATUS_TEMPERATURE] = health_event.temper_fault
        end
        if health_event.output_current_fault ~= self.last_health_status[STATUS_IOUT] then
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'PSU%s output current fault status changed from %s to %s', self.ps_id,
                self.last_health_status[STATUS_IOUT], health_event.output_current_fault)
            self.last_health_status[STATUS_IOUT] = health_event.output_current_fault
        end
        if health_event.cml_fault ~= self.last_health_status[STATUS_CML] then
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'PSU%s cml status changed from %s to %s', self.ps_id,
                self.last_health_status[STATUS_CML], health_event.cml_fault)
            self.last_health_status[STATUS_CML] = health_event.cml_fault
        end
        if health_event.word_fault ~= self.last_health_status[STATUS_WORD] then
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'PSU%s word fault status changed from %s to %s', self.ps_id,
                self.last_health_status[STATUS_WORD], health_event.word_fault)
            self.last_health_status[STATUS_WORD] = health_event.word_fault
        end
    end

    function psu_slot:get_health_event()
        -- 输入状态信息 7C
        local vin_status = self.health_status_debounce[STATUS_INPUT]:get_debounced_val(
            self.health_status[STATUS_INPUT])
        -- 温度状态信息 7D
        local temper_status = self.health_status_debounce[STATUS_TEMPERATURE]:get_debounced_val(
            self.health_status[STATUS_TEMPERATURE])
        -- 输出电压信息7A
        local vout_status = self.health_status_debounce[STATUS_VOUT]:get_debounced_val(
            self.health_status[STATUS_VOUT])
        -- 输出电流信息7B
        local iout_status = self.health_status_debounce[STATUS_IOUT]:get_debounced_val(
            self.health_status[STATUS_IOUT])
        -- 逻辑、通信、存储器状态信息7E
        local cml_status = self.health_status_debounce[STATUS_CML]:get_debounced_val(
            self.health_status[STATUS_CML])
        -- 双字节电源状态79
        local word_status = self.health_status_debounce[STATUS_WORD]:get_debounced_val(
            self.health_status[STATUS_WORD])
        if vin_status == INVALID_STATUS or temper_status == INVALID_STATUS or vout_status == INVALID_STATUS or
            iout_status == INVALID_STATUS or cml_status == INVALID_STATUS or word_status == INVALID_STATUS then
            return nil, self.health_status.failed_flag
        end
        local event = 0
        local health_event = {}
        health_event.input_voltage_fault = vin_status
        if vin_status & 0x90 > 0 then
            event = event | get_pbit(4)
        end
        health_event.temper_fault = temper_status
        if temper_status & 0xf0 > 0 then
            event = event | get_pbit(3)
        end
        health_event.output_voltage_fault = vout_status
        health_event.output_current_fault = iout_status
        health_event.cml_fault = cml_status
        health_event.word_fault = word_status
        health_event.event = event
        self:record_maintenance_log(health_event)
        return health_event, self.health_status.failed_flag
    end

    function psu_slot:update_health_event()
        local health_event, failed_flag = self:get_health_event()

        if failed_flag then
            self:dal_refresh_property('health', get_pbit(4))
            if self.read_fail_count < HEALTH_EVENT_READ_FAIL_DEBOUNCE_COUNT then
                self.read_fail_count = self.read_fail_count + 1
            else
                self:process_communication_status(HEALTH_UPDATE_FAIL, 1)
                self.scan_status = self.scan_status & ~get_pbit(0)
                self:dal_refresh_property('scan_status', self.scan_status)
            end
        else
            self.read_fail_count = 0
            self:process_communication_status(HEALTH_UPDATE_FAIL, 0)
            self.scan_status = self.scan_status | get_pbit(0)
            self:dal_refresh_property('scan_status', self.scan_status)
        end
        if health_event then
            self:dal_refresh_property('health', health_event.event)
            self:dal_refresh_property('health_event', health_event)
        end
        return E_OK
    end

    -- power_monitor 实际功率任务
    function psu_slot:power_monitor()
        if self:update_health_event() ~= E_OK then
            -- 接口调用失败， 获取不到属性，直接返回
            return E_FAILED
        end
        -- 开始监听 InputPowerWatts属性（频率100ms）
        self:remote_power_monitor()
        return E_OK
    end

    function psu_slot:psm_task_power()
        local ret, _ = self:get_dynamic_data('input_power_watts')
        if ret == E_OK then
            self.scan_status = self.scan_status | get_pbit(1)
            self:dal_refresh_property('scan_status', self.scan_status)
        end
        while true do
            self:power_monitor()
            self:sleep_ms(15000)
        end
    end

    -- 查询电源输入/输出 电流/电压 等信息
    function psu_slot:psm_task_v_i()
        local ret, rsp, _
        local read_fail_count = 0
        while true do
            rsp, _ = self:get_dynamic_data('output_power_watts')
            ret = ret or rsp
            self:sleep_ms(500)
            rsp, _ = self:get_dynamic_data('input_current_amps')
            ret = ret or rsp
            self:sleep_ms(500)
            rsp, _ = self:get_dynamic_data('output_current_amps')
            ret = ret or rsp
            self:sleep_ms(500)

            rsp, _ = self:get_dynamic_data('input_voltage')
            ret = ret or rsp
            self:sleep_ms(500)
            rsp, _ = self:get_dynamic_data('output_voltage')
            ret = ret or rsp
            self:sleep_ms(500)
            rsp, _ = self:get_dynamic_data('power_supply_type')
            ret = ret or rsp
            self:sleep_ms(500)
            rsp, _ = self:get_dynamic_data('env_temperature_celsius')
            ret = ret or rsp
            self:sleep_ms(500)
            rsp, _ = self:get_dynamic_data('primary_chip_temperature_celsius')
            ret = ret or rsp
            self:sleep_ms(500)
            rsp, _ = self:get_dynamic_data('secondary_chip_temperature_celsius')
            ret = ret or rsp

            if ret ~= E_OK then
                if read_fail_count < HEALTH_EVENT_READ_FAIL_DEBOUNCE_COUNT then
                    read_fail_count = read_fail_count + 1
                else
                    self.scan_status = self.scan_status & ~get_pbit(2)
                end
            else
                read_fail_count = 0
                self.scan_status = self.scan_status | get_pbit(2)
            end
            self:dal_refresh_property('scan_status', self.scan_status)
            ret = nil
            self:sleep_ms(15000)
        end
    end

    function psu_slot:time_service_task()
        while true do
            self.protocol:time_service()
            self:sleep_ms(TIME_SERVICE_INTERVAL)
        end
    end

    -- 更新电源输入状态
    function psu_slot:update_status_input()
        local ok, status = pcall(function()
            return self.protocol:get_status_input()
        end)
        if ok then
            self.health_status[STATUS_INPUT] = status
            return false, status ~= 0
        else
            return true, false
        end
    end

    -- 更新电源输出电压状态
    function psu_slot:update_status_vout()
        local ok, status = pcall(function()
            return self.protocol:get_status_vout()
        end)
        if ok then
            self.health_status[STATUS_VOUT] = status
            return false, status ~= 0
        else
            return true, false
        end
    end

    -- 更新电源输出电流状态
    function psu_slot:update_status_iout()
        local ok, status = pcall(function()
            return self.protocol:get_status_iout()
        end)
        if ok then
            self.health_status[STATUS_IOUT] = status
            return false, status ~= 0
        else
            return true, false
        end
    end

    -- 更新电源温度状态
    function psu_slot:update_status_temperature()
        local ok, status = pcall(function()
            return self.protocol:get_status_temperature()
        end)
        if ok then
            self.health_status[STATUS_TEMPERATURE] = status
            return false, status ~= 0
        else
            return true, false
        end
    end

    -- 更新电源CMD状态
    function psu_slot:update_status_cml()
        local ok, status = pcall(function()
            return self.protocol:get_status_cml()
        end)
        if ok then
            self.health_status[STATUS_CML] = status
            return false, status ~= 0
        else
            return true, false
        end
    end

    -- 更新电源状态双字节
    function psu_slot:update_status_word()
        local ok, status = pcall(function()
            return self.protocol:get_status_word()
        end)
        if ok then
            self.health_status[STATUS_WORD] = status
            return false, status ~= 0
        else
            return true, false
        end
    end

    function psu_slot:psm_task_status()
        local need_clear_fault, failed_flag, err, rsp = false, false, false, false
        while true do
            err, rsp = self:update_status_input()
            need_clear_fault = need_clear_fault or rsp
            failed_flag = failed_flag or err
            err, rsp = self:update_status_vout()
            need_clear_fault = need_clear_fault or rsp
            failed_flag = failed_flag or err
            err, rsp = self:update_status_iout()
            need_clear_fault = need_clear_fault or rsp
            failed_flag = failed_flag or err
            err, rsp = self:update_status_temperature()
            need_clear_fault = need_clear_fault or rsp
            failed_flag = failed_flag or err
            err, rsp = self:update_status_cml()
            need_clear_fault = need_clear_fault or rsp
            failed_flag = failed_flag or err
            err, rsp = self:update_status_word()
            need_clear_fault = need_clear_fault or rsp
            failed_flag = failed_flag or err
            if not failed_flag and need_clear_fault then
                self:sleep_ms(1000)
                pcall(function ()
                    self.protocol:send_clear_fault()
                end)
            end
            self.health_status.failed_flag = failed_flag
            need_clear_fault = false
            failed_flag = false
            self:sleep_ms(5000)
        end
    end

    function psu_slot:power_monitor_entry()
        self:add_monitor_tasks(
            function()
                self:start_to_receive_remote_power()
                self:psm_task_power()
            end
        )
        self:add_monitor_tasks(
            function()
                self:psm_task_v_i()
            end
        )
        self:add_monitor_tasks(
            function()
                self:time_service_task()
            end
        )
        self:add_monitor_tasks(
            function()
                self:psm_task_status()
            end
        )
        self.scan_status = self.scan_status | get_pbit(3)
        self:dal_refresh_property('scan_status', self.scan_status)
    end

    function psu_slot:power_monitor_start()
        log:notice('ps%d power monitor start', self.ps_id)
        self:power_monitor_entry()
    end

    return psu_slot
end