-- 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 mdb_service = require 'mc.mdb.mdb_service'
local log = require 'mc.logging'
local cjson = require 'cjson'

local m = {}

local BUSINESSPORT_CARD_TYPE_VIR<const> = 6
local BUSINESSPORT_CARD_TYPE_TEAM<const> = 7
local BUSINESSPORT_CARD_TYPE_BRIDGE<const> = 8

function m.get_adapter_ids(data)
    if not data then
        return
    end

    local ids = {}
    for _, v in ipairs(data) do
        local obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.NetworkAdapter')
        ids[#ids + 1] = obj.ID
    end

    return ids
end

function m.get_port_ids(data)
    if not data then
        return
    end

    local ids = {}
    for _, v in ipairs(data) do
        local obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.NetworkPort')
        ids[#ids + 1] = obj.PortID + 1
    end

    return ids
end

local system2port_map = {
    [1] = { 1, 3 },
    [2] = { 2, 4 },
    [3] = { 1, 1 },
    [4] = { 2, 2 },
    [5] = { 2, 1 },
    [6] = { 1, 2 },
    [7] = { 2, 3 },
    [8] = { 1, 4 }
}

function m.get_adapter_ids_from_port_paths(port_paths, objs, system_id)
    if not port_paths then
        return
    end

    local ids = {}
    local obj = mdb.get_object(bus, '/bmc/kepler/Managers/1/Multihost', 'bmc.kepler.Managers.Multihost')
    local portid, port_obj, network_adapter_id
    if obj.HostType == 'Multihost' then
        -- 先添加1823网口
        for _, port_path in ipairs(objs) do
            port_obj = mdb.get_object(bus, port_path, 'bmc.kepler.Systems.NetworkPort')
            network_adapter_id = port_obj.NetworkAdapterId
            portid = port_obj.PortID + 1
            if string.find(network_adapter_id, 'ROH') then
                goto continue
            end
            if system2port_map[system_id][2] == tonumber(portid) and
                system2port_map[system_id][1] == tonumber(string.match(network_adapter_id, "%d+")) then
                ids[#ids + 1] = network_adapter_id .. 'Port' .. portid
            end
            ::continue::
        end
        -- 添加直出网口
        for _, port_path in pairs(port_paths) do
            port_obj = mdb.get_object(bus, port_path, 'bmc.kepler.Systems.NetworkPort')
            portid = port_obj.PortID + 1
            network_adapter_id = port_obj.NetworkAdapterId
            if not string.find(network_adapter_id, 'ROH') then
                goto continue
            end
            if port_obj.SystemID == tonumber(system_id) then
                ids[#ids + 1] = string.format('%sPort%s', network_adapter_id, portid)
            end
            ::continue::
        end
        return ids
    end
    for _, port_path in ipairs(port_paths) do
        port_obj = mdb.get_object(bus, port_path, 'bmc.kepler.Systems.NetworkPort')
        if port_obj.Type == BUSINESSPORT_CARD_TYPE_VIR or
            port_obj.Type == BUSINESSPORT_CARD_TYPE_TEAM or
            port_obj.Type == BUSINESSPORT_CARD_TYPE_BRIDGE then
            ids[#ids + 1] = port_obj.NetworkAdapterId
        else
            ids[#ids + 1] = port_obj.NetworkAdapterId .. 'Port' .. (port_obj.PortID + 1)
        end
    end
    return ids
end

function m.get_optical_modules(system_id)
    local depth = 4 -- 查找光模块子路径深度
    local path = string.format('/bmc/kepler/Systems/%s/NetworkAdapters', system_id)
    local optical_modules = mdb_service.get_sub_paths(bus, path, depth, {'bmc.kepler.Systems.OpticalModule'}).SubPaths
    local port_path, obj
    local res = {}
    for _, optical_module_path in ipairs(optical_modules) do
        obj = mdb.get_object(bus, optical_module_path, 'bmc.kepler.Systems.OpticalModule')
        if obj == nil or obj.Presence ~= 1 then
            goto continue
        end

        port_path = string.match(optical_module_path, '(.*)/OpticalModule')
        if port_path == nil then
            goto continue
        end
        obj = mdb.get_object(bus, port_path, 'bmc.kepler.Systems.NetworkPort')
        if obj == nil or obj.MediumType ~= 'FiberOptic' then
            goto continue
        end

        res[#res + 1] = optical_module_path

        ::continue::
    end

    return res
end

local function get_multihost_system_id(card_id, port_id)
    local system_id_table = {
       [1] = {[1] = 3, [2] = 6, [3] = 1, [4] = 8},
       [2] = {[3] = 7, [4] = 2, [1] = 5, [2] = 4}
    }
    return system_id_table[card_id][port_id]
end

function m.is_valid_system_id(system_id, card_id, port_id, port_path)
    local obj = mdb.get_object(bus, '/bmc/kepler/Managers/1/Multihost', 'bmc.kepler.Managers.Multihost')
    if obj.HostType == 'Multihost' then
        local port_obj = mdb.get_object(bus, port_path, 'bmc.kepler.Systems.NetworkPort')
        if not port_obj then
            return false
        end
        local network_adapter_id = port_obj.NetworkAdapterId
        -- 直出网口只需判断传入的SystemID是否与网口对象的SystemID相等
        if string.find(network_adapter_id, 'ROH') then
            return tonumber(system_id) == port_obj.SystemID
        end
        -- 1823网口按映射表判断SystemID是否合法
        if not card_id or not port_id then
            return false
        end
        return get_multihost_system_id(card_id, port_id + 1) == tonumber(system_id)
    else
        local ok, rsp = pcall(mdb_service.is_valid_path, bus, '/bmc/kepler/Systems/' .. system_id)
        if not ok then
            log:error('Invalid SystemId(%s), err(%s)', system_id, rsp.message)
            error(rsp)
        end
        return rsp.Result
    end
end

function m.encode_prbs_config_data(request_data)
    local config_list = cjson.json_object_new_array()
    local config = nil
    -- 入参类型是userdata，需要重新取出，插入到table中
    for _, item in pairs(request_data) do
        config = cjson.json_object_new_object()
        for cfg_name, cfg_val in pairs(item) do
            config[cfg_name] = cfg_val
        end
        config_list[#config_list + 1] = config
    end
    return cjson.json_object_ordered_encode(config_list)
end

function m.decode_table_data(response_data)
    local ok, data_table = pcall(cjson.decode, response_data)
    if not ok then
        log:error("Invalid response, need json format")
        return nil
    end
    return data_table
end
    
function m.get_adapter_count_from_ib_port_paths(port_paths)
    if not port_paths then
        return
    end

    local count = 0
    for _, port_path in ipairs(port_paths) do
        local port_obj = mdb.get_object(bus, port_path, 'bmc.kepler.Systems.NetworkPort')
        if port_obj.NetDevFuncType == 32 then
            count = count + 1
        end
    end
    return count
end

function m.get_adapter_ids_from_ib_port_paths(port_paths)
    if not port_paths then
        return
    end

    local ids = {}
    for _, port_path in ipairs(port_paths) do
        local port_obj = mdb.get_object(bus, port_path, 'bmc.kepler.Systems.NetworkPort')
        if port_obj.NetDevFuncType == 32 then
            if port_obj.Type == BUSINESSPORT_CARD_TYPE_VIR or
                port_obj.Type == BUSINESSPORT_CARD_TYPE_TEAM or
                port_obj.Type == BUSINESSPORT_CARD_TYPE_BRIDGE then
                ids[#ids + 1] = port_obj.NetworkAdapterId
            else
                ids[#ids + 1] = port_obj.NetworkAdapterId .. 'Port' .. (port_obj.PortID + 1)
            end
        end
    end
    return ids
end

function m.get_dirty_test_results(input)
    local results = cjson.json_object_new_array()
    for _, v in ipairs(input) do
        local result = cjson.json_object_new_object()
        result.DetectorId = v.Detection[1].Value
        result.DetectionPhase = v.Detection[2].Value
        result.DetectionStatus = v.Detection[3].Value
        result.Result = cjson.json_object_new_object()
        result.Result.ContaminationDetected = v.Detection[4].Value
        result.Result.LocalContaminationDetected = v.Detection[5].Value
        result.Result.RemoteContaminationDetected = v.Detection[6].Value
        result.Result.FirstContaminationDistance = cjson.json_object_new_array()
        for substring in string.gmatch(v.Detection[7].Value, "[^/]+") do
            result.Result.FirstContaminationDistance[#result.Result.FirstContaminationDistance + 1] = tonumber(substring)
        end
        result.Result.SecondContaminationDistance = cjson.json_object_new_array()
        for substring in string.gmatch(v.Detection[8].Value, "[^/]+") do
            result.Result.SecondContaminationDistance[#result.Result.SecondContaminationDistance + 1] = tonumber(substring)
        end
        results[#results + 1] = result
    end
    return results
end

function m.get_dirty_test_init_params(input)
    local params = {}
    for i, v in pairs(input) do
        params[i] = {}
        params[i][1] = v['DetectorId'] or ''
        params[i][2] = v['DurationSeconds'] or 60
    end
    return params
end

function m.get_dirty_test_start_params(detectors, operation)
    local params = {}
    local struct, k_v
    for _, v in ipairs(detectors) do
        struct = {Detector = {}}
        k_v = {Key = 'DetectorId', Value = v or ''}
        struct.Detector[#struct.Detector + 1] = k_v
        k_v = {Key = 'Operation', Value = operation or ''}
        struct.Detector[#struct.Detector + 1] = k_v
        params[#params + 1] = struct
    end
    return params
end

function m.get_multi_rootbdfs(pcie_path, businfo)
    local bdfs = {}

    local sub_objects = mdb.get_sub_objects(bus, pcie_path .. '/PCIeFunctions/',
        'bmc.kepler.Systems.PCIeDevice.PCIeFunction')
    for _, obj in pairs(sub_objects) do
        bdfs[#bdfs + 1] = string.format("%04x:%02x:%02x.%x",
            obj.SegmentNumber, obj.RootBusNumber, obj.RootDeviceNumber, obj.RootFunctionNumber)
    end
    if #bdfs == 0 then
        bdfs[1] = businfo
    end
    table.sort(bdfs)
    return bdfs
end

return m
