-- 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 singleton = require 'mc.singleton'
local log = require 'mc.logging'
local mdb = require 'mc.mdb'
local psu_service = require 'psu_service'
local initiator = require 'mc.initiator'
local c_psu_object = require 'device.psu'
local c_psu_slot_object = require 'device.psu_slot'

local PSU_HEALTH <const> = 'psu.health'
local PSU_VIN <const> = 'psu.vin'
local PSU_VOUT <const> = 'psu.vout'
local PSU_IIN <const> = 'psu.iin'
local PSU_IOUT <const> = 'psu.iout'
local PSU_POWER <const> = 'psu.power'
local PSU_POWEROUT <const> = 'psu.powerout'

local metric_collect = class()

function metric_collect:ctor(bus)
    self.bus = bus
end

function metric_collect:get_psu_data_collection_items(obj)
    -- 对象不在位或初始化未完成时无需触发采集
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj or psu_obj.Presence ~= 1 or psu_obj.SerialNumber == '' then
        return '', {}, {}, {}
    end

    local Classification = {
        { PropName = 'Manufacture', PropVal = psu_obj.Manufacturer },
        { PropName = 'PsType', PropVal = psu_obj.Model },
        { PropName = 'PsRate', PropVal = tostring(psu_obj.Rate) },
        { PropName = 'ChassisId', PropVal = '1' }
    }
    local Identification = {
        { PropName = 'SN', PropVal = psu_obj.SerialNumber },
        { PropName = 'AnchorSlot', PropVal = tostring(psu_obj.SlotNumber) },
        { PropName = 'ComNodeId', PropVal = tostring(psu_obj.SlotNumber - 1) }
    }
    local MetricName = {
        PSU_HEALTH, PSU_VIN, PSU_VOUT, PSU_IIN, PSU_IOUT, PSU_POWER, PSU_POWEROUT
    }
    return 'PSU', Classification, Identification, MetricName
end

local function get_health(bus, data, obj)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj then
        return
    end

    table.insert(data, tostring(psu_obj.Health))
end

local function get_vin(bus, data, obj)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj then
        return
    end

    table.insert(data, tostring(psu_obj.InputVoltage))
end

local function get_vout(bus, data, obj)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj then
        return
    end

    table.insert(data, tostring(psu_obj.OutputVoltage))
end

local function get_iin(bus, data, obj)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj then
        return
    end

    table.insert(data, tostring(psu_obj.InputCurrentAmps))
end

local function get_iout(bus, data, obj)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj then
        return
    end

    table.insert(data, tostring(psu_obj.OutputCurrentAmps))
end

local function get_power(bus, data, obj)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj then
        return
    end

    table.insert(data, tostring(psu_obj.InputPowerWatts))
end

local function get_powerout(bus, data, obj)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj then
        return
    end

    table.insert(data, tostring(psu_obj.OutputPowerWatts))
end

local metric_func_table = {
    [PSU_HEALTH] = { metric_func = get_health },
    [PSU_VIN] = { metric_func = get_vin },
    [PSU_VOUT] = { metric_func = get_vout },
    [PSU_IIN] = { metric_func = get_iin },
    [PSU_IOUT] = { metric_func = get_iout },
    [PSU_POWER] = { metric_func = get_power },
    [PSU_POWEROUT] = { metric_func = get_powerout }
}

local function get_metric_data(bus, obj, metric_name)
    log:debug('metric_name = %s', metric_name)
    local data = {}
    if metric_func_table[metric_name] ~= nil then
        metric_func_table[metric_name].metric_func(bus, data, obj)
    else
        log:notice('invalid metric, metric_name = %s', metric_name)
    end
    return data
end

function metric_collect:get_psu_data_collection_data(obj, metric_name)
    local Val = {}
    for _, value in ipairs(metric_name) do
        local data = get_metric_data(self.bus, obj, value)
        if #data ~= 0 then
            table.insert(Val, { MetricName = value, Data = data })
        end
    end
    return Val
end

function metric_collect:reset_psu_metrics(ctx, obj)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    if not psu_obj then
        log:error('psu object not found, path = %s', obj.path)
        error('psu object not found')
    end
    psu_obj.EnergykWh = 0
    psu_obj.EnergySensorResetTime = ""
    log:operation(
        initiator.new(
            ctx and ctx.Interface or 'N/A',
            ctx and ctx.UserName or 'N/A',
            ctx and ctx.ClientAddr or 'N/A'
        ),
        'power_mgmt',
        'Reset PSU %s energy metrics successfully',
        psu_obj.SlotNumber
    )
end

return singleton(metric_collect)