-- 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 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 match_rule = org_freedesktop_dbus.MatchRule
local enums = require 'macros.power_mgmt_enums'

local TIME_SERVICE_INTERVAL<const> = 1000 * 60 * 5
local IS_NOT_HEALTH<const> = 0xff
local READ_FAIL_DEBOUNCE_COUNT <const> = 3
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.read_fail_count = 0
        self.last_read_interval = 10
        self.read_interval = 1000
        self.monitor_tasks = {}
    end

    function psu_slot:add_monitor_tasks(cb)
        table.insert(self.monitor_tasks, self:next_tick(cb))
    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:get_power_supply_info()
        local ok, resp = pcall(function ()
            return self.protocol:update_elabel_info()
        end)

        if not ok then
            log:info('ps%d update_elabel_info failed, response = %s', self.ps_id, resp)
            return resp
        end
        for key, value in pairs(resp) do
            self:dal_refresh_property(key, value)
        end
        log:notice('ps%s read elabel_info successfully.', self.ps_id)
    end

    function psu_slot:get_firmware_version()
        local ok, resp = pcall(function ()
            return self.protocol:refresh_basic_info()
        end)

        if not ok then
            log:info('ps%d refresh_basic_info failed, response = %s', self.ps_id, resp)
            return resp
        end
        self:dal_refresh_property('firmware_version', resp)
        log:notice('ps%s read firmware_version successfully.', self.ps_id)
    end

    function psu_slot:fetch_power_supply_info()
        local resp = self:get_power_supply_info()
        if resp ~= E_OK then
            log:warn('ps%d update_elabel_info failed, response = %s', self.ps_id, resp)
            self:next_tick(function ()
                while self:get_power_supply_info() ~= E_OK do
                    self:sleep_time(10000, 1000)
                end
            end)
        end
        resp = self:get_firmware_version()
        if resp ~= E_OK then
            log:warn('ps%d refresh_basic_info failed, response = %s', self.ps_id, resp)
            self:next_tick(function ()
                while self:get_firmware_version() ~= E_OK do
                    self:sleep_time(10000, 1000)
                end
            end)
        end
    end

    function psu_slot:update_health_event(alarm_status)
        if not alarm_status then
            return {}
        end
        local event = 0
        -- 输出欠压
        local output_under_voltage = alarm_status & get_pbit(10)
        if output_under_voltage > 0 then
            -- 根据canbus协议AlarmStatus属性bit10为1，代表输出欠压。但是根据电源告警，若输出欠压，OutputVoltageFault bit4需要置为1
            output_under_voltage = get_pbit(4)
        end
 
        -- 输出过压
        local output_over_voltage = alarm_status & get_pbit(8)
        if output_over_voltage > 0 then
            -- 根据canbus协议AlarmStatus属性bit8为1，代表输出过压。但是根据电源告警，若输出过压，OutputVoltageFault bit7需要置为1
            output_over_voltage = get_pbit(7)
        end
        local output_voltage_fault = output_under_voltage | output_over_voltage

        -- 输入欠压
        local input_under_voltage = alarm_status & get_pbit(1)
        if input_under_voltage > 0 then
            -- 根据canbus协议AlarmStatus属性bit1为1，代表输入欠压。但是根据电源告警，若输入欠压，InputVoltageFault bit4需要置为1
            input_under_voltage = get_pbit(4)
            event = event | get_pbit(4)
        end
        -- 输入过压
        local input_over_voltage = alarm_status & get_pbit(0)
        if input_over_voltage > 0 then
            -- 根据canbus协议AlarmStatus属性bit0为1，代表输入过压。但是根据电源告警，若输入过压，InputVoltageFault bit7需要置为1
            input_over_voltage = get_pbit(7)
            event = event | get_pbit(4)
        end
        local input_voltage_fault = input_under_voltage | input_over_voltage

        -- 过温
        local over_temper_fault = alarm_status & get_pbit(16)
        if over_temper_fault > 0 then
            -- 根据canbus协议AlarmStatus属性bit16为1，代表过温。但是根据电源告警，若过温，OverTemperature bit7需要置为1
            over_temper_fault = get_pbit(7)
            event = event | get_pbit(3)
        end

        -- 输出过流
        local output_over_current = alarm_status & get_pbit(9)
        if output_over_current > 0 then
            -- 根据canbus协议AlarmStatus属性bit16为1，代表输出过流。但是根据电源告警，若输出过流，OutputCurrentFault bit7需要置为1
            output_over_current = get_pbit(7)
        end
        return {
            event = event,
            output_voltage_fault = output_voltage_fault,
            output_current_fault = output_over_current,
            temper_fault = over_temper_fault,
            input_voltage_fault = input_voltage_fault
        }
    end

    function psu_slot:update_pre_health_event(health_event, pre_alarm_status)
        if not pre_alarm_status then
            return health_event
        end
 
        local event = 0
        -- 输入电压偏高
        local input_over_voltage = pre_alarm_status & get_pbit(0)
        if input_over_voltage > 0 then
            -- 根据canbus协议AlarmStatus属性bit0为1，代表输入电压偏高。但是根据电源告警，若输入电压偏高，InputVoltageFault bit6需要置为1
            input_over_voltage = get_pbit(6)
        end
 
        -- 输入电压偏低
        local input_under_voltage = pre_alarm_status & get_pbit(1)
        if input_under_voltage > 0 then
            -- 根据canbus协议AlarmStatus属性bit1为1，代表输入电压偏低。但是根据电源告警，若输入电压偏低，InputVoltageFault bit5需要置为1
            input_under_voltage = get_pbit(5)
        end
        local input_voltage_warn = input_under_voltage | input_over_voltage
 
        -- 温度偏高
        local over_temper_warn = pre_alarm_status & get_pbit(16)
        if over_temper_warn > 0 then
            -- 根据canbus协议AlarmStatus属性bit16为1，代表温度偏高。但是根据电源告警，若温度偏高，OverTemperature bit6需要置为1
            over_temper_warn = get_pbit(6)
            event = event | get_pbit(3)
        end
        
        health_event.event = health_event.event | event
        health_event.temper_fault = health_event.temper_fault | over_temper_warn
        health_event.input_voltage_fault = health_event.input_voltage_fault | input_voltage_warn
        return health_event
    end
 
    function psu_slot:health_check(batch_query_info_resp)
        local health_event = self:update_health_event(batch_query_info_resp.AlarmStatus)
        health_event = self:update_pre_health_event(health_event, batch_query_info_resp.PreAlarmStatus)
        self:dal_refresh_property('health_event', health_event)
        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

    function psu_slot:refresh_batch_query_info()
        -- 0x40 批量查询信息
        local batch_query_info = self.protocol['refresh_batch_query_info']
        local batch_query_info_ok, batch_query_info_resp = pcall(batch_query_info, self.protocol)
        if batch_query_info_ok then
            log:info('[power_mgmt]ps%d refresh_batch_query_info ok', self.ps_id)
            for prop, value in pairs(batch_query_info_resp) do
                self:dal_refresh_property(prop, value)
            end
            self:health_check(batch_query_info_resp)
        else
            log:info('[power_mgmt]ps%s refresh_batch_query_info failed, response = %s', self.ps_id,
                        batch_query_info_resp)
        end
    end

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

    local PS_TRY_CNT = 30 -- 重试次数
    function psu_slot:power_monitor_entry()
        self:add_monitor_tasks(function()
            while true do
                self:refresh_batch_query_info()
                self.scan_status = self.scan_status | get_pbit(2) | get_pbit(1)
                self:dal_refresh_property('scan_status', self.scan_status)
                self:sleep_time(1000, 1000)
            end
        end)
        self:add_monitor_tasks(
            function()
                self:time_service_task()
            end
        )
        self.scan_status = self.scan_status | get_pbit(3)
        self:dal_refresh_property('scan_status', self.scan_status)
        self:get_dynamic_data('rate', PS_TRY_CNT)
    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