-- 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 c_handler_base = require 'bma.handles.handler_base'
local c_network_port = require 'device.class.network_port'
local c_handler_eth = require 'bma.handles.handler_eth'
local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local event_mgmt = require 'event.event_mgmt'
local c_network_adapter = require 'device.class.network_adapter'
local port_mgmt = require 'device.class.nic_mgmt.port_mgmt'

local is_null = c_handler_base.is_null
local get_odata_id = c_handler_base.get_odata_id

local DEFAULT_BANDWIDTH_USAGE<const> = 0xFFFF

local c_handler_eth_statistics = class(c_handler_base)

local ODATA_TYPE<const> = '@odata.type'
local json = require 'cjson'

local oam_data_cache_list = {}

function c_handler_eth_statistics:ctor()
end

function c_handler_eth_statistics:init()
    self:regist_odata_type('OemEthernetInterfaceStatistics')
    self:regist_odata_type('OemEthernetInterfaceOAMStatistics')

    self:regist_class_type(c_network_port)

    c_handler_eth.get_instance().on_need_update:on(function(object, path)
        self:match_resource(object, path)
        self:handle_cache_data()
    end)
end

function c_handler_eth_statistics:find_object(_, data)
    local port_bma_id = self.get_parent_path(get_odata_id(data))
    return c_handler_eth.get_instance():get_object(port_bma_id)
end

function c_handler_eth_statistics:update_band_width_usage(port, data)
    if port and port.WorkloadType and port.WorkloadType == 1 then
        return
    end
    log:info('update network port BandwidthUsage from bma: path=%s, @odata.id=%s',
        port.path, get_odata_id(data))
    if data.BandwidthUsage == nil then
        return
    end

    local val = is_null(data.BandwidthUsage) and 0 or data.BandwidthUsage
    if val ~= DEFAULT_BANDWIDTH_USAGE and val <= 100 and val >= 0 then
        port.BandwidthUsagePercent = val
        pcall(function ()
            port_mgmt.get_instance():set_bandwidth_usage_to_device_obj(port, val)
        end)
    end
end

function c_handler_eth_statistics:update_network_port(port, data)
    if port and port.WorkloadType and port.WorkloadType == 1 then
        return
    end
    -- 收到的帧数量
    log:info('update network port statistics: from bma: path=%s, @odata.id=%s', port.path,
        get_odata_id(data))
    if data.RxPackets ~= nil then
        port.RXFrames = tostring(data.RxPackets)
    end

    -- 发送的帧数量
    if data.TxPackets ~= nil then
        port.TXFrames = tostring(data.TxPackets)
    end
end

function c_handler_eth_statistics:reset()
    for object, _ in pairs(self.objects) do
        object:reset_bma_stats()
    end
end

function c_handler_eth_statistics:add(_, data, object)
    self:update_network_port(object, data)
    self:update_band_width_usage(object, data)
end

function c_handler_eth_statistics:update(_, data, object)
    self:update_network_port(object, data)
    self:update_band_width_usage(object, data)
end

function c_handler_eth_statistics:check_oam_err_pkt_cnt(data)
    if not data[ODATA_TYPE] or not data[ODATA_TYPE]:find('OemEthernetInterfaceOAMStatistics') then
        return
    end
    local id = data['Id']
    if not c_network_port.port_register then
        oam_data_cache_list[id] = data
        return
    end
    oam_data_cache_list[id] = nil -- 防止重入
    local cnt = data['OAMErrPktCnt']
    local bdf = string.sub(id, string.find(id, '_') + 1)
    -- 匹配网口
    local port_obj = c_network_port.collection:find({BDF = bdf})
    if not port_obj then
        log:debug('check oam lost link state failed [not port object]')
        return
    end
    local parent
    if port_obj.CreatedByDeviceObject then
        parent = port_obj.parent
    else
        parent = port_obj:get_parent()
    end
    local adapter_obj = c_network_adapter.collection:find({NodeId = parent.NodeId})
    if not adapter_obj then
        log:debug('check oam lost link state failed [not adapter object]')
        return
    end
    cnt = (cnt == json.null or not cnt or cnt == 0xFFFFFFFF) and 0 or tonumber(cnt) -- BMA数据异常看做0处理
    event_mgmt:check_oam_err_pkt_cnt_alarm(cnt, adapter_obj.DeviceLocator, tostring(port_obj.PortID))
end


function c_handler_eth_statistics:handle_cache_data()
    if #oam_data_cache_list == 0 then
        return
    end
    for id, data in pairs(oam_data_cache_list) do
        if self:check_oam_err_pkt_cnt(data) then
            oam_data_cache_list[id] = nil
        end
    end
end

return singleton(c_handler_eth_statistics)
