-- 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 json = require 'cjson'
local log = require 'mc.logging'
local signal = require 'mc.signal'
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.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]
    -- 对应资源路径有对象并且已经match过才返回对应的对象
    if object and object[1] == true then
        return object[2]
    end

    -- 由于NVMe盘有delete_object流程，object为false
    if not object and not self:match_type(data[ODATA_TYPE]) then
        return nil
    end
    local ok, drive_object = pcall(self.find_object, self, path, data)
    if not ok then
        return nil
    end
    -- 对应资源路径有对象但没match过，将之前获取的信息与本次获取的信息合并，并以最新的为准
    if object and object[1] == false then
        for k, v in pairs(object[2]) do
            if not data[k] then
                data[k] = v
            end
        end
    end

    self:regist_object(path, drive_object, data)
    return drive_object
end

function c_handler_base:match_resource(object, path_prefix)
    local path = self.objects[object]
    if path then
        -- 如果已有匹配对象，只需要刷新指定 BMA 资源
        self.on_need_update:emit(object, path)
        return
    end

    for p, obj in pairs(self.resources) do
        if (not obj or not obj[1]) 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, data)
    if not object then
        if not self.resources[path] or not self.resources[path][1] then
            self.resources[path] = {false, data}
            log:info('c_handler_base: regist object, bma path=%s, object=nil', path)
        end
        return
    end

    -- 改为{bool, object}, 第一个参数表示资源是否已经match到对应的对象
    self.resources[path] = {true, object}
    self.objects[object] = path
    log:notice('c_handler_base: regist object, bma path=%s, object=%s', path, object.path)

    if not object.on_delete_object then
        log:notice('object on_delete_object signal not exist')
        return
    end

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

        self.resources[path] = false
        self.objects[object] = nil
        log:notice('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.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 == 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 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

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
function c_handler_base.get_odata_id(data)
    return data[ODATA_ID]
end

return c_handler_base
