-- 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 bs = require 'mc.bitstring'
local cmn = require 'common'
local log = require 'mc.logging'
local ctx = require 'mc.context'
local object_pool = require 'object_pool'
local _, skynet = pcall(require, 'skynet')
local cmds = {}
-- TOPO检测SMC命令响应体
-- bcu_index 不为0即采用解析方式区分主从板， 主板1， 从板2
local bs_topo_resp = bs.new([[<<
    target_port_id:8,
    index:8,
    uid:24/string,
    port_id:8,
    _:1/string,
    bcu_index:8,
    _:3/string
>>]])

local ZONE_A <const> = 'A'
local ZONE_B <const> = 'B'
local ZONE_C <const> = 'C'
local ZONE_D <const> = 'D'
local ZONE_E <const> = 'E'
local ZONE_F <const> = 'F'

-- TOPO检测SMC命令字
local TOPO_CMD_OFFSET <const> = {
    [ZONE_A] = 0xc005100,
    [ZONE_B] = 0xc005500,
    [ZONE_C] = 0xc005900,
    [ZONE_D] = 0xc005d00,
    [ZONE_E] = 0xc006100,
    [ZONE_F] = 0xc006500
}

local TOPO_RESP_LEN <const> = 32           -- 单条响应数据大小为32Byte
local RESP_NUM_PER_ZONE <const> = 32       -- 完整的响应数量为32
local TOPO_RESP_LEN_PER_ZONE <const> = 1024 -- 完整的为32*32 Byte
local RETRY_TIMES <const> = 3              -- SMC数据读取异常重试次数
local DEFAULT_MASK<const> = 0xffffffff
local BLOCK_ACCESS_TYPE<const> = 1

local function chip_read(chip, offset, len)
    log:debug("plugins chip read cmd%s   len%s", offset, len)
    local input = object_pool.new('input_args', offset, DEFAULT_MASK, BLOCK_ACCESS_TYPE, len, nil)
    return chip:read(input)
end

local function topo_read(chip, offset, len)
    if not chip then
        log:error('[BizTopoMonitor] Chip is nil.')
        return false
    end
    local ok, ret1, ret2
    for _ = 1, RETRY_TIMES do
        ok, ret1 = pcall(function()
            return chip_read(chip, offset, len)
        end)
        if not ok then
            log:error('[BizTopoMonitor] Read topo error, %s', ret1)
            goto next
        end
        skynet.sleep(100) -- 两次读取之间延迟1000ms
        ok, ret2 = pcall(function()
            return chip_read(chip, offset, len)
        end)
        if not ok then
            log:error('[BizTopoMonitor] Read topo error, %s', ret2)
            goto next
        end
        -- 比较两次读取结果，若不同则重新读取
        if ret1 == ret2 then
            break
        else
            ok = false
        end
        ::next::
    end

    return ok, ret1
end

local function is_resp_valid(resp)
    return resp.target_port_id ~=0 and resp.target_port_id ~= 0xff
end

function cmds.plugin_read_topo_info(chip, zone_support)
    local topo_info = {}
    local ok, data, resp_bin, resp
    if not zone_support then
        log:error("zone_support is nil")
        return false, {}
    end

    for zone, offset in pairs(TOPO_CMD_OFFSET) do
        if not zone_support[zone] then
            goto next
        end
        topo_info[zone] = {}
        ok, data = topo_read(chip, offset, TOPO_RESP_LEN_PER_ZONE)
        if not ok then
            log:error("read topo fail")
            return false, {}
        end
        for i = 1, RESP_NUM_PER_ZONE do
            if #data < i * TOPO_RESP_LEN then
                break
            end
            resp_bin = string.sub(data, (i - 1) * TOPO_RESP_LEN + 1, i * TOPO_RESP_LEN)
            resp = bs_topo_resp:unpack(resp_bin)
            -- target_port_id为0判断为无效信息
            if is_resp_valid(resp) then
                topo_info[zone][resp.port_id] = topo_info[zone][resp.port_id] and topo_info[zone][resp.port_id] or {}
                --天池规范 resp.bcu_index 默认为0，系列化特殊场景为1、2区分主从板
                topo_info[zone][resp.port_id][resp.bcu_index] = resp
            end
        end
        ::next::
    end
    
    return ok, topo_info
end

local pcie_device_plugins = class()

function pcie_device_plugins:ctor()
    log:notice('[pcie_device_plugins] ctor')
end

function pcie_device_plugins:has_cmd(cmd_name)
    return cmds[cmd_name] ~= nil
end

function pcie_device_plugins:run_cmd(chip, cmd, ...)
    log:debug('[pcie_device_plugins] run cmd[%s]', cmd)
    return cmds[cmd](chip, ...)
end

return pcie_device_plugins