-- 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 log = require 'mc.logging'
local utils = require 'types.utils'
local m_enums = require 'types.enums'
local class = require 'mc.class'
local m_error_custom = require 'messages.custom'

local POWESTATE_ENUMS = {
    ["OFFING"] = 'ON',
    ["OFF"] = 'OFF',
    ["ONING"] = 'OFF',
    ["ON"] = 'ON',
}

local function get_system_powerstate(self, system_id)
    if self.host_objs[system_id] and self.host_objs[system_id].fructrl and
            POWESTATE_ENUMS[self.host_objs[system_id].fructrl:get_PowerState()] == 'OFF' then
        return true
    end
    return false
end

local function check_before_poweron(self, system_id, ctx)
    if self.host_objs[system_id] and self.host_objs[system_id].fructrl and
        self.host_objs[system_id].fructrl:get_PwrOnLocked() and not utils.is_permit_requestor(ctx.requestor) then
            local ret, reason = self.host_objs[system_id].fructrl.power_on_lock:is_power_locked_by_upgrade()
            if ret and (ctx.Interface == 'CLI' or ctx.Interface == 'WEB') then
                log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to power' ..
                ' on failed')
                error(m_error_custom.PowerOnNotAllowed(reason .. ' is upgrading!'))
            end
        return false
    end
    return true
end

local function check_before_poweroff(self, system_id)
    if self.host_objs[system_id] and self.host_objs[system_id].fructrl and
        self.host_objs[system_id].fructrl:get_PowerOffLocked() then
        return false
    end
    return true
end

local system_power_ctrl = class()

local system_powerctrl_api = {
    [m_enums.PowerCtrlType.On] = function (self, system_id, ctx, cause)
        -- 当前有锁且没有特殊上下文，直接报错，不插入队列
        if not check_before_poweron(self, system_id, ctx) then
            log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to power' ..
                ' on failed')
            error(m_error_custom.OperationFailed())
        end
        self.fructrl_proxy_ins:one_host_power_on(system_id, cause, ctx)
        log:operation(ctx:get_initiator(), 'fructrl', '[System%s]wait for power on', system_id)
        return 0
    end,
    [m_enums.PowerCtrlType.GracefulShutdown] = function(self, system_id, ctx, cause)
        if not check_before_poweroff(self, system_id) then
            log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to power' ..
                ' off failed')
            error(m_error_custom.PowerOffNotAllowed('system power off lock exists'))
        end
        self.fructrl_proxy_ins:one_host_power_off(system_id, ctx, m_enums.PowerCtrlType.GracefulShutdown, cause)
        return 0
    end,
    [m_enums.PowerCtrlType.ForceOff] = function (self, system_id, ctx, cause)
        if not check_before_poweroff(self, system_id) then
            log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to power' ..
                ' off failed')
            error(m_error_custom.PowerOffNotAllowed('system power off lock exists'))
        end
        self.fructrl_proxy_ins:one_host_power_off(system_id, ctx, m_enums.PowerCtrlType.ForceOff, cause)
        return 0
    end,
    [m_enums.PowerCtrlType.ForceRestart] = function (self, system_id, ctx, cause)
        if get_system_powerstate(self, system_id) then
            log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to forced' ..
                ' system reset failed')
            error(m_error_custom.OperationFailed())
        end
        self.fructrl_proxy_ins:one_host_restart(system_id, ctx, cause)
        log:operation(ctx:get_initiator(), 'fructrl', '[System%s]wait for forced system reset', system_id)
        return 0
    end,
    [m_enums.PowerCtrlType.PowerCycle] = function(self, system_id, ctx, cause)
        if not check_before_poweroff(self, system_id) then
            log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to normal ' ..
                'power off the service system, and then power it on failed')
            error(m_error_custom.PowerOffNotAllowed('system power off lock exists'))
        end
        if not self.host_objs[system_id] or not self.host_objs[system_id].powerapi then
            return m_enums.RetValue.ERR
        end
        if get_system_powerstate(self, system_id) then
            log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to normal ' ..
                'power off the service system, and then power it on failed')
            error(m_error_custom.OperationFailed())
        end
        self.fructrl_proxy_ins:one_host_cycle(system_id, ctx, cause, m_enums.PowerCtrlType.PowerCycle)
        log:operation(ctx:get_initiator(), 'fructrl', '[System%s]wait for normal power ' ..
            'off the service system, and then power it on', system_id)
        return 0
    end,
    [m_enums.PowerCtrlType.ForcePowerCycle] = function(self, system_id, ctx, cause)
        if not check_before_poweroff(self, system_id) then
            log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to forced power off ' ..
                'the service system and then power it on failed')
            error(m_error_custom.PowerOffNotAllowed('system power off lock exists'))
        end
        if not self.host_objs[system_id] or not self.host_objs[system_id].powerapi then
            return m_enums.RetValue.ERR
        end
        if get_system_powerstate(self, system_id) then
            log:system(system_id):operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to forced power off ' ..
                'the service system, and then power it on failed')
            error(m_error_custom.OperationFailed())
        end
        self.fructrl_proxy_ins:one_host_cycle(system_id, ctx, cause, m_enums.PowerCtrlType.ForcePowerCycle)
        log:operation(ctx:get_initiator(), 'fructrl', '[System%s]wait for forced power ' ..
            'off the service system, and then power it on', system_id)
        return 0
    end,
    [m_enums.PowerCtrlType.Nmi] = function(self, system_id, ctx, cause)
        if not self.host_objs[system_id] or not self.host_objs[system_id].powerapi then
            return m_enums.RetValue.ERR
        end
        return self.host_objs[system_id].powerapi:system_reset(ctx, m_enums.PowerCtrlType.Nmi, cause)
    end,
    [m_enums.PowerCtrlType.ACCycle] = function(self, system_id, ctx, cause)
        if not self.host_objs[system_id] or not self.host_objs[system_id].powerapi then
            return m_enums.RetValue.ERR
        end
        return self.host_objs[system_id].powerapi:system_reset(ctx, m_enums.PowerCtrlType.ACCycle, cause)
    end
}

function system_power_ctrl:ctor(fructrl_proxy_ins, host_objs)
    self.fructrl_proxy_ins = fructrl_proxy_ins
    self.host_objs = host_objs
end

function system_power_ctrl:power_ctrl(system_id, ctx, power_ctrl_type, cause)
    if not system_powerctrl_api[power_ctrl_type] then
        log:operation(ctx:get_initiator(), 'fructrl', 'Set system %s to %s failed', system_id, power_ctrl_type)
        error(m_error_custom.ValueOutOfRange('%OperateType'))
    end
    return system_powerctrl_api[power_ctrl_type](self, system_id, ctx, cause)
end

return system_power_ctrl