-- 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 singleton = require 'mc.singleton'
local log = require 'mc.logging'
require 'object_manager.basic'

---@class c_factory: c_basic_class
local c_factory = class()

function c_factory:ctor()
    self.factory = {}
    self.slots = false
end

function c_factory:connect(sig, cb)
    self.slots = self.slots or {}
    self.slots[#self.slots+1] = sig:on(cb)
end

---@param mc c_mc_object_manage
function c_factory:install_mc_signal(mc)
    mc.on_add_object:on(function(class_name, object, position)
        self:create_object(class_name, object, position)
    end)
    mc.on_delete_object:on(function(class_name, object, position)
        self:del_object(class_name, object, position)
    end)

    self:connect(mc.on_before_add_object, function(class_name, object)
        return self:before_add_object(class_name, object)
    end)
end

function c_factory:reset()
    for _, cls in pairs(self.factory) do
        cls.super.__reset()
    end

    if self.slots then
        for _, slot in ipairs(self.slots) do
            slot:disconnect()
        end
    end
    self.slots = false
end

function c_factory:register(class_name, cls)
    self.factory[class_name] = cls
end

function c_factory:create_object(class_name, base_obj, position)
    local cls = self.factory[class_name]
    if not cls then
        log:debug('c_factory:create_object failed: unknown class %s', class_name)
        return
    end

    local collection = cls.collection
    local raw_obj = collection:new_raw_object(base_obj, position)
    local object, index_name, key = collection:find_by_raw_object(raw_obj)
    if object then

        log:error('c_factory create_object falied: object duplicate, %s[%s] key=%s', class_name,
            index_name, key)
        return
    end

    local new_object = cls.new(base_obj, position)
    collection:add_object(new_object)
    cls.__inc_object_id()
    cls.on_add_object:emit(new_object)
end

-- 递归调用析构函数
local function destory_object(object)
    local dtor = nil
    local obj = object
    repeat
        if dtor ~= obj.dtor then
            dtor = obj.dtor
            dtor(obj)
        end
        obj = obj.super
    until not obj
end

function c_factory:before_add_object(class_name, base_obj)
    local cls = self.factory and self.factory[class_name] or nil
    if not cls then
        log:debug('c_factory: before_add_object failed: unknown class %s', class_name)
        return true
    end

    if cls.before_add_object and type(cls.before_add_object) == 'function' then
        return cls.before_add_object(base_obj)
    end

    return true
end

function c_factory:del_object(class_name, base_obj, position)
    local cls = self.factory[class_name]
    if not cls then
        log:error('c_factory:del_object failed: unkonw class %s', class_name)
        return
    end

    local collection = cls.collection
    local raw_obj = collection:new_raw_object(base_obj, position)
    local object = collection:find_by_raw_object(raw_obj)
    if not object then
        return
    end

    collection:del_object(raw_obj)
    local ok, err = pcall(function()
        cls.on_delete_object:emit(object)
    end)
    if not ok then
        log:error('c_factory:del_object %s failed: %s', class_name, err)
    end

    ok, err = pcall(destory_object, object)
    if not ok then
        log:error('c_factory destory %s failed: %s', class_name, err)
    end
end

return singleton(c_factory)
