-- 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 client = require 'storage.client'
local class = require 'mc.class'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local c_drive_collection = require 'drive.drive_collection'
local c_controller_collection = require 'controller.controller_collection'
local c_storageconfig = require 'storageconfig.storageconfig_object'
local c_mctp_service = require 'mctp.mctp_service'
local log = require 'mc.logging'
local signal = require 'mc.signal'
local c_tasks = require 'mc.orm.tasks'
local mdb = require 'mc.mdb'
local common_def = require 'common_def'
local STORAGE_MGNT_STATE = common_def.STORAGE_MGNT_STATE

local c_bus_monitor_service = class()

function c_bus_monitor_service:ctor(bus)
    self.bus = bus
    self.bus_monitors = {}
    self.on_os_state_changed = signal.new()
    self.on_smbios_status_changed = signal.new()
    self.power_state = '0'
    self.task = nil
end

local SYSTEM_PATH <const> = '/bmc/kepler/Systems/'
local FRU_PATH <const> = '/FruCtrl'

function c_bus_monitor_service:init()
    self:fru_monitor()
    self:smbios_monitor()
    self:drive_monitor()
    self.on_os_state_changed:on(function (status)
        self:set_os_power_status(status)
    end)
end

-- 根据BmcEid确定当前的OS状态
function c_bus_monitor_service:set_os_power_status(status)
    if not status then
        log:notice('[Storage]OS Off')
        c_storageconfig.get_instance():change_power_state(0)
        c_storageconfig.get_instance().on_update_flag:emit(STORAGE_MGNT_STATE.STORAGE_MGNT_NOT_READY)
        c_storageconfig.get_instance():clear_all_controller_info()
    else
        log:notice('[Storage]OS Ready')
        c_storageconfig.get_instance():change_power_state(1)
        c_storageconfig.get_instance().on_update_flag:emit(STORAGE_MGNT_STATE.STORAGE_MGNT_NO_CTRLS)
    end
    log:notice('[Storage]c_mctp_service change power state to %s', status and 'ON' or 'OFF')
    c_mctp_service.get_instance():change_power_state(status)
    c_controller_collection.get_instance().on_os_state_changed:emit(status)
    c_drive_collection.get_instance().on_smbios_status_changed:emit(status)
end

function c_bus_monitor_service:fru_monitor()
    -- 监视FRU状态
    local fru_path = string.format('%s%d%s', SYSTEM_PATH, 1, FRU_PATH)
    local fru_bus_sig = org_freedesktop_dbus.MatchRule.signal('PropertiesChanged'):with_interface(
        org_freedesktop_dbus.SD_BUS_PROPERTIES):with_path_namespace(fru_path)

    -- 注册响应函数
    local fru_slot = self.bus:match(fru_bus_sig, function(msg)
        local _, values = msg:read('sa{sv}as')
        if values.PowerState ~= nil then
            c_storageconfig.get_instance().power_state = values.PowerState:value()
            if (self.power_state == 'ON' or self.power_state == 'OFFING') and
                (values.PowerState:value() == 'OFF') then
                log:notice('[monitor-power] monitor off, current power state is %s, change to %s',
                    self.power_state, values.PowerState:value())
                self:set_os_power_status(false)
            end
            log:notice('[monitor-power] set power state from %s to %s', self.power_state, values.PowerState:value())
            self.power_state = values.PowerState:value()
        elseif values.SysResetDetected ~= nil then
            log:notice('[monitor-power] monitor system reset, power state is %s, SysResetDetected value is %s',
                self.power_state, values.SysResetDetected:value())
            if self.power_state == 'ON' and values.SysResetDetected:value() == 1 then
                self:set_os_power_status(false)
            end
        end
    end)

    -- 当模块重启时需要查询一下当前的FRU状态，如果FRU状态正常，则直接启动后面的流程
    local ok, obj_list
    log:notice('Start init fru state, power_state:%s.', self.power_state)
    self.task = c_tasks.get_instance():new_task('Init_Fru_State'):loop(function(task)
        ok, obj_list = pcall(mdb.get_sub_objects, self.bus, fru_path, 'bmc.kepler.Systems.FruCtrl')
        if not ok or next(obj_list) == nil then
            log:error('get fructrl object failed')
            return
        end
        for _, obj in pairs(obj_list) do
            if obj.PowerState ~= '' then
                log:notice('[init] old power state is %s, new is %s', self.power_state, obj.PowerState)
            end
            self.power_state = obj.PowerState
            c_storageconfig.get_instance().power_state = obj.PowerState
            if self.power_state == '' then
                return
            end
            if self.power_state == 'ON' then
                self:set_os_power_status(true)
                break
            end
        end
        task:stop()
        self.task = nil
    end):set_timeout_ms(1000)

    self.bus_monitors['PowerOff'] = fru_slot
end

local SMBIOS_PATH <const> = '/SmBios'
function c_bus_monitor_service:smbios_monitor()
    -- 监视SMBIOS状态
    local smbios_path = string.format('%s%d%s', SYSTEM_PATH, 1, SMBIOS_PATH)
    local smbios_bus_sig = org_freedesktop_dbus.MatchRule.signal('PropertiesChanged'):with_interface(
        org_freedesktop_dbus.SD_BUS_PROPERTIES):with_path_namespace(smbios_path)
    self:update_init_smbios_status()
    -- 注册响应函数
    local smbios_slot = self.bus:match(smbios_bus_sig, function(msg)
        local _, values = msg:read('sa{sv}as')
        if values.SmBiosStatus ~= nil then
            log:notice('[monitor-power] SmBiosStatus is %s, power_state is %s',
                values.SmBiosStatus:value(), self.power_state)
            local smbios_status = values.SmBiosStatus:value()
            if smbios_status == 3 and (self.power_state == 'ON' or self.power_state == 'ONING') then
                self:set_os_power_status(true)
            end
            self.on_smbios_status_changed:emit(smbios_status)
            c_storageconfig.get_instance():change_smbios_status(smbios_status)
        end
    end)

    self.bus_monitors['SmBiosOn'] = smbios_slot
end

-- 监听之前先更新一次SmbiosStatus的值
local SMBIOS_INTF <const> = 'bmc.kepler.Systems.SmBios'
function c_bus_monitor_service:update_init_smbios_status()
    local smbios_path = string.format('%s%d%s', SYSTEM_PATH, 1, SMBIOS_PATH)
 
    local ok, obj_list = pcall(mdb.get_sub_objects, self.bus, smbios_path, SMBIOS_INTF)
    if not ok or next(obj_list) == nil then
        log:error('get smbios object failed')
        return
    end
 
    for _, obj in pairs(obj_list) do
        self.on_smbios_status_changed:emit(obj.SmBiosStatus)
        c_storageconfig.get_instance():change_smbios_status(obj.SmBiosStatus)
    end
end

local DRIVE_PATH <const> = '/Storage/Drives/'
-- 监听资源树上drive对象下属性的变化
function c_bus_monitor_service:drive_monitor()
    -- 监听drive对象的生成
    c_drive_collection.get_instance().on_add_drive:on(function(drive)
        -- 监视自身状态变化
        local path = string.format('%s%d%s%s', SYSTEM_PATH, 1, DRIVE_PATH, drive.name)
        local bus_sig = org_freedesktop_dbus.MatchRule.signal('PropertiesChanged'):with_path_namespace(path)
        -- 注册响应函数
        local slot = self.bus:match(bus_sig, function(msg)
            local _, values = msg:read('sa{sv}as')
            for k, v in pairs(values) do
                drive.on_property_changed:emit(k, v:value())
            end
        end)
        self.bus_monitors[drive.ObjectName] = slot
    end)

    c_drive_collection.get_instance().on_del_drive:on(function(drive)
        local slot = self.bus_monitors[drive.ObjectName]
        if slot then
            slot:free()
            self.bus_monitors[drive.ObjectName] = nil
        end
    end)
end

function c_bus_monitor_service:dump_smbios_info(fp_w)
    local objs = client:GetSmBiosObjects()
    if not next(objs) then
        log:error('SmBiosObjects is null!')
        return
    end
    for _, smbios_obj in pairs(objs) do
        local info_str = string.format('Current SmBiosStatus = %s\n', smbios_obj.SmBiosStatus)
        fp_w:write(info_str)
        return
    end
end

function c_bus_monitor_service:dump_bios_info(fp_w)
    local objs = client:GetBiosObjects()
    if not next(objs) then
        log:error('BiosObjects is null!')
        return
    end
    for _, bios_obj in pairs(objs) do
        local info_str = string.format('Current BiosBootStage = %s\n', bios_obj.BiosBootStage)
        fp_w:write(info_str)
        return
    end
end

function c_bus_monitor_service:dump_objs_info(fp_w)
    self:dump_smbios_info(fp_w)
    fp_w:write("==============================\n")
    self:dump_bios_info(fp_w)
    fp_w:write("==============================\n")
end

return singleton(c_bus_monitor_service)