-- 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 class = require 'mc.class'
local singleton = require 'mc.singleton'
local skynet = require 'skynet'
local log = require 'mc.logging'
local power_mgmt_utils = require 'power_mgmt_utils'
local enums = require 'macros.power_mgmt_enums'
local system_power = require 'system_power'
local power_mgmt_metric = require 'power_mgmt_metric'
local psu_service = require 'psu_service'
local base_msg = require 'messages.base'
local cjson = require 'cjson'
local utils_core = require 'utils.core'
local life_prediction_model = require 'life_prediction_model'

local capacitor_mgmt = class()

local DETECTION_PERIOD = 180 * 24 * 60 * 60
local START_WAIT_TIME_SECONDS = 120000
local ACCUMULATED_PERIOD_SECONDS = 360000
local AVERAGE_POWER_PERIOD_SECONDS = 6000
local CALIBRATE_INTERVAL_SECONDS = 1500
local WINDOW_SIZE = 60
local SYSTEM_CHECK_FLAG = 0
local EXECUTE_CHECK_FLAG = 1
local E_OK <const> = nil -- 函数执行成功返回nil
local E_FAILED <const> = '' -- 空错误信息
local LIFE_PREDICTION_TASK_PERIOD<const> = 10 * 60 * 100 -- 寿命预测任务周期为10分钟
local LIFE_PREDICTION_TIME_INTERVAL_SECONDS<const> = 60 * 60 * 24 * 30 -- 寿命预测时间间隔为30天
local LIFE_PREDICTION_NULL<const> = "Null" -- 寿命预测默认状态
local LIFE_PREDICTION_GET_UPTIME_FAILED<const> = "Get uptime failed" -- 寿命预测获取BMC启动时间失败
local LIFE_PREDICTION_WAITING_INTERVAL <const> = "Waiting interval" -- 等待寿命预测时间间隔
local LIFE_PREDICTION_IN_PROGRESS<const> = "In progress" -- 寿命预测进行中
local LIFE_PREDICTION_COMPLETED<const> = "Completed" -- 寿命预测完成

function capacitor_mgmt:ctor(bus)
    self.psu_obj = c_psu_object.collection.objects
    self.power_mgmt_utils_instance = power_mgmt_utils.get_instance()
    self.system_power_instance = system_power.get_instance()
    self.power_mgmt_metric = power_mgmt_metric.get_instance()
    self.psu_service = psu_service.get_instance()
    self.last_timers = 0
    self.avapower_statistics_count = 1
    self.window_full_flag = 0
    self.calibrate_supported = 0
    self.last_life_prediction_time = 0
    self.life_prediction_status = LIFE_PREDICTION_NULL
    self.dod_freq_count_map = {} --深度 + 频率放电次数映射表
end

function capacitor_mgmt:calibrate_timers(expected_time)
    local accumulated_time = self.system_power_instance:get_calibrate_accumulated_time()

    if accumulated_time > expected_time then
        log:notice('accumulated time %s is exceeded expected time %s', accumulated_time, expected_time)
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,'will calibrate capacitor, the accumulated time is %s',
            accumulated_time)
        return true
    else
        accumulated_time = accumulated_time + power_mgmt_utils.uptime() - self.last_timers
        self.system_power_instance:set_calibrate_accumulated_time(accumulated_time)
        self.last_timers = power_mgmt_utils.uptime()
    end
    
    return false
end

function capacitor_mgmt:check_onepower_alarm_status(psu_obj)
    if psu_obj.TPSUStatusHigh ~= 0 or psu_obj.TPSUStatusLow ~= 0 then
        log:debug('psu%s is faulty, alarm status is %s %s', psu_obj.ps_id, psu_obj.TPSUStatusHigh,
            psu_obj.TPSUStatusLow)
        return false
    end

    return true
end

function capacitor_mgmt:check_onepower_sleep_status(psu_obj)
    if psu_obj.SleepMode ~= enums.SLEEP_MODE.NORMAL_STR then
        log:debug('psu%s is sleep, sleep mode is %s', psu_obj.ps_id, psu_obj.SleepMode)
        return false
    end

    return true
end

function capacitor_mgmt:check_onepower_upgrade_status(psu_obj)
    if psu_obj.IsUpgrading == true then
        log:debug('psu%s is upgrading, upgrading status is %s', psu_obj.ps_id, psu_obj.IsUpgrading)
        return false
    end

    return true
end

function capacitor_mgmt:check_system_averagepower_condition()
    local system_inputpower_list = {}
    local system_inputpower_sum = 0
    local system_averagepower = 0
    local system_rated_power = self.power_mgmt_metric:get_system_rated_power()
    local upper_limit_power = system_rated_power * 0.8
    local lower_limit_power = system_rated_power * 0.3
    while true do 
        local system_inputpower_consumption = self.power_mgmt_metric:get_system_inputpower_consumption()

        if self.window_full_flag == 0 then
            system_inputpower_sum = system_inputpower_sum + system_inputpower_consumption
        else
            system_inputpower_sum = system_inputpower_sum + system_inputpower_consumption -
                system_inputpower_list[self.avapower_statistics_count]
            system_averagepower = system_inputpower_sum / WINDOW_SIZE
            if system_averagepower > lower_limit_power and system_averagepower < upper_limit_power then
                self.window_full_flag = 0
                self.avapower_statistics_count = 1
                log:debug('check system averagepower condition succeed')
                return true  -- 直到功耗满足条件再退出
            end
        end

        system_inputpower_list[self.avapower_statistics_count] = system_inputpower_consumption
        self.avapower_statistics_count = self.avapower_statistics_count + 1

        if self.avapower_statistics_count > WINDOW_SIZE then
            self.window_full_flag = 1
            self.avapower_statistics_count = 1
        end

        skynet.sleep(AVERAGE_POWER_PERIOD_SECONDS)
    end
end

function capacitor_mgmt:check_powersupply_requirement_condition(flag)
    local err_flag = 0
    local psu_present_count = 0
    c_psu_object.collection:fold(function (_, psu_obj)
        psu_present_count = psu_present_count + 1

        if not self:check_onepower_alarm_status(psu_obj) then
            err_flag = 1
            return
        end

        if not self:check_onepower_sleep_status(psu_obj) then
            err_flag = 1
            return
        end

        if flag == EXECUTE_CHECK_FLAG and not self:check_onepower_upgrade_status(psu_obj) then
            err_flag = 1
            return
        end
    end)

    if err_flag == 1 or psu_present_count ~= #self.psu_service.psu_slot then
        log:debug('check powersupply requirement condition failed')
        return false
    end

    return true
end

function capacitor_mgmt:check_system_inputpower_consumption_condition()
    local system_inputpower_consumption = self.power_mgmt_metric:get_system_inputpower_consumption()
    local system_rated_power = self.power_mgmt_metric:get_system_rated_power()
    local upper_limit_power = system_rated_power * 0.8
    local lower_limit_power = system_rated_power * 0.3

    if system_inputpower_consumption < upper_limit_power and system_inputpower_consumption > lower_limit_power then
        return true
    else
        log:debug('check system inputpower consumption condition failed')
        return false
    end

end

function capacitor_mgmt:capaticors_calibrate_execute()
    local successfully_count = 0
    local total_count = #self.psu_service.psu_slot == 0 and 1 or #self.psu_service.psu_slot
    c_psu_object.collection:fold(function (_, psu_obj)
        if not self:check_calibrate_execute_condition() then
            skynet.sleep(CALIBRATE_INTERVAL_SECONDS)
            return
        end
        if not self:capaticor_calibrate_execute(psu_obj) then
            skynet.sleep(CALIBRATE_INTERVAL_SECONDS)
            return
        end

        successfully_count = successfully_count + 1
        skynet.sleep(CALIBRATE_INTERVAL_SECONDS)
    end)

    if successfully_count / total_count > 0.8 then  -- 当成功完成电容自检的数量超过总数量的80%时，认为整体成功
        log:debug('execute capaticors calibrate successfully, successfully_count %s total_count %s',
            successfully_count,total_count)
        c_psu_object.collection:fold(function (_, psu_obj)
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'PSU%s execute capaticors calibrate finished, capacity microfarads: %s', psu_obj.SlotNumber,
                psu_obj.CapacityMicrofarads)
        end)
        return true
    else
        log:debug('execute capaticors calibrate failed, successfully_count %s total_count %s',
            successfully_count,total_count)
        return false
    end
end

function capacitor_mgmt:check_calibrate_system_condition()
    if not self:check_powersupply_requirement_condition(SYSTEM_CHECK_FLAG) then
        log:debug('check calibrate system condition failed')
        return false
    end

    if not self:check_system_averagepower_condition() then
        log:debug('check calibrate system condition failed')
        return false
    end

    return true
end

function capacitor_mgmt:check_calibrate_execute_condition()
    if not self:check_powersupply_requirement_condition(EXECUTE_CHECK_FLAG) then
        log:debug('check calibrate execute condition failed')
        return false
    end

    if not self:check_system_inputpower_consumption_condition() then
        log:debug('check calibrate execute condition failed')
        return false
    end

    return true
end

function capacitor_mgmt:capaticor_calibrate_execute(psu_obj)
    local err_info = psu_obj:set_power_capacitor_calibrate()

    if err_info then
        log:debug('set power capacitor calibrate failed, err_info:%s', err_info)
        return false
    end

    return true
end

function capacitor_mgmt:capacitor_calibrate_task()
    while true do
        if self:calibrate_timers(DETECTION_PERIOD) then
            if not self:check_calibrate_system_condition() then
                self.last_timers = power_mgmt_utils.uptime()
                self.system_power_instance:set_calibrate_accumulated_time(0)
                goto continue
            end

            if self:capaticors_calibrate_execute() then
                self.last_timers = power_mgmt_utils.uptime()
                self.system_power_instance:set_calibrate_accumulated_time(0)
            end
        end

        ::continue::
        skynet.sleep(ACCUMULATED_PERIOD_SECONDS) -- 每一小时校准统计时间
    end
end

function capacitor_mgmt:check_capaticor_calibrate_supported()
    c_psu_object.collection:fold(function (_, psu_obj)
        local err_info = psu_obj:get_power_capacitor_calibrate()
        if err_info and err_info.name == base_msg.ActionNotSupportedMessage.Name then
            return
        end

        self.calibrate_supported = 1
    end)

    if self.calibrate_supported == 1 then
        return true
    else
        log:notice('capacitor calibrate is not supported')
        return false
    end
end

function capacitor_mgmt:set_life_prediction_status(status)
    if self.life_prediction_status ~= status then
        log:notice("life prediction status changes from '%s' to '%s'", self.life_prediction_status, status)
        self.life_prediction_status = status
    end
end

function capacitor_mgmt:dump_log()
    local dod_freq_count_info

    for ps_id, dod_freq_count_map in pairs(self.dod_freq_count_map) do
        dod_freq_count_info = {}
        for dod_level, freq_count_info in pairs(dod_freq_count_map) do
            for freq, count in pairs(freq_count_info) do
                table.insert(dod_freq_count_info, ", dod")
                table.insert(dod_freq_count_info, dod_level)
                table.insert(dod_freq_count_info, " ")
                table.insert(dod_freq_count_info, "freq")
                table.insert(dod_freq_count_info, freq)
                table.insert(dod_freq_count_info, ": ")
                table.insert(dod_freq_count_info, count)
            end
        end
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'PSU%s discharge count info%s', ps_id, table.concat(dod_freq_count_info))
    end
end

function capacitor_mgmt:predicted_lifespan(model_level, psu_obj)
    local index, count, ret, model_index, dod_freq_count
    local model_params, dod_freq_count_map = {}, {}
    -- model_level高字节的低4位表示放电深度档位
    for dod_level = 1, (model_level & 0xF00) >> 8 do
        -- model_level低字节的高4位表示放电频率档位
        for freq_level = 1, (model_level & 0x0F0) >> 4 do
            dod_freq_count = 0
            -- model_level低字节的低4位表示温度档位
            for temp_level = 1, (model_level & 0x00F) do
                -- 电源下发索引从0开始，需要减1
                ret = psu_obj:set_life_prediction_count_index(dod_level - 1, freq_level - 1, temp_level - 1)
                if ret ~= E_OK then
                    log:error('psu%s set life prediction count index failed', psu_obj.ps_id)
                    return ret
                end
                -- 等待2s，让电源上报档位放电数据
                skynet.sleep(200)
                ret, index, count = psu_obj:get_life_prediction_count()
                if ret ~= E_OK then
                    log:error('psu%s get life prediction count failed', psu_obj.ps_id)
                    return ret
                end
                model_index = (dod_level - 1) << 8 | (freq_level - 1) << 4 | (temp_level - 1)
                -- 电源上报数据的档位和预期获取的档位不一致，不进行预测
                if index ~= model_index then
                    log:error('psu%s life prediction count index is %s, expected index is %s', psu_obj.ps_id, index,
                        model_index)
                    return E_FAILED
                end
                dod_freq_count = dod_freq_count + count
                model_params[model_index] = count
            end
            if not dod_freq_count_map[dod_level] then
                dod_freq_count_map[dod_level] = {}
            end
            dod_freq_count_map[dod_level][freq_level] = dod_freq_count
        end
    end
    self.dod_freq_count_map[psu_obj.SlotNumber] = dod_freq_count_map
    local lifespan = life_prediction_model.predicted_lifespan(model_params)
    if not lifespan then
        log:error('psu%s predicted lifespan failed', psu_obj.ps_id)
        return E_FAILED
    end
    return E_OK, lifespan
end

function capacitor_mgmt:life_prediction_execute()
    local model_level = life_prediction_model.get_model_level()
    local level, ret, lifespan
    c_psu_object.collection:fold(function (_, psu_obj)
        ret, level = psu_obj:get_life_prediction_level()
        -- 电源上报档位和模型不一致，不进行预测
        if ret ~= E_OK or model_level ~= level then
            log:error('psu%s life prediction level is %s, model level is %s, not predict', psu_obj.ps_id, level, model_level)
            return
        end
        log:notice('psu%s predicted lifespan begins', psu_obj.ps_id)
        ret, lifespan = self:predicted_lifespan(model_level, psu_obj)
        log:notice('psu%s predicted lifespan completed', psu_obj.ps_id)
        if ret == E_OK then
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
                'Power supply %s predicted lifespan successful, lifespan: %s', psu_obj.ps_id, lifespan)
        end
    end)
end

function capacitor_mgmt:life_prediction_task()
    log:notice('start life prediction task')
    local ok, uptime
    while true do
        ok, uptime = pcall(utils_core.get_bmc_uptime)
        if not ok then
            self:set_life_prediction_status(LIFE_PREDICTION_GET_UPTIME_FAILED)
            goto continue
        end
        if self.last_life_prediction_time ~= 0 and uptime - self.last_life_prediction_time <
            LIFE_PREDICTION_TIME_INTERVAL_SECONDS then
            self:set_life_prediction_status(LIFE_PREDICTION_WAITING_INTERVAL)
            goto continue
        end
        self:set_life_prediction_status(LIFE_PREDICTION_IN_PROGRESS)
        self:life_prediction_execute()
        self.last_life_prediction_time = uptime
        self:set_life_prediction_status(LIFE_PREDICTION_COMPLETED)
        ::continue::
        skynet.sleep(LIFE_PREDICTION_TASK_PERIOD)
    end
end

function capacitor_mgmt:init()
    skynet.fork(
        function()
            skynet.sleep(START_WAIT_TIME_SECONDS) -- 启动时最长等待20min  避免无法拿到PSU对象
            local software_type = power_mgmt_utils.get_instance():get_chassis_type()
            if software_type ~= enums.SOFTWARE_TYPE.CABINET or not self:check_capaticor_calibrate_supported() then
                log:notice('software_type is %s, not support capacitor calibrate', software_type)
                return
            end
            skynet.fork(function ()
                self:capacitor_calibrate_task()
            end)
            skynet.fork(function ()
                self:life_prediction_task()
            end)
        end
    )
end

return singleton(capacitor_mgmt)