-- 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.
 
-- 天工 3200w电源
-- 不支持设置冷休眠，设置电源主备模式，设置风扇转速
-- 额定功率和功率为0，不需要算入总功率，总额定功率
-- 轮询间隔时间原来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 fw_def = require 'device_tree.adapters.power_mgmt.protocol.upgrade.fw_def'
local match_rule = org_freedesktop_dbus.MatchRule
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 function get_pbit(val)
    return 1 << val
end

return function (obj)
    local psu_slot = class(obj)

    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 = {}
    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()
    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:update_health_event()
        local health_event, failed_flag = self.protocol:get_health_event()
        if not health_event then
            log:info('get health_event failed')
            return E_FAILED
        end

        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:dal_refresh_property('communication_status', 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:dal_refresh_property('communication_status', 0)
            self:dal_refresh_property('health', health_event.event)
            self.scan_status = self.scan_status | get_pbit(0)
            self:dal_refresh_property('scan_status', self.scan_status)
        end

        self:dal_refresh_property('health_event', health_event)
        return E_OK
    end

    -- power_monitor 实际功率任务
    function psu_slot:power_monitor()
        if self:update_health_event() ~= E_OK then
            -- 接口调用失败， 获取不到属性，直接返回
            return E_FAILED
        end

        return E_OK
    end

    function psu_slot:psm_task_power()
        while true do
            self:power_monitor()
            self:sleep_ms(1500)
        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
            rsp, _ = self:get_dynamic_data('input_current_amps')
            ret = ret or rsp
            rsp, _ = self:get_dynamic_data('output_current_amps')
            ret = ret or rsp

            rsp, _ = self:get_dynamic_data('input_voltage')
            ret = ret or rsp
            rsp, _ = self:get_dynamic_data('output_voltage')
            ret = ret or rsp
            rsp, _ = self:get_dynamic_data('power_supply_type')
            ret = ret or rsp
            rsp, _ = self:get_dynamic_data('env_temperature_celsius')
            ret = ret or rsp
            rsp, _ = self:get_dynamic_data('primary_chip_temperature_celsius')
            ret = ret or rsp
            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(3000)
        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:power_monitor_entry()
        self:add_monitor_tasks(
            function()
                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.scan_status = self.scan_status | get_pbit(1) | get_pbit(3)
        self:dal_refresh_property('scan_status', self.scan_status)
    end

    function psu_slot:power_monitor_start()
        local ok, uptime = pcall(utils_core.get_bmc_uptime)
        if not ok then
            log:error('Get bmc uptime failed')
        else
            log:notice('Current bmc uptime is %s', uptime)
            local wait_time = enums.UPTIME - uptime
            if wait_time > 0 then
                skynet.sleep(wait_time * 100) -- BMC启动前UPTIME时间内不进行电源数据采集
            end
        end
        log:notice('ps%d power monitor start', self.ps_id)
        self:power_monitor_entry()
    end

    return psu_slot
end

