-- 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: power state machine management

local skynet = require 'skynet'
local log = require 'mc.logging'
local class = require 'mc.class'
local m_enums = require 'types.enums'
local coroutine = require 'skynet.coroutine'

-- class power_state
local cpower_state = class()

local EPowerState = {
    PowerState_NoAction = 0,
    PowerState_Oning = 1,
    PowerState_Offing = 2,
    PowerState_WaitingOn = 3,
    PowerState_WaitingOff = 4,
    PowerState_OningWaitingOff = 5,
    PowerState_OffingWaitingOn = 6
}

function cpower_state:ctor(hotswap, fructrl, id)
    self.state = EPowerState.PowerState_NoAction
    self.fructrl = fructrl
    self.hs = hotswap
    self.system_id = id

    -- 创建协程
    self.co = coroutine.create(function()
        local evt, fruid
        while true do
            -- 阻塞，获取resume发来的事件
            evt, fruid = coroutine.yield()
            self:state_machine_run(evt, fruid)
        end
    end)
    coroutine.resume(self.co)
end

function cpower_state:send_powerctrl_event(val, fruid)
    if val == "" then
        return
    end
    -- 发送事件
    coroutine.resume(self.co, val, fruid)
end

function cpower_state:state_no_action(event, fruid)
    if event == tostring(m_enums.CtrlEventTypes.CtrlPowerOn) then
        self.state = EPowerState.PowerState_Oning
        log:notice("[System:%s]From NoAction to Oning.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_noaction_oning(fruid)
        end)
        return
    end
    if event == tostring(m_enums.CtrlEventTypes.CtrlPowerOff) then
        self.state = EPowerState.PowerState_Offing
        log:notice("[System:%s]From NoAction to Offing.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_noaction_offing(fruid)
        end)
        return
    end
end

function cpower_state:state_oning(event, fruid)
    if event == tostring(m_enums.CtrlEventTypes.CtrlPowerOff) then
        self.state = EPowerState.PowerState_OningWaitingOff
        log:notice("[System:%s]From Oning to OningWaitingOff.", self.system_id)
        return
    end
    if event == tostring(m_enums.CtrlEventTypes.CtrlFinished) then
        self.state = EPowerState.PowerState_NoAction
        log:notice("[System:%s]From Oning to NoAction.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_oning_noaction()
        end)
        return
    end
end

function cpower_state:state_offing(event, fruid)
    if event == tostring(m_enums.CtrlEventTypes.CtrlPowerOn) then
        self.state = EPowerState.PowerState_OffingWaitingOn
        log:notice("[System:%s]From Offing to OffingWaitingOn.", self.system_id)
        return
    end
    if event == tostring(m_enums.CtrlEventTypes.CtrlFinished) then
        self.state = EPowerState.PowerState_NoAction
        log:notice("[System:%s]From Offing to NoAction.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_offing_noaction()
        end)
        return
    end
end

function cpower_state:state_waitting_on(event, fruid)
    if event == tostring(m_enums.CtrlEventTypes.CtrlPowerOff) then
        self.state = EPowerState.PowerState_NoAction
        log:notice("[System:%s]From WaitingOn to NoAction.", self.system_id)
        return
    end
    if event == tostring(m_enums.CtrlEventTypes.CtrlFinished) then
        self.state = EPowerState.PowerState_Oning
        log:notice("[System:%s]From WaitingOn to Oning.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_waitingon_oning(fruid)
        end)
        return
    end
end

function cpower_state:state_waitting_off(event, fruid)
    if event == tostring(m_enums.CtrlEventTypes.CtrlPowerOn) then
        self.state = EPowerState.PowerState_NoAction
        log:notice("[System:%s]From WaitingOff to NoAction.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_waitingoff_noaction(fruid)
        end)
        return
    end
    if event == tostring(m_enums.CtrlEventTypes.CtrlFinished) then
        self.state = EPowerState.PowerState_Offing
        log:notice("[System:%s]From WaitingOff to Offing.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_waitingoff_offing(fruid)
        end)
        return
    end
end

function cpower_state:state_oning_waitting_off(event, fruid)
    if event == tostring(m_enums.CtrlEventTypes.CtrlPowerOn) then
        self.state = EPowerState.PowerState_Oning
        log:notice("[System:%s]From OningWaitingOff to Oning.", self.system_id)
        return
    end
    if event == tostring(m_enums.CtrlEventTypes.CtrlFinished) then
        self.state = EPowerState.PowerState_WaitingOff
        log:notice("[System:%s]From OningWaitingOff to WaitingOff.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_oningwaitingoff_waitingoff(fruid)
        end)
        return
    end
end

function cpower_state:state_offing_waitting_on(event, fruid)
    if event == tostring(m_enums.CtrlEventTypes.CtrlPowerOff) then
        self.state = EPowerState.PowerState_Offing
        log:notice("[System:%s]From OffingWaitingOn to Offing.", self.system_id)
        return
    end
    if event == tostring(m_enums.CtrlEventTypes.CtrlFinished) then
        self.state = EPowerState.PowerState_WaitingOn
        log:notice("[System:%s]From OffingWaitingOn to WaittingOn.", self.system_id)

        skynet.fork_once(function()
            self:pwr_action_offingwaitingon_waitingon(fruid)
        end)
        return
    end
end

-- 状态机行为，后期扩展主要改这里
function cpower_state:pwr_action_noaction_oning(fruid)
    -- 上电前准备，广播通知其他App有禁止上电的可以加锁了
    if self.fructrl:before_poweron() then
        -- 驱动热插拔状态机
        self.hs:send_hotswap_event(tostring(m_enums.HotswapEventTypes.FruInsertionCriteriaMet))
        -- 上电，服务器历史沿用策略，重试3次上电，防止偶然的硬件故障导致上电失败
        local NUM = 3
        for k = 1, NUM do
            if self.fructrl:poweron() then
                break
            end
            if k == NUM then
                log:error('[System:%s]Power on timeout, change status to off', self.system_id)
                self.fructrl:set_PowerState(m_enums.PowerStatus.OFF)
            end
        end
    end

    -- 驱动控制状态机
    self:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlFinished), fruid)
end

function cpower_state:pwr_action_noaction_offing(fruid)
    -- 驱动热插拔状态机，去激活
    self.hs:send_hotswap_event(tostring(m_enums.HotswapEventTypes.FruDeactivated))
    -- 下电
    self.fructrl:poweroff(fruid)
    -- 驱动控制状态机
    self:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlFinished), fruid)
end

function cpower_state:pwr_action_oning_noaction()
    if self.fructrl:get_PowerState() == tostring(m_enums.PowerStatus.ON) then
        -- 驱动热插拔状态机，激活完成
        self.hs:send_hotswap_event(tostring(m_enums.HotswapEventTypes.FruActivatedCompleted))
    end
end

function cpower_state:pwr_action_offing_noaction()
    if self.fructrl:get_PowerState() == tostring(m_enums.PowerStatus.OFF) then
        -- 驱动热插拔状态机，去激活完成
        self.hs:send_hotswap_event(tostring(m_enums.HotswapEventTypes.FruDeactivatedCompleted))
    end
end

function cpower_state:pwr_action_oningwaitingoff_waitingoff(fruid)
    -- 延时一会儿，如果当前状态依然在WaitingOff，就传递个CtrlFinished
    self:send_powerctrl_event("", fruid)
    skynet.sleep(200)
    if self.state == EPowerState.PowerState_WaitingOff then
        self:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlFinished), fruid)
    end
end

function cpower_state:pwr_action_offingwaitingon_waitingon(fruid)
    -- 延时一会儿，如果当前状态依然在WaitingOn，就传递个CtrlFinished
    self:send_powerctrl_event("", fruid)
    skynet.sleep(200)
    if self.state == EPowerState.PowerState_WaitingOn then
        self:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlFinished), fruid)
    end
end

function cpower_state:pwr_action_waitingoff_offing(fruid)
    -- 驱动热插拔状态机，去激活
    self.hs:send_hotswap_event(tostring(m_enums.HotswapEventTypes.FruDeactivated))
    -- 下电
    self.fructrl:poweroff(fruid)
    -- 驱动控制状态机
    self:send_powerctrl_event("", fruid)
    skynet.sleep(1)
    self:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlFinished), fruid)
end

function cpower_state:pwr_action_waitingon_oning(fruid)
    -- 上电前准备，广播通知其他App有禁止上电的可以加锁了
    if self.fructrl:before_poweron() then
        -- 驱动热插拔状态机，插入条件满足
        self.hs:send_hotswap_event(tostring(m_enums.HotswapEventTypes.FruInsertionCriteriaMet))
        -- 上电，服务器历史沿用策略，重试3次上电，防止偶然的硬件故障导致上电失败
        local NUM = 3
        for k = 1, NUM do
            if self.fructrl:poweron() then
                break
            end
            if k == NUM then
                log:error('[System:%s]Power on timeout, change status to off', self.system_id)
                self.fructrl:set_PowerState(m_enums.PowerStatus.OFF)
            end
        end
    end

    -- 驱动控制状态机
    self:send_powerctrl_event("", fruid)
    skynet.sleep(1)
    self:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlFinished), fruid)
end

function cpower_state:pwr_action_waitingoff_noaction(event)
    -- 驱动热插拔状态机，激活完成
    self.hs:send_hotswap_event(tostring(m_enums.HotswapEventTypes.FruActivatedCompleted))
end

-- 本表的定义需要在state_x等接口定义之后
local powerctrl_funcs = {
    [EPowerState.PowerState_NoAction] = cpower_state.state_no_action,
    [EPowerState.PowerState_Oning] = cpower_state.state_oning,
    [EPowerState.PowerState_Offing] = cpower_state.state_offing,
    [EPowerState.PowerState_WaitingOn] = cpower_state.state_waitting_on,
    [EPowerState.PowerState_WaitingOff] = cpower_state.state_waitting_off,
    [EPowerState.PowerState_OningWaitingOff] = cpower_state.state_oning_waitting_off,
    [EPowerState.PowerState_OffingWaitingOn] = cpower_state.state_offing_waitting_on
}

function cpower_state:state_machine_run(event, fruid)
    powerctrl_funcs[self.state](self, event, fruid)
end

return cpower_state
