-- 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 json = require 'cjson'
local class = require 'mc.class'
local signal = require 'mc.signal'
local log = require 'mc.logging'
local json_null = json.null

local NA<const> = 'N/A'
local ODATA_ID<const> = '@odata.id'
local ODATA_TYPE<const> = '@odata.type'

local c_handler_base = class(nil, nil, true)

function c_handler_base:ctor()
    self.odata_types = {}
    self.class_types = {}
    self.resources = {} -- 资源路径到对象的映射关系
    self.objects = {} -- 对象到资源树路径的映射关系
    self.add_object_complete = true

    self.on_need_update = signal.new()
end

function c_handler_base:regist_odata_type(odata_type)
    self.odata_types[#self.odata_types + 1] = odata_type .. '$'
end

function c_handler_base:regist_class_type(cls)
    self.class_types[#self.class_types + 1] = cls.__class_name
end

-- odata_type_full 是完整的 odata.type, odata_types 中为 odata.type 的后缀不带版本号等信息
function c_handler_base:match_type(odata_type_full)
    if not odata_type_full then
        return false
    end

    for _, odata_type in ipairs(self.odata_types) do
        if odata_type_full:find(odata_type) then
            return true
        end
    end

    return false
end

function c_handler_base:match_class_type(cls)
    for _, class_name in ipairs(self.class_types) do
        if class_name == cls.__class_name then
            return cls
        end
    end

    return nil
end

function c_handler_base:match_object(path, data)
    local object = self.resources[path]
    if object then
        return object
    end

    if data and not self:match_type(data[ODATA_TYPE]) then
        return nil
    end
    if self.add_object_complete then
        object = self:find_object(path, data)
    end

    self:regist_object(path, object)
    return object
end

function c_handler_base:get_object(path)
    if not path then
        return nil
    end

    return self.resources[path]
end

function c_handler_base:match_resource(object, path_prefix)
    local path = object and self.objects[object] or nil

    if path then
        -- 如果已有匹配对象，只需要刷新指定 BMA 资源
        self.on_need_update:emit(object, path)
        return
    end

    for p, obj in pairs(self.resources) do
        if not obj and (not path_prefix or p:find(path_prefix, 0, true) == 1) then
            -- 所有没有匹配到对象的 BMA 资源都需要重新刷新
            self.on_need_update:emit(object, p)
        end
    end
end

function c_handler_base:regist_object(path, object)
    if not object then
        if self.resources[path] == nil then
            self.resources[path] = false
            log:info('c_handler_base: regist object, bma path=%s, object=nil', path)
        end

        return
    end

    self.resources[path] = object
    self.objects[object] = path
    log:info('c_handler_base: regist object, bma path=%s, object=%s', path, object.path)

    object:connect_signal(object.on_delete_object, function(obj)
        if obj ~= object then
            return
        end

        self.resources[path] = false
        self.objects[object] = nil
        log:info('c_handler_base: unregist object, bma path=%s, object=%s', path, object.path)
    end)
end

function c_handler_base:find_object(path, data)
end

function c_handler_base:add(path, data, object)
end

function c_handler_base:update(path, data, object)
end

function c_handler_base:delete(path, data, object)
end

function c_handler_base:reset()
end

local function get_oem_huawei(data)
    if data and data.Oem and data.Oem.Huawei then
        return data.Oem.Huawei
    end
end

local function is_na(val)
    return val == NA
end

local function is_null(val)
    return val == json_null
end

local function is_nil_or_null(val)
    return val == nil or val == json_null
end

local function is_invalid_str(val)
    return val == nil or val == json_null or #val == 0 or val == NA
end

local function default_na(val)
    if val == nil or val == json_null then
        return NA
    end

    return val
end

local function update_if_not_config(dst, src)
    if dst == nil or dst == NA or dst == '' then
        dst = src
    end
    return dst
end

local function update_if_valid(dst, src)
    if src == nil or src == json_null then
        return dst
    end
    dst = src
    return dst
end

local function update_number_if_valid(dst, src)
    if src == nil or src == json_null then
        return dst
    end
    dst = tonumber(src)
    return dst
end

local function get_value_to_update(dst, src, default)
    if src == nil then
        return dst
    end
    if src == json_null then
        return default
    end
    return src
end

c_handler_base.get_value_to_update = get_value_to_update
c_handler_base.get_oem_huawei = get_oem_huawei
c_handler_base.is_na = is_na
c_handler_base.is_null = is_null
c_handler_base.is_nil_or_null = is_nil_or_null
c_handler_base.is_invalid_str = is_invalid_str
c_handler_base.default_na = default_na
c_handler_base.update_if_not_config = update_if_not_config
c_handler_base.update_if_valid = update_if_valid
c_handler_base.update_number_if_valid = update_number_if_valid
function c_handler_base.get_odata_id(data)
    if not data then
        return
    end
    return data[ODATA_ID]
end

function c_handler_base.get_parent_path(odata_id)
    if not odata_id then
        return nil
    end

    -- 查找父路径
    -- 例如：/redfish/v1/Sms/1/Systems/1/EthernetInterfaces/0000:05:01.0_0000:07:00.0/Statistics
    local parent_id = odata_id:match('^(.+)/[^/]+$')
    if not parent_id then
        return nil
    end

    return parent_id
end

function c_handler_base.prepare_ipv4_address(ipv4_addresses)
    if not ipv4_addresses or is_null(ipv4_addresses) then
        return {}
    end

    local addrs = {}
    for i, addr in ipairs(ipv4_addresses) do
        addrs[i] = {
            Address = default_na(addr.Address),
            AddressOrigin = default_na(addr.AddressOrigin),
            Gateway = addr.Gateway and table.concat(addr.Gateway, ' ') or 'N/A',
            SubnetMask = default_na(addr.SubnetMask)
        }
    end
    return addrs
end

function c_handler_base.prepare_ipv6_address(ipv6_addresses)
    if not ipv6_addresses or is_null(ipv6_addresses) then
        return {}
    end

    local addrs = {}
    for i, addr in ipairs(ipv6_addresses) do
        addrs[i] = {
            Address = default_na(addr.Address),
            AddressOrigin = default_na(addr.AddressOrigin),
            AddressState = default_na(addr.AddressState),
            Gateway = addr.Gateway or {},
            PrefixLength = default_na(addr.PrefixLength)
        }
    end
    return addrs
end

function c_handler_base.decode_bdf(bdf_str)
    if not bdf_str or type(bdf_str) ~= 'string' then
        error(string.format('invalid bdf type: %s', bdf_str))
    end

    local segment, bus, device, func = string.match(bdf_str, '(%x+):(%x+):(%x+)%.(%x*)')
    if not bus then
        error(string.format('invalid bdf: %s', bdf_str))
    end

    return {
        Segment = tonumber(segment, 16),
        Bus = tonumber(bus, 16),
        Device = tonumber(device, 16),
        Function = tonumber(func, 16)
    }
end

-- 芯片型号Model，如果xml配置的原始值为空或N/A，则更新该属性，否则以BMC配置的值为准
function c_handler_base.update_network_card_model(na, data)
    if is_nil_or_null(data.Model) then
        return
    end

    if na.Model and #na.Model > 0 and not is_na(na.Model) then
        return
    end

    na.Model = default_na(data.Model)
end

-- 芯片厂商ChipManufacturer，如果xml配置的原始值为空或N/A，则更新该属性，否则以BMC配置的值为准
function c_handler_base.update_network_card_manufacturer(na, data)
    if is_nil_or_null(data.Manufacturer) then
        return
    end

    if na.Manufacturer and #na.Manufacturer > 0 and not is_na(na.Manufacturer) then
        return
    end

    na.Manufacturer = default_na(data.Manufacturer)
end

local NETDEV_FUNCTYPE_ETHERNET<const> = 1
local NETDEV_FUNCTYPE_FC<const> = 2
local NETDEV_FUNCTYPE_IB<const> = 32
function c_handler_base.update_network_port_netdev_functype(port, data, func_type)
    if not data['@odata.id'] then
        return
    end
    if port.NetDevFuncType == NETDEV_FUNCTYPE_IB then
        return
    end

    local support_functype = port.SupportedFuncType
    if support_functype == NETDEV_FUNCTYPE_ETHERNET or support_functype == NETDEV_FUNCTYPE_FC then
        -- 对仅支持1种类型的卡，已预配置NetDevFuncType值和SupportedFuncType一致，不需更新
        return
    end

    port.NetDevFuncType = update_if_valid(port.NetDevFuncType, func_type)
end

return c_handler_base
