-- 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 ctx = require 'mc.context'
local mctp_lib = require 'mctp_lib'
local libmgmt_protocol = require 'libmgmt_protocol'
local c_object_manage = require 'mc.orm.object_manage'
local c_network_port = require 'device.class.network_port'
local log_collector = require 'device.class.log_collector'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local card_defs = require 'device.class.network_card_adapter.card_defs'
local card_resource_path = 'device.class.network_card_adapter.resource_tree'
local card_resource_mpu = require(card_resource_path .. '.card_resource_mpu')
local card_resource_ncsi_update = require(card_resource_path .. '.card_resource_ncsi_update')
local card_resource_smbus_update = require(card_resource_path .. '.card_resource_smbus_update')
local card_resource_ncsi_collect_log = require(card_resource_path .. '.card_resource_ncsi_collect_log')

local card_resource_init_protocol = {}

local MODULE_NAME_NETWORK_ADAPTER<const> = 'network_adapter'

local function get_endpoint_and_transport_from_mctp(phy_addr, msg_type, mctp_table)
    local ok
    local bus = c_object_manage.get_instance().bus
    ok, mctp_table.endpoint, mctp_table.transport = pcall(mctp_lib.get_endpoint_and_transport, bus,
        MODULE_NAME_NETWORK_ADAPTER, phy_addr, msg_type)
    if not ok or mctp_table.endpoint == nil or mctp_table.transport == nil then
        log:raise('unable to create mctp transport and endpoint. msg: %s', mctp_table.endpoint)
    end
end

local function create_mctp_endpoint_for_sdi6X(resource_obj, msg_type)
    local count = 0
    local max_retry<const> = 120
    log:notice('start create mctp endpoint with Bus=%s, msg_types=%s', resource_obj.DevBus, msg_type)
    while count < max_retry do
        if resource_obj.DevBus ~= 0 or resource_obj.DevDevice ~= 0 or resource_obj.DevFunction ~= 0 then
            local phy_addr_function0 = mctp_lib.bdf_to_phy_addr(resource_obj.DevBus, resource_obj.DevDevice, 0)
            local phy_addr_function1 = mctp_lib.bdf_to_phy_addr(resource_obj.DevBus, resource_obj.DevDevice, 1)
            log:notice('creating mctp transport and endpoint with phy_addr0=%s, phy_addr0=%s, msg_types=%s',
                phy_addr_function0, phy_addr_function1, msg_type)
            local mctp_table = {
                endpoint = nil,
                transport = nil
            }

            -- 同一时间仅会有一个endpoint创建成功
            resource_obj:next_tick(get_endpoint_and_transport_from_mctp, phy_addr_function0, msg_type, mctp_table)
            resource_obj:next_tick(get_endpoint_and_transport_from_mctp, phy_addr_function1, msg_type, mctp_table)

            while (mctp_table.endpoint == nil) or (mctp_table.transport == nil) do
                resource_obj:sleep_ms(1000)
            end
            resource_obj.transports[#resource_obj.transports + 1] = mctp_table.transport
            resource_obj.mctp_endpoints[#resource_obj.mctp_endpoints + 1] = mctp_table.endpoint
            log:notice(
                'register mctp transport and endpoint(bus: %s device:%s) successful',
                resource_obj.DevBus, resource_obj.DevDevice)
            log:notice(
                'register mctp mctp_endpoints %s, transports %s',
                #resource_obj.mctp_endpoints, #resource_obj.transports)
            return mctp_table.endpoint
        end
        count = count + 1
        resource_obj:sleep_ms(1000)
    end
    log:raise('unable to create mctp transport and endpoint with bdf 0')
end

local function create_mctp_endpoint_for_others(resource_obj, msg_type)
    local count = 0
    local max_retry<const> = 120
    log:notice('start create mctp endpoint with Bus=%s, msg_types=%s', resource_obj.DevBus, msg_type)
    while count < max_retry do
        if resource_obj.DevBus ~= 0 or resource_obj.DevDevice ~= 0 or resource_obj.DevFunction ~= 0 then
            local phy_addr = mctp_lib.bdf_to_phy_addr(resource_obj.DevBus, resource_obj.DevDevice,
                resource_obj.DevFunction)
            log:notice('creating mctp transport and endpoint with bdf=%s:%s.%s phy_addr=%d, msg_types=%d',
                resource_obj.DevBus, resource_obj.DevDevice, resource_obj.DevFunction, phy_addr, msg_type)
            local bus = c_object_manage.get_instance().bus
            local ok, endpoint, transport = pcall(mctp_lib.get_endpoint_and_transport, bus,
                MODULE_NAME_NETWORK_ADAPTER, phy_addr, msg_type)
            if not ok or not endpoint or not transport then
                log:raise('unable to create mctp transport and endpoint. msg: %s', endpoint)
            end
            resource_obj.transports[#resource_obj.transports + 1] = transport
            resource_obj.mctp_endpoints[#resource_obj.mctp_endpoints + 1] = endpoint
            log:notice('register mctp transport and endpoint(phy: %s) successful', phy_addr)
            log:notice(
                'register mctp mctp_endpoints %s, transports %s',
                #resource_obj.mctp_endpoints, #resource_obj.transports)
            return endpoint
        end
        count = count + 1
        resource_obj:sleep_ms(1000)
    end
    log:raise('unable to create mctp transport and endpoint with bdf 0')
end

local function create_mctp_endpoint(resource_obj, msg_type)
    if resource_obj.Name == 'QT100' or resource_obj.Name == 'QT100n' then
        return create_mctp_endpoint_for_sdi6X(resource_obj, msg_type)
    else
        return create_mctp_endpoint_for_others(resource_obj, msg_type)
    end
end

function card_resource_init_protocol.init_pldm(resource_obj, pldm_config_func)
    local endpoint = create_mctp_endpoint(resource_obj, mctp_lib.MCTP_MESSAGE_TYPE_PLDM)
    resource_obj.pldm_config_obj = libmgmt_protocol.device_spec_parser(pldm_config_func(endpoint))
end

function card_resource_init_protocol.init_ncsi(resource_obj, ncsi_config_func, ports)
    card_resource_mpu.check_mpu_status(resource_obj)
    local endpoint = create_mctp_endpoint(resource_obj, mctp_lib.MCTP_MESSAGE_TYPE_NCSI)
    resource_obj.ncsi_config_obj = libmgmt_protocol.device_spec_parser(ncsi_config_func(endpoint))
    card_resource_ncsi_update.update_ncsi_properties(resource_obj, ports)
end

function card_resource_init_protocol.setup_vdpci_lldp_listener(resource_obj, ports)
    -- 网卡lldp over mctp走的是mctp vdpci(0x7e)通道
    local endpoint = create_mctp_endpoint(resource_obj, mctp_lib.MCTP_MESSAGE_TYPE_VDPCI)
    local bus = c_object_manage.get_instance().bus
    local sig =
        org_freedesktop_dbus.MatchRule.signal('MessageReceived', 'bmc.kepler.Systems.Mctp.PCIeEndpoint'):with_path(
            endpoint.path)
    resource_obj.lldp_receive_listener = bus:match(sig, function(msg)
        resource_obj:next_tick(function()
            local ok, lldp_rsp = pcall(libmgmt_protocol.vdpci_lldp_parser, msg:read())
            if not ok or not lldp_rsp then
                log:debug('unable to parse vdpci lldp message, error: %s', lldp_rsp)
                return
            end
            lldp_rsp.parsed_data = {}
            for _, tlv in ipairs(lldp_rsp.tlvs) do
                local ret, t = pcall(libmgmt_protocol.lldp_tlv_parser, tlv)
                if ret and t then
                    for k, v in pairs(t) do
                        lldp_rsp.parsed_data[k] = v
                    end
                end
            end
            local port = c_network_port.collection:find({
                Position = resource_obj:get_position(),
                PortID = lldp_rsp.port_id
            })
            port:update_lldp_receive(lldp_rsp)
        end)
    end)

    -- 必须打开网口lldp开关才能收到lldp信息
    for _, p in ipairs(ports) do
        p:enable_lldp(true)
    end
end

local function init_smbus(resource_obj, smbus_config_func, ports)
    resource_obj.smbus_config_obj = libmgmt_protocol.device_spec_parser(smbus_config_func(resource_obj.RefChip))
    card_resource_smbus_update.update_smbus_properties(resource_obj, ports)
end

local function init_smbus_signal_monitor(resource_obj)
    -- 接收到bmc重启信号，BMC重启这种只触发一次，且如果在超时前已经有其它模式触发收集则不再收集
    resource_obj:connect_signal(log_collector.log_dump_reset_bmc_sig, function()
        if not resource_obj.smbus_has_collected and resource_obj.smbus_collect_status == card_defs.LOG_DUMP_IDLE then
            log:info('receive bmc reset signal')
            resource_obj.smbus_collect_status = card_defs.LOG_DUMP_BUSY
            card_resource_smbus_update.collect_log_by_smbus_task(resource_obj)
            resource_obj.smbus_collect_status = card_defs.LOG_DUMP_IDLE
        end
    end)
    -- 接收到os重启信号，只要没有任务正在收集则可以启动收集
    resource_obj:connect_signal(log_collector.log_dump_reset_pmu_sig, function()
        if resource_obj.smbus_collect_status == card_defs.LOG_DUMP_IDLE then
            log:info('receive os reset signal')
            resource_obj.smbus_collect_status = card_defs.LOG_DUMP_BUSY
            card_resource_smbus_update.collect_log_by_smbus_task(resource_obj)
            resource_obj.smbus_collect_status = card_defs.LOG_DUMP_IDLE
        end
    end)
end

local function clean_transport_endpoint(resource_obj, msg_type)
    local path_name
    for i, obj in pairs(resource_obj.mctp_endpoints) do
        path_name = obj.path:match(".+/(.+)") -- 提取资源树路径斜线分割的最后一部分
        if tonumber(path_name) == msg_type then
            -- 找到了对应的endpoint，删除实例
            table.remove(resource_obj.mctp_endpoints, i)
            table.remove(resource_obj.transports, i)  -- 新增的时候transport索引和endpoint一样。
            log:notice('clean_transport_endpoint mctp_endpoints %s, transports %s',
                #resource_obj.mctp_endpoints, #resource_obj.transports)
        end
    end
end

local function init_pldm_over_mctp_signal_monitor(resource_obj, pldm_config_func)
    -- 接收到os重启信号，清理endpoint后再次创建
    resource_obj:connect_signal(log_collector.log_dump_reset_smbios_sig, function()
        clean_transport_endpoint(resource_obj, mctp_lib.MCTP_MESSAGE_TYPE_PLDM)
        local endpoint = create_mctp_endpoint(resource_obj, mctp_lib.MCTP_MESSAGE_TYPE_PLDM)
        resource_obj.pldm_config_obj = libmgmt_protocol.device_spec_parser(pldm_config_func(endpoint))
    end)
end

local function stop_ncsi_schedulers(resource_obj)
    for _, s in ipairs(resource_obj.ncsi_schedulers) do
        s:deconstruct()
    end
    resource_obj.ncsi_schedulers = {}
    -- 停止对应网口的轮询操作
    local ports = c_network_port.collection:fetch_by_position(resource_obj:get_position())
    for _, port in pairs(ports) do
        port:stop()
    end
end

local function init_ncsi_over_mctp_signal_monitor(resource_obj, ncsi_config_func, ports)
    -- 接收到bmc重启信号，BMC重启这种只触发一次，且如果在超时前已经有其它模式触发收集则不再收集
    resource_obj:connect_signal(log_collector.log_dump_reset_bmc_sig, function()
        if not resource_obj.ncsi_has_collected and resource_obj.ncsi_collect_status == card_defs.LOG_DUMP_IDLE then
            log:info('receive bmc reset signal')
            resource_obj.ncsi_collect_status = card_defs.LOG_DUMP_BUSY
            card_resource_ncsi_collect_log.collect_log_by_ncsi_task(resource_obj)
            resource_obj.ncsi_collect_status = card_defs.LOG_DUMP_IDLE
        end
    end)
    -- 接收到os重启信号，只要没有任务正在收集则可以启动收集
    resource_obj:connect_signal(log_collector.log_dump_reset_smbios_sig, function()
        -- 清理之前的scheduler
        stop_ncsi_schedulers(resource_obj)
        -- 清理之前的transport endpointls
        clean_transport_endpoint(resource_obj, mctp_lib.MCTP_MESSAGE_TYPE_NCSI)
        -- 再次创建endpoint
        local endpoint = create_mctp_endpoint(resource_obj, mctp_lib.MCTP_MESSAGE_TYPE_NCSI)
        resource_obj.ncsi_config_obj = libmgmt_protocol.device_spec_parser(ncsi_config_func(endpoint))
        card_resource_ncsi_update.update_ncsi_properties(resource_obj, ports)

        if resource_obj.ncsi_collect_status == card_defs.LOG_DUMP_IDLE then
            log:info('receive os reset signal')
            resource_obj.ncsi_collect_status = card_defs.LOG_DUMP_BUSY
            card_resource_ncsi_collect_log.collect_log_by_ncsi_task(resource_obj)
            resource_obj.ncsi_collect_status = card_defs.LOG_DUMP_IDLE
        end
    end)
end

local function init_std_smbus(resource_obj, std_smbus_config_func)
    local std_smbus_config_obj = std_smbus_config_func.new(resource_obj.RefChip)
    -- 获取std_smbus能力码
    local ok, resp = pcall(std_smbus_config_obj.GetCapability, std_smbus_config_obj)
    if not ok then
        log:error('[MCU] get capability failed, %s', resp)
    end
    -- 设置电子标签
    ok = pcall(function()
        std_smbus_config_obj:GetNetElabel(function(elabel)
            local prop = {}
            local value = {}
            for elabel_prop, val in pairs(elabel) do
                table.insert(prop, elabel_prop)
                table.insert(value, val)
            end
            resource_obj.RefFrudata:Update(ctx.new(), prop, value)
        end)
    end)
    if not ok then
        log:error('[dpu] set frudata failed, msg: %s', resp)
    end
    local data
    -- 更新PfMacInfo
    resource_obj:next_tick(function()
        while true do
            data = std_smbus_config_obj:GetMacAddress()
            resource_obj.PfMacInfo = data
            resource_obj:sleep_ms(5000) -- 等待5秒钟
        end
    end)
end

function card_resource_init_protocol.init_protocol(resource_obj, hardware_config, ports)
    if hardware_config.smbus and resource_obj.RefChip and resource_obj.RefChip.WriteRead then
        resource_obj:next_tick(init_smbus, resource_obj, hardware_config.smbus, ports)
        resource_obj:next_tick(init_smbus_signal_monitor, resource_obj)
    end
    if hardware_config.mctp_pldm and resource_obj.SupportedMctp then
        resource_obj:next_tick(card_resource_init_protocol.init_pldm, resource_obj, hardware_config.mctp_pldm)
        resource_obj:next_tick(init_pldm_over_mctp_signal_monitor, resource_obj, hardware_config.mctp_pldm)
    end
    if hardware_config.mctp and resource_obj.SupportedMctp then
        resource_obj:next_tick(card_resource_init_protocol.init_ncsi, resource_obj, hardware_config.mctp, ports)
        resource_obj:next_tick(init_ncsi_over_mctp_signal_monitor, resource_obj, hardware_config.mctp, ports)
    end
    if hardware_config.lldp and resource_obj.SupportedLLDP then
        resource_obj:next_tick(card_resource_init_protocol.setup_vdpci_lldp_listener, resource_obj, ports)
    end
    if hardware_config.std_smbus and resource_obj.RefChip and resource_obj.RefChip.WriteRead then
        resource_obj:next_tick(init_std_smbus, resource_obj, hardware_config.std_smbus)
    end
end

return card_resource_init_protocol