-- Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
--
-- this file licensed under the 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 log = require 'mc.logging'
local utils = require 'mc.utils'
local split = utils.split
local client = require 'thermal_mgmt.client'
local enums = require 'basic_cooling.define.cooling_enums'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local match_rule = org_freedesktop_dbus.MatchRule
local interfaces_added = org_freedesktop_dbus.ObjMgrInterfacesAdded
local interfaces_removed = org_freedesktop_dbus.ObjMgrInterfacesRemoved
local properties_changed = org_freedesktop_dbus.ObjMgrPropertiesChanged

local base = class()

function base:ctor(bus)
    self.bus = bus
    self.obj_name = ''
    -- 监听的对象信息
    self.objs = nil
    -- 监听的资源树路径
    self.paths_table = {}
    -- 监听的接口
    self.interfaces_table = {}
    self.paths_namespace = {}
end

function base:init()
    self.register_slot = self:register_listen_callback()
    for _, path in pairs(self.paths_table) do
        self.paths_namespace[path] = path:gsub(":[^/]+", "*"):gsub("${[^/}]+}", "*")
    end
end

function base:register_listen_callback()
    if not self.paths_table then
        return
    end
    local register_slot = {}
    local interfaces_added_sig, interfaces_removed_sig, properties_changed_sig
    for _, path in pairs(self.paths_table) do
        -- 接口新增信号注册
        interfaces_added_sig = match_rule.signal(interfaces_added.name,
            interfaces_added.interface):with_path_namespace(path)
        table.insert(register_slot, self.bus:match(interfaces_added_sig, function(msg)
            self:interface_added_callback(msg:sender(), msg:read('oa{sa{sv}}'))
        end))
        -- 接口删除信号注册
        interfaces_removed_sig = match_rule.signal(interfaces_removed.name,
            interfaces_removed.interface):with_path_namespace(path)
        table.insert(register_slot, self.bus:match(interfaces_removed_sig, function(msg)
            self:interface_removed_callback(msg:sender(), msg:read('oas'))
        end))
        -- 属性变化信号注册
        for interface, interface_info in pairs(self.interfaces_table) do
            local properties = {}
            for prop, _ in pairs(interface_info[enums.data_keeping_config.PROPERTIES]) do
                table.insert(properties, prop)
            end
            if not next(properties) then
                goto continue
            end
            properties_changed_sig = match_rule.signal(properties_changed.name,
                properties_changed.interface):with_path_namespace(path):with_properties(interface, properties)
            table.insert(register_slot, self.bus:match(properties_changed_sig, function(msg)
                self:property_changed_callback(msg:sender(), msg:path(), msg:read('sa{sv}as'))
            end))
            ::continue::
        end
    end
    return register_slot
end

local function is_path_match(path, msg_path)
    if path == msg_path then
        return true
    end
    if type(path) ~= 'string' or path:find('%*') == nil then
        return false
    end
    local path_segs = split(path, '/')
    local msg_path_segs = split(msg_path, '/')
    if #path_segs ~= #msg_path_segs then
        return false
    end
    for i = 1, #path_segs do
        if path_segs[i] ~= '*' and path_segs[i] ~= msg_path_segs[i] then
            return false
        end
    end
    return true
end

function base:check_path_valid(msg_path)
    for _, path in pairs(self.paths_namespace) do
        if is_path_match(path, msg_path) then
            return true
        end
    end
    return false
end

-- 防止组件重启获取不到数据或者获取数据不全
function base:refresh_objs_info()
    log:debug('Start to refresh %s information', self.obj_name)
    local objs_path = {}
    for path, _ in pairs(self.objs or {}) do
        objs_path[path] = false
    end
    for interface, interface_info in pairs(self.interfaces_table) do
        interface_info[enums.data_keeping_config.FOREACH_FUNC](client, function(obj)
            if not self:check_path_valid(obj.path) then
                return
            end
            objs_path[obj.path] = true
            if not self.objs then
                self.objs = {}
            end
            if not self.objs[obj.path] then
                self.objs[obj.path] = {}
            end
            if not self.objs[obj.path][interface] then
                self.objs[obj.path][interface] = {}
                self:sync_obj_info(self.objs[obj.path][interface], obj, interface)
                self:action_after_obj_added(obj.path, interface)
            else
                -- 对象存在，再重新更新下内存对象属性值
                self:sync_obj_info(self.objs[obj.path][interface], obj, interface)
                log:debug('Refresh %s(%s) interface(%s) info successfully', self.obj_name, obj.path, interface)
            end
        end)
        for path, exist in pairs(objs_path) do
            if not exist then
                self.objs[path][interface] = nil
                self:action_after_obj_removed(path, interface)
            end
            objs_path[path] = false
        end
    end
    for path, _ in pairs(objs_path) do
        objs_path[path] = nil
    end
    log:debug('Refresh %s information successfully', self.obj_name)
end

-- 同步资源树对象数据
function base:sync_obj_info(targ_obj, sync_obj, interface)
    for prop, log_flag in pairs(self.interfaces_table[interface][enums.data_keeping_config.PROPERTIES]) do
        if sync_obj[prop] == nil or targ_obj[prop] == sync_obj[prop] then
            goto continue
        end
        targ_obj[prop] = sync_obj[prop]
        if log_flag == enums.data_keeping_log.LOG then
            log:notice('Sync object prop(%s): %s', prop, sync_obj[prop])
        else
            if log:getLevel() >= log.DEBUG then
                log:debug('Sync object prop(%s): %s', prop, sync_obj[prop])
            end
        end
        ::continue::
    end
    targ_obj.obj = sync_obj
end

function base:get_obj_info_by_interface(obj_path, interface)
    if not self.interfaces_table[interface] then
        return
    end
    if not self.objs then
        self.objs = {}
    end
    if not self.objs[obj_path] then
        self.objs[obj_path] = {}
    end
    self.objs[obj_path][interface] = {}
    local obj
    self.interfaces_table[interface][enums.data_keeping_config.FOREACH_FUNC](client, function(o)
        if o.path == obj_path then
            obj = o
        end
    end)
    if not obj then
        log:error("Get object(%s) failed, interface(%s)", obj_path, interface)
        return
    end
    self:sync_obj_info(self.objs[obj_path][interface], obj, interface)
end

function base:interface_added_callback(sender, path, interfaces_and_properties)
    for interface, _ in pairs(interfaces_and_properties) do
        if self.interfaces_table[interface] and (not self.objs or not self.objs[path] or
            not self.objs[path][interface]) then
            self:get_obj_info_by_interface(path, interface)
            self:action_after_obj_added(path, interface)
        end
    end
end

function base:interface_removed_callback(sender, path, interfaces)
    for _, interface in pairs(interfaces) do
        if self.interfaces_table[interface] and self.objs and self.objs[path] and self.objs[path][interface] then
            self.objs[path][interface] = nil
            if not next(self.objs[path]) then
                self.objs[path] = nil
            end
            self:action_after_obj_removed(path, interface)
        end
    end
end

function base:property_changed_callback(sender, path, interface, changed_props, invalidated_props)
    if not self.interfaces_table[interface] then
        return
    end
    if not self.objs or not self.objs[path] or not self.objs[path][interface] then
        self:get_obj_info_by_interface(path, interface)
        self:action_after_obj_added(path, interface)
        return
    end
    for prop, value in pairs(changed_props) do
        if not self.interfaces_table[interface][enums.data_keeping_config.PROPERTIES][prop] then
            goto continue
        end
        self.objs[path][interface][prop] = value:value()
        if self.interfaces_table[interface][enums.data_keeping_config.PROPERTIES][prop] ==
            enums.data_keeping_log.LOG then
            log:notice('Object(%s) prop(%s) changed: %s', path, prop, tostring(value:value()))
        else
            if log:getLevel() >= log.DEBUG then
                log:debug('Object(%s) prop(%s) changed: %s', path, prop, tostring(value:value()))
            end
        end
        self:action_after_property_changed(path, interface, prop, value:value())
        ::continue::
    end
end

-- 用于实现对象添加后的动作，由子类实现
function base:action_after_obj_added(path, interface)
    log:notice('Add object(%s) completed, interface: %s', path, interface)
end

-- 用于实现对象删除后的动作，由子类实现
function base:action_after_obj_removed(path, interface)
    log:notice('Removed object(%s) completed, interface: %s', path, interface)
end

-- 用于实现特定属性变更后的动作，由子类实现
function base:action_after_property_changed(path, interface, prop, value)
end

return base
