-- 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 skynet_queue = require 'skynet.queue'
local client = require 'thermal_mgmt.client'
local signal = require 'mc.signal'
local props = require 'basic_cooling.define.cooling_properties'

local FANS_PATH <const> = '/bmc/kepler/Systems/1/Thermal/Fans'

local fan_props_table = {
    [props.FAN_ID] = true,
    [props.FAN_SLOT] = true,
    [props.FAN_FRONT_PRESENCE] = true,
    [props.FAN_REAR_PRESENCE] = true,
    [props.FRONT_STATUS] = true,
    [props.REAR_STATUS] = true,
    [props.MAX_SUPPORTED_PWM] = true,
    [props.FAN_MODEL] = true,
    ["ExpectedPWM"] = true,
    ["SetFanPWM_PACKED"] = true
}

-- 对象数据收集模板，供新增数据功能参考
local fans_data_keeping = class()

function fans_data_keeping:ctor(bus)
    self.bus = bus
    self.queue = skynet_queue() -- 队列中的任务按照顺序执行
    self.intf = 'bmc.kepler.Systems.Fan'
    self.fan_identified_state = {}
    self.fan_num = 0
    self.identified_fan_num = 0
    self.identified_state_changed = signal.new()
    self.fans = {}
end

function fans_data_keeping:init()
    self.slot = self:props_changed_listenning()
end

function fans_data_keeping:get_all_infos()
    self:get_thermal_obj()
    self:get_fans_info()
end

function fans_data_keeping:props_changed_listenning()
    return client:OnFanPropertiesChanged(function(prop_value, path, interface)
        self:interface_props_changed_callback(prop_value, path, interface)
    end)
end

function fans_data_keeping:get_fans_info()
    client:ForeachFanObjects(function (obj)
        self.queue(function() -- 获取风扇信息先等待队列中的其他更新风扇先执行
            self:add_one_fan_data(obj.path, obj)
        end)
    end)
    if self.identified_fan_num > 0 then
        self.identified_state_changed:emit()
    end
end

-- 获取天池规范芯片支持设置全量风扇转速的对象
function fans_data_keeping:get_thermal_obj()
    if not self.thermal_obj then
        client:ForeachFansObjects(function (obj)
            if obj.SetPWM_PACKED then
                self.thermal_obj = obj
                return
            end
        end)
    end
    return self.thermal_obj
end

function fans_data_keeping:update_all_datas()
    self:get_fans_info()
    self:get_thermal_obj()
    for path, fan_obj in pairs(self.fans) do
        local fan_data = 'path:' .. tostring(path)
        for k, v in pairs(fan_obj) do
            fan_data = fan_data .. ' | ' .. tostring(k) .. ': ' .. tostring(v)
        end
        log:notice(fan_data)
    end
end

-- 获取风扇信息
function fans_data_keeping:get_fans_info_with_fan_id()
    local fans_info = {}
    local fan_id
    for _, fan_obj in pairs(self.fans) do
        fan_id = fan_obj[props.FAN_ID]
        fans_info[fan_id] = fan_obj
    end
    return fans_info
end

function fans_data_keeping:add_one_fan_num()
    self.fan_num = (not self.fan_num) and 1 or (self.fan_num + 1)
end

function fans_data_keeping:add_one_identified_fan_num()
    self.identified_fan_num = (not self.identified_fan_num) and 1 or (self.identified_fan_num + 1)
end

function fans_data_keeping:delet_one_fan_num()
    if self.fan_num > 0 then
        self.fan_num = self.fan_num - 1
    else
        log:error("The number of fan less then 1, cur num: %d", self.fan_num)
    end
end

function fans_data_keeping:delet_one_identified_fan_num()
    if self.identified_fan_num > 0 then
        self.identified_fan_num = self.identified_fan_num - 1
    else
        log:error("The number of identified fan less then 1, cur num: %d", self.identified_fan_num)
    end
end

function fans_data_keeping:add_one_fan_data(path, fan_obj)
    local fan_info = {}
    if not fan_obj then -- 若没传入风扇对象,遍历获取
        client:ForeachFanObjects(function (obj)
            if obj.path == path then
                fan_obj = obj
                return
            end
        end)
    end
    if not fan_obj then
        log:error("Get fan object(%s) failed", path)
        return
    end
    for prop, _ in pairs(fan_props_table) do
        if fan_obj[prop] ~= nil then
            fan_info[prop] = fan_obj[prop]
        end
    end
    fan_info.path = path
    self.fans[path] = fan_info

    if self.fan_identified_state[path] then
        log:error("Duplicated fan obj(path:%s)", path)
        return
    end

    self:add_one_fan_num()

    if string.len(fan_obj.Model) > 0 then
        self.fan_identified_state[path] = true
        self:add_one_identified_fan_num()
    else
        self.fan_identified_state[path] = false
    end
    return self.fan_identified_state[path]
end

-- 更新指定路径风扇的资源树属性到内存中
function fans_data_keeping:update_one_fan_info(path)
    local fan_obj
    local fan_info = {}
    client:ForeachFanObjects(function (obj)
        if obj.path == path then
            fan_obj = obj
            return
        end
    end)
    if not fan_obj then
        log:error("Get obj(%s) failed", path)
        return
    end
    for prop, _ in pairs(fan_props_table) do
        if fan_obj[prop] ~= nil then
            fan_info[prop] = fan_obj[prop]
        end
    end
    fan_info.path = path
    self.fans[path] = fan_info
end

-- 获取系统风扇类型
function fans_data_keeping:get_system_fan_type()
    for _, fan_obj in pairs(self.fans) do
        if type(fan_obj[props.FAN_MODEL]) == 'string' and string.len(fan_obj[props.FAN_MODEL]) ~= 0 then
            return fan_obj[props.FAN_MODEL]
        end
    end
    log:debug('system fan type is null')
    return nil
end

-- fan对象添加回调
function fans_data_keeping:interface_added_callback(sender, path, interfaces_and_properties)
    if interfaces_and_properties['bmc.kepler.Systems.Fans'] then
        self:get_thermal_obj()
    end
    local props_t = interfaces_and_properties[self.intf]
    if not props_t then
        return
    end
    self.queue(function() -- 添加风扇信息先等待队列中的其他更新风扇先执行
        local fan_info = self.fans[path]
        if not fan_info then
            if self:add_one_fan_data(path) then -- 增加一个被识别的风扇
                self.identified_state_changed:emit()
            end
            return
        end
        for prop, value in pairs(props_t) do
            if fan_props_table[prop] then
                fan_info[prop] = value:value()
            end
        end
    end)
end

-- fan对象移除回调
function fans_data_keeping:interface_removed_callback(sender, path, interfaces)
    for _, intf in pairs(interfaces) do
        self:interface_removed_process(sender, path, intf)
    end
end

function fans_data_keeping:interface_removed_process(sender, path, interface)
    if interface == 'bmc.kepler.Systems.Fans' then
        self.thermal_obj = nil
        log:notice("Delete thermal obj(path:%s)", path)
        return
    end

    if self.intf ~= interface then
        return
    end

    self.fans[path] = nil

    if self.fan_identified_state[path] == nil then
        log:error("Fan obj(path:%s) not in table", path)
        return
    end

    self:delet_one_fan_num()
    if self.fan_identified_state[path] then
        self:delet_one_identified_fan_num()
        self.fan_identified_state[path] = nil
        log:notice("Delete identified fan(path:%s)", path)
    end

    log:notice("Delete fan obj(path:%s), cur fan num:%u, cur identified fan num:%u", path, self.fan_num,
        self.identified_fan_num)
end

function fans_data_keeping:interface_props_changed_callback(changed_props, path, interface)
    if self.intf ~= interface then
        return
    end
    self.queue(function() -- 风扇信息变更先等待队列中的其他更新风扇先执行
        local fan_info = self.fans[path]
        if not fan_info then
            self:add_one_fan_data(path)
            return
        end
        for prop, value in pairs(changed_props) do
            if fan_props_table[prop] then
                fan_info[prop] = value:value()
            end
        end

        if not changed_props["Model"] then
            return
        end

        if self.fan_identified_state[path] and string.len(changed_props["Model"]:value()) == 0 then
            self:delet_one_identified_fan_num()
        end

        if not self.fan_identified_state[path] and string.len(changed_props["Model"]:value()) > 0 then
            self:add_one_identified_fan_num()
            self.identified_state_changed:emit()
        end

        log:debug("The fan(path:%s) model is changed, cur mode:%s", path, changed_props["Model"]:value())
    end)
end

return fans_data_keeping
