-- 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 bs_util = require 'util.base_util'
local menu_node = require 'domain.config.MenuNode'
local json = require 'cjson'
local registry_service = require 'service.registry_service'
local setting = require 'pojo.config.file.setting'
local skynet = require 'skynet'
local Config = class()

local START_INDEX <const> = 1

function Config:ctor(path, setting_path, current_path)
    self.path = path
    -- 待生效配置
    self.setting_path = setting_path
    -- 已生效配置
    self.current_path = current_path
    self.root = menu_node.new()
    -- 记录attribute所在的menu
    self.attribute_menu = {}
end

function Config:init()
    self:build()
end

local function update_registry(path, registry)
    if not registry then
        return
    end
    local data = bs_util.get_file_order_json(path)
    if not data then
        return
    end
    local setting_obj = setting.new(data)
    registry:update_registry(setting_obj)
    pcall(setting_obj.destroy, setting_obj)
end

function Config:update_denpendency(list, attribute_prop)
    for k, v in pairs(list) do
        self:update_value({
            attribute_name = k,
            attribute_prop = attribute_prop,
            attribute_value = v
        })
    end
end

function Config:update_attribute()
    local registry = registry_service.new(bs_util.get_file_json(self.path), true)
    update_registry(self.current_path, registry)
    update_registry(self.setting_path, registry)
    local hidden_list, immutable_list, readonly_list = registry:get_list()
    self:update_denpendency(hidden_list, 'Hidden')
    self:update_denpendency(immutable_list, 'Immutable')
    self:update_denpendency(readonly_list, 'ReadOnly')
    skynet.fork(function()
        collectgarbage('collect')
        registry:destroy()
    end)
end

function clear_data(data)
    for k, _ in pairs(data) do
        data[k] = nil
    end
end

function Config:build()
    local data = bs_util.get_file_order_json(self.path)
    if not data then
        return
    end
    pcall(self.build_menu, self, data)
    pcall(self.build_attribute, self, data)
    pcall(self.update_attribute, self)
    clear_data(data)
end

local function parse_menu_path(path)
    if not path then
        return {}
    end
    local menus = {}
    path = string.sub(path, 3, -1) .. '/'
    for menu_name in string.gmatch(path, '[^/]+') do
        table.insert(menus, menu_name)
    end
    return menus
end

function Config:build_menu(data)
    if not data or not data.RegistryEntries or not data.RegistryEntries.Menus then
        return
    end
    local menus = data.RegistryEntries.Menus
    for _, v in pairs(menus) do
        self.root:insert(parse_menu_path(v.MenuPath), START_INDEX, v.ReadOnly)
    end
end

function Config:build_attribute(data)
    if not data or not data.RegistryEntries or not data.RegistryEntries.Attributes then
        return
    end
    local attributes = data.RegistryEntries.Attributes
    for _, v in pairs(attributes) do
        self.root:add_attribute(parse_menu_path(v.MenuPath), START_INDEX, v)
        self.attribute_menu[v.AttributeName] = v.MenuPath
    end
end

function Config:traverse(cfg_tbl)
    self.root:traverse(cfg_tbl)
end

-- 更新值信息
function Config:update_value(attribute)
    local menu_path = self.attribute_menu[attribute.attribute_name]
    if not menu_path then
        return
    end
    self.root:update_value(parse_menu_path(menu_path),
        START_INDEX, attribute)
end

-- 根据配置文件内容，更新值信息
function Config:update_config(path, attribute_prop)
    local data = bs_util.get_file_json(path)
    if not data then
        return
    end
    for k, v in pairs(data) do
        self:update_value({
            attribute_name = k,
            attribute_prop = attribute_prop,
            attribute_value = v
        })
        data[k] = nil
    end
end

-- 更新currentvalue值
function Config:update_current_value_by_file()
    self:clear_current_value()
    self:update_config(self.current_path, 'CurrentValue')
end

-- 清除currentvalue值
function Config:clear_current_value()
    for attribute_name, menu_path in pairs(self.attribute_menu) do
        self:update_value({
            attribute_name = attribute_name,
            attribute_prop = 'CurrentValue',
            attribute_value = json.null
        })
    end
end

-- 根据配置文件内容，更新setting值
function Config:update_setting_value_by_file()
    self:clear_setting_value()
    self:update_config(self.setting_path, 'SettingValue')
end

-- 清除setting值
function Config:clear_setting_value()
    for attribute_name, menu_path in pairs(self.attribute_menu) do
        self:update_value({
            attribute_name = attribute_name,
            attribute_prop = 'SettingValue',
            attribute_value = json.null
        })
    end
end

-- 更新依赖关系：导入配置之后、上报配置之后、清除配置之后
function Config:update_denpendency_after_clear()
    for attribute_name, menu_path in pairs(self.attribute_menu) do
        self.root:clear_value(parse_menu_path(menu_path), START_INDEX, attribute_name)
    end
    self:update_attribute()
end

return Config