-- Copyright (c) 2025 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 org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local property_changed = org_freedesktop_dbus.ObjMgrPropertiesChanged
local match_rule = org_freedesktop_dbus.MatchRule
local comm_defs = require 'device_mgmt.comm_defs'
local comm_fun = require 'device_mgmt.comm_fun'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local c_device_service = require 'device.device_service'
local cmn = require 'common'
local clsmgmt = require 'mc.class_mgnt'
local c_object = require 'mc.orm.object'
local c_pcie_device = c_object('PCIeDevice')

local pcie_device_mgmt = class()

-- 设备树对象属性到资源树对象属性的映射
local PROP_TABLE = {
    ["bmc.dev.PCIeDevice"] = {
        ["DeviceName"] = 'DeviceName',
        ["FunctionClass"] = 'FunctionClass',
        ["DeviceType"] = 'PCIeDeviceType',
        ["SlotType"] = 'SlotType',
        ["FunctionProtocol"] = 'FunctionProtocol',
        ["FunctionType"] = 'FunctionType',
        ["Container"] = 'Container',
        ["ComponentType"] = 'DeviceType',
        ["RefComponent"] = 'RefComponent'
    }
}

-- 资源树对象属性到设备树属性的映射
local RESOURCE_PROP_TABLE = {
    ["SlotID"] = 'Slot',
    ["Segment"] = 'Segment',
    ["SocketID"] = 'SocketId',
    ["Bus"] = function(device_obj, v)
        device_obj["Bus"] = v
        return device_obj["Bus"] == v
    end,
    ["Device"] = 'Device',
    ["Function"] = 'Function',
    ["DevBus"] = 'DevBus',
    ["DevDevice"] = 'DevDevice',
    ["DevFunction"] = 'DevFunction',
    ["DiagnosticFault"] = 'DiagnosticFault',
    ["PredictiveFault"] = 'PredictiveFault',
    ["UCEByBIOS"] = 'UCEByBIOS',
    ["BandwidthReduction"] = 'BandwidthReduction',
    ["LinkSpeedReduced"] = 'LinkSpeedReduced',
    ["DeviceName"] = 'DeviceName', -- 在替换槽位后设置回设备树
    ["Position"] = 'Location',
    ["GroupPosition"] = 'GroupPosition' -- 这个参数只在资源树有，设备树需要从资源树拿
}

local PATH_PATTERN = {
    comm_defs.PCIE_NIC_CARD_PATH_PATTERN,
    comm_defs.PCIE_GPU_CARD_PATH_PATTERN
}

function c_pcie_device.create_mdb_object(app, pcie_device_object_name, object_position)
    return app:CreatePCIeDevice(1, pcie_device_object_name, function(obj)
            obj.ObjectName = pcie_device_object_name
            obj.GroupPosition = pcie_device_object_name
            obj.ObjectIdentifier = {
                1, "1", "1", object_position
            }
            end)
end

-- 同步设备树属性到资源树对象
function pcie_device_mgmt:sync_dev_prop_to_resource_obj(resource_obj, interface, prop, value, device_path)
    if prop == "DeviceName" and self.dev_obj_device_name_init[device_path] then
        return
    elseif prop == "DeviceName" and not self.dev_obj_device_name_init[device_path] then
        self.dev_obj_device_name_init[device_path] = true
    end
    if PROP_TABLE[interface] and PROP_TABLE[interface][prop] then
        local ok
        repeat
            log:info('[device_mgmt] sync_dev_prop_to_resource_obj intf=%s prop=%s value=%s', interface, prop, value)
            if type(PROP_TABLE[interface][prop]) == 'string' then
                resource_obj[PROP_TABLE[interface][prop]] = value
                ok = resource_obj[PROP_TABLE[interface][prop]] == value
            else
                ok = PROP_TABLE[interface][prop](resource_obj, value)
            end
            if not ok then
                cmn.skynet.sleep(1)
            end
        until ok
    end
end

-- 同步资源树属性到设备树对象
function pcie_device_mgmt:sync_resource_prop_to_dev_obj(dev_obj, prop, value)
    if RESOURCE_PROP_TABLE[prop] then
        local ok
        repeat
            log:info('[device_mgmt] sync_resource_prop_to_dev_obj prop=%s value=%s', prop, value)
            if type(RESOURCE_PROP_TABLE[prop]) == 'string' then
                dev_obj[RESOURCE_PROP_TABLE[prop]] = value
                ok = dev_obj[RESOURCE_PROP_TABLE[prop]] == value
            else
                ok = RESOURCE_PROP_TABLE[prop](dev_obj, value)
            end
            if not ok then
                cmn.skynet.sleep(1)
            end
        until ok
    end
end

function pcie_device_mgmt:listen_device_obj_property_change(bus, sig_slot, device_path, resource_obj)
    log:info('[device_mgmt]listen_device_obj_property_change start, device_path is %s', device_path)
    local property_changed_sig = match_rule.signal(property_changed.name, property_changed.interface):with_path(
        device_path)
    sig_slot[#sig_slot + 1] = bus:match(property_changed_sig, function(msg)
        local interface, props = msg:read('sa{sv}as')
        local sync_ok, sync_ret
        for k, v in pairs(props) do
            -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
            sync_ok, sync_ret = pcall(function()
                self:sync_dev_prop_to_resource_obj(resource_obj, interface, k, v:value(), device_path)
            end)
            if not sync_ok then
                log:error('[device_mgmt] listen_device_obj_property_change error=%s', sync_ret)
            end
        end
    end)
end

function pcie_device_mgmt:synchronize_property(bus, device_path, resource_obj)
    log:info('[device_mgmt]synchronize_property start, device_path is %s', device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        device_path,
        { comm_defs.PCIE_DEVICE_INTERFACE })
    local device_obj, props
    local sync_ok, sync_ret
    for service_name, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            device_obj = mdb.get_object(bus, device_path, interface)
            props = bus:call(service_name, device_path, comm_defs.ORG_PROPERTIES_INTERFACE, 'GetAll', 's', interface)
            for prop in pairs(props) do
                -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
                sync_ok, sync_ret = pcall(function()
                    self:sync_dev_prop_to_resource_obj(resource_obj, interface, prop,
                        comm_fun.get_prop(device_obj, prop), device_path)
                end)
                if not sync_ok then
                    log:error('[device_mgmt] listen_device_obj_property_change error=%s', sync_ret)
                end
            end
        end
    end
end

function pcie_device_mgmt:get_object_name_by_device_path(bus, device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        device_path, { comm_defs.OBJECT_PROPERTIES_INTERFACE })
    local device_obj
    for ret_key, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            log:notice('[device_mgmt]get_object_name_by_device_path ret_key=%s interface=%s', ret_key, interface)
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            device_obj = mdb.get_object(bus, device_path, interface)
            goto continue
        end
    end
    ::continue::
    if not device_obj then
        log:error('[device_mgmt]get_object_name_by_device_path failed, device_path is %s', device_path)
        return
    end
    local object_name = comm_fun.get_prop(device_obj, 'ObjectName')
    log:notice('[device_mgmt]get_object_name_by_device_path, object_name is %s', object_name)

    return object_name
end

function pcie_device_mgmt:create_resource_obj(bus, device_path)
    log:notice('[device_mgmt]create_resource_obj start, device_path is %s', device_path)
    local object_name = self:get_object_name_by_device_path(bus, device_path)
    if not object_name then
        log:error('[device_mgmt]create_resource_obj failed, because object_name is nil, device_path is %s', device_path)
        return
    end
    -- 创建资源树对象 SystemId默认1 Id是Object_name
    local ok, ret, position = pcall(function()
        local pcie_device_object_name = comm_fun.get_device_name_by_object_name(object_name,
            comm_defs.PCIE_DEVICE_CLASS_NAME)
        local object_position = comm_fun.get_position_by_object_name(pcie_device_object_name)
        log:notice('[device_mgmt]create_resource_obj, pcie_device_object_name is %s', pcie_device_object_name)
        -- c_pcie_device调用了框架机制，在创建资源树对象后使用new又创建了orm对象，其能把资源树的更改同步到数据库
        local resource_obj = c_pcie_device.create_mdb_object(self.app, pcie_device_object_name, object_position)
        c_pcie_device.new(resource_obj, 0, {GroupPosition = pcie_device_object_name})
        return resource_obj, object_position
    end)
    if not ok or not ret then
        log:error('[device_mgmt]create_resource_obj failed, device_path=%s ret=%s', device_path, ret)
        return
    else
        log:notice('[device_mgmt]create_resource_obj success, device_path is %s', device_path)
        return ret, position
    end
end

function pcie_device_mgmt:on_add_device_obj()
    log:notice('[device_mgmt]on_add_device_obj start')
    for _, path_pattern in pairs(PATH_PATTERN) do
        comm_fun.set_interface_add_signal(self.bus, self.sig_slot, path_pattern, comm_defs.PCIE_DEVICE_INTERFACE,
            function(path)
                self:init_obj(path)
            end)
    end
end

function pcie_device_mgmt:init_obj(device_path)
    -- 用pcall包裹，避免某一个网卡初始化失败导致循环结束
    pcall(function()
        if self.objects[device_path] then 
            log:notice('[device_mgmt]init_obj already exist, device_path is %s', device_path)
            return 
        end
        -- 记录设备树对象的ObjectName
        local dev_obj = comm_fun.get_device_obj_by_path(self.bus, device_path, {comm_defs.OBJECT_PROPERTIES_INTERFACE})
        self.dev_obj_name_paths[device_path] = comm_fun.get_prop(dev_obj, 'ObjectName')
        self.dev_obj_set_dev_bus_done[device_path] = false -- 标记当前设备树对象是否调用过SetDeviceBDF
        self.dev_obj_device_name_init[device_path] = false
        
        -- 创建资源树对象
        local resource_obj, position = self:create_resource_obj(self.bus, device_path)
        if resource_obj then
            self.objects[device_path] = resource_obj
            -- 先启监听设备树对象属性变化，更新对应的资源树对象属性
            self:listen_device_obj_property_change(self.bus, self.sig_slot, device_path, resource_obj)
            -- 再将设备树对象属性赋值给对应资源树对象
            self:synchronize_property(self.bus, device_path, resource_obj)
            
            -- 调用组件回调
            c_device_service.get_instance():on_add_object("PCIeDevice", resource_obj, position)
            cmn.skynet.sleep(100)
            c_device_service.get_instance():on_add_object_complete(position)
            -- 同步资源树对象属性到设备树对象属性
            cmn.skynet.sleep(100)
            self:sync_all_resource_prop_to_dev_obj(resource_obj, device_path)
        end
    end)
end

function pcie_device_mgmt:on_del_device_obj()
    log:notice('[device_mgmt]on_del_device_obj start')
    for _, path_pattern in pairs(PATH_PATTERN) do
        comm_fun.set_interface_del_signal(self.bus, self.sig_slot, path_pattern, comm_defs.PCIE_DEVICE_INTERFACE,
            function(path)
                self:del_obj(path)
            end)
    end
end

function pcie_device_mgmt:del_obj(device_path)
    pcall(function()
        if not self.objects[device_path] then 
            log:notice('[device_mgmt]del_obj not exist, device_path is %s', device_path)
            return 
        end
        self.dev_obj_name_paths[device_path] = nil
        self.dev_obj_set_dev_bus_done[device_path] = nil
        self.dev_obj_device_name_init[device_path] = nil

        local resource_obj = self.objects[device_path]
        local position = resource_obj.ObjectIdentifier[comm_defs.POSITION_INDEX]
        local object_name = resource_obj.ObjectName
        log:notice('[device_mgmt]del_obj class_name is PCIeDevice, object_name is %s', object_name)
        -- 删除资源树对象
        clsmgmt(comm_defs.PCIE_DEVICE_CLASS_NAME):remove(resource_obj)
        -- 调用组件回调
        c_device_service.get_instance():on_delete_object(comm_defs.PCIE_DEVICE_CLASS_NAME, resource_obj, position)
        self.objects[device_path] = nil
    end)
end

function pcie_device_mgmt:init_check_obj()
    for _, path_pattern in pairs(PATH_PATTERN) do
        local device_paths = comm_fun.get_all_device_paths(self.bus, path_pattern, 1,
            comm_defs.PCIE_DEVICE_INTERFACE)
        for _, device_path in pairs(device_paths) do
            log:notice('[device_mgmt]get device_path: %s', device_path)
            self:init_obj(device_path)
        end
    end
end

-- 判断DevBus是否发生变化，如果变化就调用SetDeviceBDF
function pcie_device_mgmt:set_device_bdf(dev_path, device_obj, prop, value)
    if prop ~= 'DevBus' or value == 0 or self.dev_obj_set_dev_bus_done[dev_path] then
        return
    end
    if self.objects[dev_path] then
        local resource_obj = self.objects[dev_path]
        local ok, rsp_code = pcall(function()
            return device_obj:call_method("SetDeviceBDF", "yyy", resource_obj["DevBus"],
                resource_obj["DevDevice"], resource_obj["DevFunction"])
        end)
        if not ok or not rsp_code then
            log:error('[device_mgmt]SetDeviceBDF failed, rsp_code=%s b=%s d=%s f=%s', rsp_code,
                resource_obj["DevBus"], resource_obj["DevDevice"], resource_obj["DevFunction"])
        else
            log:notice('[device_mgmt]SetDeviceBDF success, rsp_code=%s b=%s d=%s f=%s', rsp_code,
                resource_obj["DevBus"], resource_obj["DevDevice"], resource_obj["DevFunction"])
            self.dev_obj_set_dev_bus_done[dev_path] = true
        end
    end
end

function pcie_device_mgmt:set_device_bdf_init()
    local ok, rsp_code, device_obj, resource_obj
    for dev_path, is_done in pairs(self.dev_obj_set_dev_bus_done) do
        if not is_done and self.objects[dev_path] and self.objects[dev_path]["DevBus"] ~= 0 then
            device_obj = comm_fun.get_device_obj_by_path(self.bus, dev_path, 
                {comm_defs.PCIE_DEVICE_INTERFACE})
            resource_obj = self.objects[dev_path]
            self:set_device_bdf(dev_path, device_obj, "DevBus", resource_obj["DevBus"])
        end
    end
end

function pcie_device_mgmt:sync_all_resource_prop_to_dev_obj(resource_obj, dev_path)
    local ok, ret = pcall(function()
        local dev_obj = comm_fun.get_device_obj_by_path(self.bus, dev_path, {comm_defs.PCIE_DEVICE_INTERFACE})
        for res_prop, dev_prop in pairs(RESOURCE_PROP_TABLE) do
            -- 设置设备树对象属性
            self:sync_resource_prop_to_dev_obj(dev_obj, res_prop, resource_obj[res_prop])
            -- 调用设备树方法
            self:set_device_bdf(dev_path, dev_obj, res_prop, resource_obj[res_prop])
        end
    end)
    if not ok then
        log:error('[device_mgmt]sync_all_resource_prop_to_dev_obj err=%s', ret)
    end
end

-- PCIeDevice对象变化，设置回设备树对象
function pcie_device_mgmt:on_dev_prop_changed()
    c_device_service.get_instance().dev_prop_changed:on(function (object_name, prop, value)
        if not RESOURCE_PROP_TABLE[prop] then
            log:debug('[device_mgmt]on_dev_prop_changed not include, prop=%s', prop)
            return
        end

        local res_position = comm_fun.get_position_by_object_name(object_name) -- 资源树对象 position
        local dev_obj, dev_obj_position, ok, ret
        for dev_path, dev_obj_name in pairs(self.dev_obj_name_paths) do
            if comm_fun.get_position_by_object_name(dev_obj_name) == res_position then
                -- 匹配到对应的设备树对象，获取设备树对象
                dev_obj = comm_fun.get_device_obj_by_path(self.bus, dev_path, 
                    {comm_defs.PCIE_DEVICE_INTERFACE})
                ok, ret = pcall(function()
                    -- 设置设备树对象属性
                    self:sync_resource_prop_to_dev_obj(dev_obj, prop, value)
                    -- 调用设备树方法
                    self:set_device_bdf(dev_path, dev_obj, prop, value)
                end)
                if not ok then
                    log:error('[device_mgmt]on_dev_prop_changed err=%s', ret)
                end
                break
            end
        end
    end)
end

function pcie_device_mgmt:ctor(app)
    self.app = app
    self.bus = app.bus
    self.objects = {}  -- 设备树对象路径对应资源树对象
    self.sig_slot = {} -- 存放设备树对象相关信号
    self.dev_obj_name_paths = {} -- 设备树对象路径对应ObjectName
    self.dev_obj_set_dev_bus_done = {} -- 设备树对象路径对应是否触发设备BDF监听
    self.dev_obj_device_name_init = {} -- 设备树对象DeviceName属性获取过一次
end

function pcie_device_mgmt:init()
    log:notice('[device_mgmt]pcie_device_mgmt init')
    local ok, ret = pcall(function()
        -- 先监听网卡/GPU卡设备树对象新增信号
        self:on_add_device_obj()
        self:on_del_device_obj()
        -- 获取所有的网卡/GPU设备树对象，创建对应的资源树对象
        self:init_check_obj()
        -- 监听所有PCIeDevice对象属性变化
        self:on_dev_prop_changed()
        -- 查询PCIeDevice对象属性，调用设备树方法
        self:set_device_bdf_init()
    end)
    if not ok then
        log:error('[device_mgmt]init failed, err=%s', ret)
    end
end

return singleton(pcie_device_mgmt)
