-- 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: powercycle 相关操作，依赖fructrl和控制状态机

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

local c_pwr_cycle = class()

function c_pwr_cycle:ctor(fructrl, ctrlstate, id)
    self.fructrl = fructrl
    self.ctrlstate = ctrlstate
    self.cycle_interrupt_flag = false -- 是否存在打断cycle动作的标记，false不存在，true存在
    self.system_id = id
    self.pwr_off_interval = 0 -- 记录从下电指令到达到真正下电的时间
end

function c_pwr_cycle:init()
    -- powercycle 单次任务，耗时4ms
    skynet.fork_once(function()
        local flag, type = self:meet_cycle_condition(true, 0)
        if flag then
            self:pp_execute_power_cycle(type, 0)
        end
    end)
end

function c_pwr_cycle:set_cycle_interrupt_flag(val)
    log:notice("[System:%s]cycle interrupt flag is set to %s!", self.system_id, val)
    self.cycle_interrupt_flag = val
end

-- Description :power cycle流程中的下电部分
local function pp_execute_pwr_cycle_off(type, fructrl, ctrlstate, fruid)
    -- 根据type分别执行powercyle, forcepowercycle, accycle
    if type == 1 then
        -- 状态机安全下电
        ctrlstate:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlPowerOff), fruid)
        -- 记录复位原因
        fructrl:add_RestartCauseRecords(m_enums.RestartCause.ChassisControlCommand)
    elseif type == 2 then
        -- 直接强制下电
        skynet.fork_once(function()
            fructrl:poweroff_force()
        end)
    end
end

-- Description :power cycle流程中的等待下电完成
function c_pwr_cycle:pp_execute_pwr_cycle_wait(fruid)
    -- BBU电池耗尽时间为7-8min中，定义为耗尽时间的3倍左右即20min
    local power_off_timeout = 1200
    self.pwr_off_interval = 0

    while power_off_timeout > 0 do
        if self.cycle_interrupt_flag then
            log:notice("[System:%s]Other power action, interrupt cycle!", self.system_id)
            return false
        end

        -- 循环检测系统电源信号，等待系统下电
        if self.fructrl:get_FruPowerState(fruid) == m_enums.PowerStatus.OFF then
            log:notice("[System:%s]already power off from power cycle!", self.system_id)
            break
        end

        skynet.sleep(100) -- 延时1000ms
        self.pwr_off_interval = self.pwr_off_interval + 1
        power_off_timeout = power_off_timeout - 1
    end
    -- 超时还未成功下电，记录日志
    if power_off_timeout == 0 then
        log:notice("[System:%s]power off timeout from power cycle!", self.system_id)
    end

    log:notice("[System:%s]Power off time is %d when do power cycle.", self.system_id, self.pwr_off_interval)
    return true
end

-- powercycle配置的延时时间
function c_pwr_cycle:pp_pwr_cycle_configured_delay()
    local interval = self.fructrl:get_PwrCycleDelaySeconds()
    if self.pwr_off_interval < interval then
        interval = interval - self.pwr_off_interval
    end
    local count = 0

    while true do
        if self.cycle_interrupt_flag then
            log:notice("[System:%s]Other power action, interrupt cycle!", self.system_id)
            return false
        end

        skynet.sleep(100)
        count = count + 1
        if count >= interval then
            break
        end
    end

    log:notice("[System:%s]Power cycle delay time is %d seconds.", self.system_id, interval)
    return true
end

function c_pwr_cycle:pp_pwr_cycle_wait_unlock()
    log:notice('[System:%s]wait PwrOnLocked unlock···', self.system_id)
    while true do
        if self.cycle_interrupt_flag then
            log:notice("[System:%s]Other power action, interrupt cycle!", self.system_id)
            return false
        end

        -- 上电锁内部有超时机制，最终必然会解锁
        if not self.fructrl:get_PwrOnLocked() then
            log:notice('[System:%s]Quit pp_pwr_cycle_wait_unlock already.', self.system_id)
            break
        end
        skynet.sleep(200)
    end
    return true
end

function c_pwr_cycle:get_PwrCycleType()
    return self.fructrl:get_PwrCycleType()
end

function c_pwr_cycle:set_PwrCycleType(val)
    return self.fructrl:set_PwrCycleType(val)
end


function c_pwr_cycle:pp_execute_power_cycle(type, fruid)
    -- 先取消cycle打断
    self:set_cycle_interrupt_flag(false)

    -- 1.下电
    log:notice("[System:%s]perform power cycle off part.", self.system_id)
    pp_execute_pwr_cycle_off(type, self.fructrl, self.ctrlstate, fruid)

    -- 2.等下电完成；固定等待时间；等上电锁解锁
    if not self:pp_execute_pwr_cycle_wait(fruid) or not self:pp_pwr_cycle_configured_delay() or
        not self:pp_pwr_cycle_wait_unlock() then
        self:set_PwrCycleType(0)
        return
    end

    -- 3.计时已完成，重新通过状态机上电
    log:notice("[System:%s]perform power cycle on part.", self.system_id)
    self.ctrlstate:send_powerctrl_event(m_enums.CtrlEventTypes.CtrlPowerOn, fruid)
    -- 记录复位原因
    self.fructrl:add_RestartCauseRecords(m_enums.RestartCause.ChassisControlCommand)

    -- 数据库状态刷新为无动作。（0~2，分别表示无动作、powercycle、forcepowercycle）
    self:set_PwrCycleType(0)
end

-- 初始化与指令的判断条件不同
function c_pwr_cycle:meet_cycle_condition(is_init, fruid)
    -- 查询数据库状态，判断是否已经存在cycle动作
    local type = self:get_PwrCycleType()

    if is_init then
        -- 初始化要求：type~=0才执行powercycle
        if type == 0 then
            log:notice("[System:%s]Not exist powercycle task when start bmc.", self.system_id)
            return false, type
        end
    else
        -- 指令要求：是上电状态，并且type==0，才执行powercycle
        if m_enums.PGSignalState[self.fructrl:get_FruPowerState(fruid)] ~= m_enums.PowerStatus.ON then
            log:notice("[System:%s]Powerstate is not ON, can not do power cycle!(type=%d)", self.system_id, type)
            return false, type
        end
        if type ~= 0 then
            log:notice("[System:%s]Already in power cycle, cannot do again. (type=%d)", self.system_id, type)
            return false, type
        end
    end

    return true, type
end

return c_pwr_cycle
