-- 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.

-- Description: fructrl object management创建8个host对象，并管理对应分发的对象，管理多host共享对象：ac、button_evt
local class = require 'mc.class'
local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local m_enums = require 'types.enums'
local object_manage = require 'mc.mdb.object_manage'
local base_messages = require 'messages.base'
local m_error_custom = require 'messages.custom'
local m_error_base = require 'messages.base'
local c_button_evt = require 'button_evt'
local c_fructrl_obj = require 'fructrl_obj'
local mdb = require 'mc.mdb'
local chassis_fructrl = require 'chassis_fructrl.power_ctrl'
local system_fructrl = require 'system_fructrl.power_ctrl'
local multi_host = require 'multi_host'
local fructrl_capabilities = require 'fructrl_capabilities'
local ac_cycle = require 'ac_cycle'
local fructrl_proxy = require 'fructrl_proxy'
local skynet = require 'skynet'
local synctool = require 'synctool'
local c_bmc_start_flag = require 'system_fructrl.bmc_start_flag'
local c_system_poh = require 'system_fructrl.system_poh'

local fructrl_obj_mgnt = class()

function fructrl_obj_mgnt:ctor(db, bus)
    self.db = db
    self.bus = bus
    self.host_obj = {}
    self.multihost = multi_host.new()
    self.fructrl_capabilities = fructrl_capabilities.new()
end

local function onehost_system_power_ctrl(self, system_id, ctx, PowerCtrlType, RestartCause)
    log:notice('host %s call power ctrl, ctrl_type=%s, reason=%s, fruid=%s', system_id,
        PowerCtrlType, RestartCause, ctx['fru_id'])
    return self.host_obj[system_id].powerapi:system_reset(ctx, PowerCtrlType, RestartCause)
end

function fructrl_obj_mgnt:init()
    self.button_evt = c_button_evt.new(self.db)
    self.c_bmc_start_flag = c_bmc_start_flag.new()
    self.c_system_poh = c_system_poh.new()
    self.system_action_callback = onehost_system_power_ctrl
    synctool.new(self.host_obj, self.multihost)
end

local LOCK_FOREVER = 0xFFFF -- 用特殊的超时时间代表永久锁

local chassis_power_lock_mgmt = {
    power_on_lock_info = {},
    power_off_lock_info = {}
}
function chassis_power_lock_mgmt:add_lock(app_name, reason, type)
    if type == 'on' then
        self.power_on_lock_info[app_name] = reason
        log:notice("add chassis power on lock, appname:%s reason:%s", app_name, reason)
    else
        self.power_off_lock_info[app_name] = reason
        log:notice("add chassis power off lock, appname:%s reason:%s", app_name, reason)
    end
end

function chassis_power_lock_mgmt:delete_lock(app_name, type)
    if type == 'on' then
        self.power_on_lock_info[app_name] = nil
        log:notice("delete chassis power on lock, appname:%s", app_name)
    else
        self.power_off_lock_info[app_name] = nil
        log:notice("delete chassis power off lock, appname:%s", app_name)
    end
end

function chassis_power_lock_mgmt:update_lock(locked, app_name, reason, type)
    if locked then
        self:add_lock(app_name, reason, type)
    else
        self:delete_lock(app_name, type)
    end
end

local VERSION <const> = {
    [1] = '01010318',
    [2] = '0101031A',
    [3] = '01010317',
    [4] = '01010319',
    [5] = '01010320',
    [6] = '01010322',
    [7] = '0101031F',
    [8] = '01010321'
}

function fructrl_obj_mgnt:on_preprocess_cb(object)
    local system_id = object:get_system_id()
    if object:get_position() ~= "00" and object:get_position() == VERSION[system_id] then
        return false
    end
    return true
end

function fructrl_obj_mgnt:object_manage_callback()
    -- 添加对象回调
    object_manage.on_add_object(self.bus, function(class_name, object, position)
        self:add_objects_callback(class_name, object, position)
    end, function(object)
        return self:on_preprocess_cb(object)
    end)
    -- 删除对象回调
    object_manage.on_delete_object(self.bus, function(class_name, object, position)
        log:notice('[Fructrl] Delete object callback, class_name: ' .. class_name ..
            ', object name: ' .. object.name .. ', position: ' .. position)
    end)
    -- 添加对象完成回调
    object_manage.on_add_object_complete(self.bus, function(position)
        self:add_object_complete_callback(position)
    end)
    -- 删除对象完成回调
    object_manage.on_delete_object_complete(self.bus, function(position)
        log:notice('[Fructrl] Delete object complete callback, position: ' .. position)
    end)
end

function fructrl_obj_mgnt:add_objects_callback(class_name, object, position)
    local system_id = object:get_system_id()
    if object:get_position() ~= "00" and position == VERSION[system_id] then
        return
    end
    log:notice('[System:%s]Add object callback, class_name: %s, object name: %s, position: %s.',
        system_id, class_name, object.name, position)

    if not self.host_obj[system_id] then
        self.host_obj[system_id] = c_fructrl_obj.new(self.db, self.bus, system_id)
        if self.chassis_fructrl_ins and self.chassis_fructrl_ins.chassis_fructrl_obj and
            self.chassis_fructrl_ins.chassis_fructrl_obj.PowerOnStrategy then
            self.host_obj[system_id]:update_chassis_poweron_strategy(
                self.chassis_fructrl_ins.chassis_fructrl_obj.PowerOnStrategy)
        end
        log:notice('create host %s object successful', system_id)
    end
    if class_name == 'Multihost' then
        self.multihost:register_object(object)
    elseif class_name == 'FruCtrlCapabilities' then
        self.fructrl_capabilities:register_object(object)
    elseif class_name == 'ACCycle' then
        if not self.accycle then
            self.accycle = ac_cycle.new(self.db)
        end
        self.accycle:insert_accycle_object(object)
    elseif class_name == 'ButtonEvt' then
        self.button_evt:add_buttonevt_callback(object, self.host_obj[1].fructrl)
    elseif class_name == 'ChassisFruCtrl' then
        self:add_chassis_fructrl(object)
    elseif class_name == 'BmcStartFlag' then
        self.c_bmc_start_flag:add_bmcstartflag_callback(object)
    elseif class_name == 'POH' then
        self.c_system_poh:add_poh_callback(object, self.host_obj)
    else
        self.host_obj[system_id]:add_object_callback(class_name, object,
            chassis_power_lock_mgmt.power_on_lock_info, position)
    end
end

function fructrl_obj_mgnt:add_object_complete_callback(position)
    log:notice('[Fructrl] Add object complete callback, position: ' .. position)
    for k, _ in pairs(self.host_obj) do
        self.host_obj[k]:add_fruobj_complete_callback(self.c_bmc_start_flag)
    end
end

local function obj_get_system_id(self, obj, intf)
    local ok, fructrl_obj = pcall(mdb.get_object, self.bus, obj.path, intf)
    if not ok then
        return nil
    end

    return fructrl_obj.extra_params.SystemId
end

function fructrl_obj_mgnt:add_chassis_fructrl(object)
    local function check_host_in_place(count, objs)
        for i=1, count do
            if not objs[i] then
                return false
            end
        end
        return true
    end

    local function wait_host_objs(host_count, host_objs)
        -- 1min内Fructrl对象未分发，则超时退出
        for times=1, 60 do
            if check_host_in_place(host_count, host_objs) then
                log:notice("%s fructrl objs is in place", host_count)
                return
            end
            skynet.sleep(100)
        end
        log:warn("wait host objs[%s] time out", host_count)
    end
    skynet.fork(function ()
        wait_host_objs(self.multihost:get_host_count(), self.host_obj)
        self.fructrl_proxy = fructrl_proxy.new(object, self.multihost, self.host_obj)
        self.chassis_fructrl_ins = chassis_fructrl.new(self.bus, object, self.fructrl_proxy)
        self.system_fructrl_ins = system_fructrl.new(self.fructrl_proxy, self.host_obj)
        self.system_action_callback = function (self, system_id, ctx, PowerCtrlType, RestartCause)
            return self.system_fructrl_ins:power_ctrl(system_id, ctx, PowerCtrlType, RestartCause)
        end
    end)
end

function fructrl_obj_mgnt:chassis_power_ctrl(ctx, PowerCtrlType, RestartCause)
    return self.chassis_fructrl_ins:power_ctrl(ctx, PowerCtrlType, RestartCause)
end

function fructrl_obj_mgnt:chassis_set_poweron_lock(obj, ctx, PwrOnLocked, Timeout, AppName, Reason)
    local ok, ret = pcall(function ()
        return self.chassis_fructrl_ins:set_poweron_lock(obj, ctx, PwrOnLocked, Timeout, AppName, Reason)
    end)
    if not ok then
        log:error("chassis set poweron lock failed, err: %s", ret)
        error(base_messages.InternalError())
    end

    if Timeout == LOCK_FOREVER then
        chassis_power_lock_mgmt:update_lock(PwrOnLocked, AppName, Reason, 'on')
    end

    return ret
end

function fructrl_obj_mgnt:chassis_set_poweroff_lock(obj, ctx, Locked, Timeout, AppName, Reason)
    local ok, ret = pcall(function ()
        return self.chassis_fructrl_ins:set_poweroff_lock(obj, ctx, Locked, Timeout, AppName, Reason)
    end)
    if not ok then
        log:error("chassis set poweroff lock failed, err: %s", ret)
        error(base_messages.InternalError())
    end

    if Timeout == LOCK_FOREVER then
        chassis_power_lock_mgmt:update_lock(Locked, AppName, Reason, 'off')
    end
end

local function check_param_of_poweron_strategy_exceptions(Reason, Execute, EffectivePeriod, Priority)
    if #tostring(Reason) > 10 then
        error(m_error_custom.ValueOutOfRange('%Reason'))
    end
    if tostring(Execute) ~= 'Yes' and tostring(Execute) ~= 'No' and tostring(Execute) ~= 'None' then
        error(m_error_base.PropertyValueNotInList('%Execute:' .. Execute, '%Execute'))
    end
    if tonumber(Priority) == 0 or tonumber(Priority) > 10 then
        error(m_error_custom.ValueOutOfRange('%Priority'))
    end
    if tostring(EffectivePeriod) ~= 'Once' and tostring(EffectivePeriod) ~= 'Continuous' then
        error(m_error_base.PropertyValueNotInList('%EffectivePeriod:' .. EffectivePeriod, '%EffectivePeriod'))
    end
end

function fructrl_obj_mgnt:chassis_set_poweron_strategy_exceptions(obj, ctx, Reason, Execute, EffectivePeriod, Priority)
    check_param_of_poweron_strategy_exceptions(Reason, Execute, EffectivePeriod, Priority)
    local ok, ret = pcall(function ()
        return self.chassis_fructrl_ins:set_poweron_strategy_exceptions(obj, ctx, Reason, Execute, EffectivePeriod,
            Priority)
    end)
    if not ok then
        log:error("chassis set poweron strategy failed, err: %s", ret)
        error(base_messages.InternalError())
    end
    return ret
end

function fructrl_obj_mgnt:PowerCtrl(obj, ctx, PowerCtrlType, RestartCause, FruID)
    local system_id = ctx.SystemId and tonumber(ctx.SystemId) or
        (obj_get_system_id(self, obj, 'bmc.kepler.Systems.FruCtrl') or 1)
    if not FruID then
        ctx['fru_id'] = 0
    else
        ctx['fru_id'] = FruID
    end
    log:notice('[System:%s]call PowerCtrl method, PowerCtrlType=%s, RestartCause=%s, FruID=%s.',
        system_id, PowerCtrlType, RestartCause, ctx['fru_id'])
    return self:system_action_callback(system_id, ctx, PowerCtrlType, RestartCause)
end

function fructrl_obj_mgnt:SetPowerOnLock(obj, ctx, PwrOnLocked, Timeout, AppName, Reason)
    local system_id = ctx.SystemId and tonumber(ctx.SystemId) or
        (obj_get_system_id(self, obj, 'bmc.kepler.Systems.PowerOnLock') or 1)

    log:notice('[System:%s]set poweron lock, pwronlocked=%s, timeout=%d, appname=%s, reason=%s',
        system_id, PwrOnLocked, Timeout, AppName, Reason)
    self.host_obj[system_id].fructrl.power_on_lock:power_lock_entrance(PwrOnLocked, Timeout, AppName, Reason)
    return 0
end

function fructrl_obj_mgnt:SetPowerOffLock(obj, ctx, Locked, Timeout, AppName, Reason)
    local system_id = ctx.SystemId and tonumber(ctx.SystemId) or
        (obj_get_system_id(self, obj, 'bmc.kepler.FruCtrl.PowerOffLock') or 1)

    log:notice('[System:%s]set powerff lock, locked=%s, timeout=%d, appname=%s, reason=%s',
        system_id, Locked, Timeout, AppName, Reason)
    self.host_obj[system_id].fructrl.power_off_lock:power_lock_entrance(Locked, Timeout, AppName, Reason)
end

function fructrl_obj_mgnt:SetPowerOnStrategyExceptions(obj, ctx, Reason, Execute, EffectivePeriod, Priority)
    local system_id = ctx.SystemId and tonumber(ctx.SystemId) or
        (obj_get_system_id(self, obj, 'bmc.kepler.Systems.FruCtrl') or 1)
    log:notice('[System:%s]set power restore policy, reason=%s, execute=%s, period=%s, priority=%s',
        system_id, Reason, Execute, EffectivePeriod, Priority)
    check_param_of_poweron_strategy_exceptions(Reason, Execute, EffectivePeriod, Priority)
    self.host_obj[system_id].pwr_restore:update_pwr_restore(Reason, Execute, EffectivePeriod, tostring(Priority))
    return 0
end

function fructrl_obj_mgnt:SetACLost(obj, ctx, Type)
    local system_id = ctx.SystemId and tonumber(ctx.SystemId) or
        (obj_get_system_id(self, obj, 'bmc.kepler.Systems.FruCtrl') or 1)
    return self.host_obj[system_id].fructrl:set_ACLost(Type)
end

return singleton(fructrl_obj_mgnt)