-- 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 singleton = require('mc.singleton')
local utils = require('mc.utils')

local m = {}
m.__index = m

function m.new()
    return setmetatable({
        pre_tree = {},
        tree = {
            interfaces = {},
            children = {},
        },
        mocks = {},
        invokes = {},
    }, m)
end

local function key_contains(element, tab)
    for k, _ in pairs(tab) do
        if k == element then
            return true
        end
    end

    return false
end

local function split_path(path)
    if string.sub(path, 1, 1) ~= '/' then
        return nil
    end

    path = string.sub(path, 2)
    return utils.split(path, '/')
end

local function table_contains_tables(t1, t2, ignore_case)
    for k1, v1 in pairs(t1) do
        for k2, v2 in pairs(t2) do
            if ignore_case and type(v1) == 'string' and type(v2) == 'string' then
                v1 = v1:lower()
                v2 = v2:lower()
            end
            if k1 == k2 and v1 == v2 then
                return true
            end
        end
    end

    return false
end

local function get_path_dfs(res, node, interface, filter, ignore_case, path)
    for k, v in pairs(node.interfaces) do
        if k == interface and table_contains_tables(v, filter, ignore_case) then
            res[#res + 1] = path
        end
    end

    for k, v in pairs(node.children) do
        get_path_dfs(res, v, interface, filter, ignore_case, k)
    end
end

local function is_valid_path_dfs(node, path)
    for k, v in pairs(node.children) do
        if k == path or is_valid_path_dfs(v, path) then
            return true
        end
    end

    return false
end

function m:get_object(path, interface)
    local node = self:get_node(path)
    if not node then
        return nil
    end

    return node.interfaces[interface]
end

function m:change_prop(path, interface, prop, val)
    local node = self:get_node(path)
    if not node then
        return nil
    end

    if node.interfaces[interface][prop] then
        node.interfaces[interface][prop] = val
    else
        return
    end

    if node.interfaces[interface]['_' .. prop] then
        node.interfaces[interface]['_' .. prop]()
    end
end

-- 获取path下深度为depth、接口在interfaces范围内的子路径集合
function m:get_sub_paths(path, depth, interfaces)
    local node = self:get_node(path)
    if not node then
        return nil
    end

    -- 找到深度为depth的所有子路径
    local father = { [path] = node }
    while depth > 0 do
        local son = {}
        for _, v in pairs(father) do
            for sub_path, sub_node in pairs(v.children) do
                son[sub_path] = sub_node
            end
        end
        father = son
        depth = depth - 1
    end

    -- 若没有指定接口,则将深度为depth的所有子路径返回
    local sub_paths = {}
    if interfaces[1] == nil then
        for k, _ in pairs(father) do
            sub_paths[#sub_paths + 1] = k
        end

        return sub_paths
    end

    -- 若指定接口,则将深度为depth、接口在interfaces范围内的所有子路径返回
    for k, v in pairs(father) do
        for i, _ in pairs(v.interfaces) do
            if utils.array_contains(interfaces, i) then
                sub_paths[#sub_paths + 1] = k
                break
            end
        end
    end

    return sub_paths
end

function m:get_path(interface, filter, ignore_case)
    local node = self.tree
    local res = {}
    get_path_dfs(res, node, interface, filter, ignore_case, '/')

    return res
end

function m:is_valid_path(path)
    local node = self.tree
    return is_valid_path_dfs(node, path)
end

function m:update_interface(interface, properties, methods)
    local t = interface == nil and {} or interface
    if properties then
        for k, v in pairs(properties) do
            t[k] = v
        end
    end

    if not methods then
        return t
    end

    for k, v in pairs(methods) do
        t[k] = function()
            local dt_mdb = require('tree')
            local obj = dt_mdb.get_instance()
            obj:mock(v.mocks)
            return v.rsp
        end
    end

    return t
end

function m:get_node(path)
    local tab = split_path(path)
    if type(tab) ~= 'table' then
        return nil
    end

    local node = self.tree
    local p = ''
    for _, v in pairs(tab) do
        p = p .. '/' .. v
        if node.children[p] == nil then
            return
        end

        node = node.children[p]
    end

    return node
end

function m:find_node(path)
    local tab = split_path(path)
    if type(tab) ~= 'table' then
        return nil
    end

    local node = self.tree
    local p = ''
    for _, v in pairs(tab) do
        p = p .. '/' .. v
        if node.children[p] == nil then
            local child = {}
            child.children = {}
            child.interfaces = {}
            node.children[p] = child
        end

        node = node.children[p]
    end

    return node
end

function m:update_node(path, interface, properties, methods)
    local node = self:find_node(path)
    if not node then
        return
    end

    node.interfaces[interface] = self:update_interface(node.interfaces[interface], properties, methods)
end

function m:delete_node(path)
    local tab = split_path(path)
    if type(tab) ~= 'table' then
        return nil
    end

    local node = self.tree
    local p = ''
    for _, v in pairs(tab) do
        p = p .. '/' .. v
        if node.children[p] == nil then
            return
        end

        if p == path then
            node.children[p] = nil
        end

        node = node.children[p]
    end
end

function m:register_func(path, interface, properties)
    local node = self:find_node(path)
    if not node then
        return
    end

    for k, v in pairs(properties) do
        node.interfaces[interface]['_' .. k] = function()
            self:mock(v)
        end
    end
end

function m:mock_one(mock)
    local path = mock.path
    local interface = mock.interface
    local properties = mock.properties
    local methods = mock.methods
    local operation = mock.operation
    if not path then
        print('The mock lacks \'path\'.')
        return
    end

    if operation == 'Delete' then
        self:delete_node(path)
        return
    end

    self:update_node(path, interface, properties, methods)
end

function m:invoke_one(invoke)
    local path = invoke.path
    local interface = invoke.interface
    local properties = invoke.properties
    if not path or not interface or not properties then
        return
    end

    self:register_func(path, interface, properties)
end

function m:mock(mocks)
    if type(mocks) ~= 'table' then
        return
    end

    for _, v in pairs(mocks) do
        if key_contains(v, self.mocks) then
            self:mock_one(self.mocks[v])
        end
    end
end

function m:invoke(invokes)
    if type(invokes) ~= 'table' then
        return
    end

    for _, v in pairs(invokes) do
        if key_contains(v, self.invokes) then
            self:invoke_one(self.invokes[v])
        end
    end
end

-- 资源树桩备份
function m:backup_stub()
    self.pre_tree = utils.table_copy(self.tree)
end

-- 资源树桩清理
function m:clear_stub()
    self.tree = {
        interfaces = {},
        children = {},
    }
end

-- 资源树桩还原
function m:recover_stub()
    self.tree = utils.table_copy(self.pre_tree)
end

-- 执行用例文件后清理
function m:clear()
    self:clear_stub()
    self.mocks = {}
    self.invokes = {}
end

return singleton(m)