-- 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 log = require 'mc.logging'

local card_resource_ncsi_update = {}

local OCP_CARD_TYPE<const> = 10
local PACKAGE_ID_MAX<const> = 7

local function update_ncsi_firmware(resource_obj, ports)
    local s = resource_obj.ncsi_config_obj:VendorID({package_id = resource_obj.package_id})
    s.on_data_change:on(function(firmware)
        if not firmware then
            log:debug("firmware is nil")
            return
        end

        resource_obj.FirmwareVersion = firmware.FirmwareVersion

        -- 仅在四元组无法从CSR获取时使用mctp上报的四元组信息
        if resource_obj.VendorID == '' then
            resource_obj.VendorID = firmware.VendorID
        end
        if resource_obj.DeviceID == '' then
            resource_obj.DeviceID = firmware.DeviceID
        end
        if resource_obj.SubsystemVendorID == '' then
            resource_obj.SubsystemVendorID = firmware.SubsystemVendorID
        end
        if resource_obj.SubsystemDeviceID == '' then
            resource_obj.SubsystemDeviceID = firmware.SubsystemDeviceID
        end
        -- Hi1822上测试发现网口firmware version与网卡相同
        -- 暂时通过网卡来获取后分发给网口对象，网口不必再去获取
        for _, port in pairs(ports) do
            port:set_firmware_version(firmware.FirmwareVersion)
        end
    end)
    table.insert(resource_obj.ncsi_schedulers, s)
    s:start()
end

local function update_chip_temp_by_ncsi(resource_obj)
    local s = resource_obj.ncsi_config_obj:ChipTemp({package_id = resource_obj.package_id})
    s.on_data_change:on(function(temp)
        resource_obj.TemperatureCelsius = temp
        resource_obj.TemperatureStatus = (resource_obj.smbios_status == 3 and temp >= 32768) and 1 or 0
    end)
    table.insert(resource_obj.ncsi_schedulers, s)
    s:start()
end

local function update_link_ability_by_ncsi(resource_obj)
    local s = resource_obj.ncsi_config_obj:LinkAbility({package_id = resource_obj.package_id}):value()
    if not s then
        return
    end
    resource_obj.LinkWidthCapability = s.link_width
    resource_obj.LinkSpeedCapability = s.link_speed
    log:info('get the link capability, LinkWidthCapability:%s, LinkSpeedCapability:%s',
        resource_obj.LinkWidthCapability, resource_obj.LinkSpeedCapability)
end

local function update_link_status_by_ncsi(resource_obj)
    local s = resource_obj.ncsi_config_obj:LinkInfo({package_id = resource_obj.package_id}):value()
    if not s then
        return
    end
    resource_obj.LinkWidth = s.link_width
    resource_obj.LinkSpeed = s.link_speed
    log:info('get the link status, link_width_status:%s, link_speed_status:%s', resource_obj.LinkWidth,
        resource_obj.LinkSpeed)
end

local function update_error_code_by_ncsi(resource_obj)
    local s = resource_obj.ncsi_config_obj:FaultStatCode({package_id = resource_obj.package_id})
    s.on_data_change:on(function(data)
        if data.health_status ~= 0 then
            log:error('card:%s, is not in normal state, health_status: %s', resource_obj.NodeId,
                data.health_status)
        end
        -- 故障告警只用第一个故障码
        if data.error_codes and #data.error_codes > 0 then
            resource_obj.FaultState = data.error_codes[1]
        end
        log:debug('netcard health is %s, faultcode is :%s', resource_obj.Health, resource_obj.FaultCode)
    end)
    table.insert(resource_obj.ncsi_schedulers, s)
    s:start()
end

local function update_network_adapter(resource_obj, ports)
    resource_obj:next_tick(update_ncsi_firmware, resource_obj, ports)
    resource_obj:next_tick(update_chip_temp_by_ncsi, resource_obj)
    resource_obj:next_tick(update_link_ability_by_ncsi, resource_obj)
    resource_obj:next_tick(update_link_status_by_ncsi, resource_obj)
    resource_obj:next_tick(update_error_code_by_ncsi, resource_obj)
end

local function disable_hardware_arbittration(resource_obj)
    local cur_package_id = 0
    local ret
    while cur_package_id <= PACKAGE_ID_MAX do
        ret = resource_obj.ncsi_config_obj:DisableHardwareArbitration({package_id = cur_package_id}):value()
        if ret then
            resource_obj.package_id = cur_package_id
            log:notice('disable hardware arbittration success, set %s package id to %s',
                resource_obj.NodeId, cur_package_id)
            break
        end
        cur_package_id = cur_package_id + 1
    end
end

function card_resource_ncsi_update.update_ncsi_properties(resource_obj, ports)
    resource_obj:sleep_ms(5000)
    if resource_obj.Type == OCP_CARD_TYPE or resource_obj.Model == 'BF3' then
        disable_hardware_arbittration(resource_obj)
    end
    for _, port in pairs(ports) do
        -- 需要先初始化每个网口，才能获取数据。网卡数据默认从端口0获取
        -- 上板测过网卡数据每个网口都能获取到，且数据相同
        -- 这里初始化网口在本协程跑，其余数据在新协程跑
        port:set_ncsi_config_obj(resource_obj.ncsi_config_obj)
        if resource_obj.Type == OCP_CARD_TYPE or resource_obj.Model == 'BF3' then
            port:set_package_id(resource_obj.package_id)
        end
        port:initialize_ncsi_channel()
        port:next_tick(port.update_ncsi_properties, port)
    end
    update_network_adapter(resource_obj, ports)
end

return card_resource_ncsi_update