-- 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 log = require 'mc.logging'
local cmn = require 'common'
local mdb = require 'mc.mdb'
local client = require 'pcie_device.client'
local mc_signal = require 'mc.signal'

local DEFAULT_INTERFACE<const> = 'bmc.kepler.Systems.PCIeDevices.PCIeDevice'
---@class PCIeDevice @PCIe设备
---@field public mds table @MDS对象
local c_pcie_device = class()

function c_pcie_device:get_prop(name, interface)
    if not self.mds or not name then
        return nil
    end

    if interface and self.mds[interface] then
        return self.mds[interface][name]
    elseif self.mds[name] then
        return self.mds[name]
    elseif self.mds[DEFAULT_INTERFACE] then
        return self.mds[DEFAULT_INTERFACE][name]
    end

    return nil
end

function c_pcie_device:set_prop(name, val, interface)
    if not self.mds or not name then
        return nil
    end

    if interface and self.mds[interface] and self.mds[interface][name] ~= nil then
        self.mds[interface][name] = val
    elseif self.mds[name] then
        self.mds[name] = val
    elseif self.mds[DEFAULT_INTERFACE] and self.mds[DEFAULT_INTERFACE][name] ~= nil then
        self.mds[DEFAULT_INTERFACE][name] = val
    end

    return nil
end

function c_pcie_device:update_bus_info(segment, socket_id, bus, device, func)
    self.mds.Segment = segment
    self.mds.SocketID = socket_id
    self.mds.Bus = bus
    self.mds.Device = device
    self.mds.Function = func
end

-- 从PcieAddrInfo对象同步属性值到当前PCIeDevice对象
-- 1. 关联PcieAddrInfo对象，对其属性变化建立监听
-- 2. 立即同步一次属性值
-- 3. 释放同步完成信号：sync_pai_to_device_complete
function c_pcie_device:sync_pcie_addr_info(pcie_addr_info_obj)
    -- PcieAddrInfo Property : PCIeDevice Property
    local prop_map = {
        ['SlotID'] = function (value)
            self:set_prop('SlotID', value)
            self:update_pciedevice_devicename()
        end,
        ['Segment'] = 'Segment',
        ['SocketID'] = 'SocketID',
        ['Bus'] = 'Bus',
        ['Device'] = 'Device',
        ['Function'] = 'Function',
        ['DevBus'] = 'DevBus',
        ['DevDevice'] = 'DevDevice',
        ['DevFunction'] = 'DevFunction',
        ['MultihostPresence'] = 'MultihostPresence'
    }
    -- 注册属性变化的监听
    pcie_addr_info_obj.mds_obj.property_changed:on(
        function(name, value, sender)
            if name == 'SlotID' then
                prop_map[name](value)
                return
            end
            if prop_map[name] then
                self:set_prop(prop_map[name], value)
            end
        end)
    -- 同步一次属性值
    local value
    for addr_prop_name, device_prop_name in pairs(prop_map) do
        if type(device_prop_name) == 'string' then
            log:notice('sync_pcie_addr_info, name=%s, value=%s, position=%s, src_name=%s', device_prop_name,
                pcie_addr_info_obj:get_prop(addr_prop_name), self.position, pcie_addr_info_obj.mds_obj.name)
            self:set_prop(device_prop_name, pcie_addr_info_obj:get_prop(addr_prop_name))
        else
            value = pcie_addr_info_obj:get_prop(addr_prop_name)
            log:notice('sync_pcie_addr_info, name=%s, value=%s, position=%s, src_name=%s', addr_prop_name, value,
                self.position, pcie_addr_info_obj.mds_obj.name)
            local ok, err = pcall(function()
                prop_map[addr_prop_name](value)
            end)
            if not ok then
                log:warn('sync_pcie_addr_info, not ok, err=%s', err)
            end
        end
    end
    self.ref_pcie_addr_info = pcie_addr_info_obj

    -- 释放完成信号
    self.sync_pai_to_device_complete:emit(self)

    log:notice('Sync PCIeAddrinfo completed, PCIeDevice position=%s', self.position)
end

-- 获取Container对象，默认为上级器件的Component对象
function c_pcie_device:get_container_obj()
    local COMPONENT_PRE_PATH = '/bmc/kepler/Systems/' .. self.system_id .. '/Components/'
    local COMPONENT_INTERFACE = 'bmc.kepler.Systems.Component'

    local container_obj_name = self:get_prop('Container')
    if not container_obj_name or container_obj_name == '' then
        return false
    end
    -- 获取对应riser卡的Component 间隔5秒重试一次 最多100次
    local ok, obj = cmn.retry_func(500, 100, function()
        return pcall(mdb.get_object, self.bus, COMPONENT_PRE_PATH .. container_obj_name,
            COMPONENT_INTERFACE)
    end)
    if not ok then
        log:error('Get container obj failed, err=%s', obj)
        return false
    end
    return obj
end

-- 通过Container属性更新Position
function c_pcie_device:update_pciedevice_position()
    local container = self:get_container_obj()
    if not container then
        return
    else
        local position = container.Name
        self:set_prop('Position', position)
        -- 监听Name的变化
        client:OnComponentPropertiesChanged(function(values, path)
            if path ~= container.path or not values['Name'] then
                return
            end
            self:set_prop('Position', values['Name']:value())
        end)
    end
end

-- 更新DeviceName
function c_pcie_device:update_pciedevice_devicename()
    local device_name = self.origin_name
    local slot_id = self:get_prop('SlotID')
    if device_name == nil or device_name == '' then
        device_name = 'PCIe Card ' .. tostring(slot_id)
        self:set_prop('DeviceName', device_name)
    else
        device_name = string.gsub(device_name, '%$', tostring(slot_id), 1) -- 1表示替换1次
        self:set_prop('DeviceName', device_name)
    end
    self.device_name_ready = slot_id ~= 0
end

local function get_system_id(path)
    return tonumber(string.match(path, '/bmc/kepler/Systems/(%d+)'))
end 

function c_pcie_device:init()
    self.system_id = get_system_id(self.mds.path)
    cmn.skynet.fork(function ()
        self:update_pciedevice_position()
    end)
end

function c_pcie_device:ctor(mds_obj, position, bus)
    self.mds = mds_obj
    self.position = position
    self.bus = bus
    self.listening = {}
    self.device_name_ready = false
    self.origin_name = self.mds['DeviceName']
    -- 信号：从PcieAddrInfo对象同步属性值完成
    self.sync_pai_to_device_complete = mc_signal.new()
end

return c_pcie_device
