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

local fructrl_proxy = class()

local function check_action_equal(a, b)
    if a.system_id == b.system_id then
        return true
    end
    return false
end

local function make_action(system_id, op_cause, ctx)
    return {
        system_id = system_id,
        op_cause = op_cause,
        ctx = ctx
    }
end

function fructrl_proxy:set_host_power_ctrl(system_id, power_ctrl_type)
    local test_data_dir = skynet.getenv('TEST_DATA_DIR')
    local host_power_state_file = test_data_dir .. 'power_states/' .. 'host_' .. system_id .. '_power_state'
    print("os.execute: ", 'echo ' .. power_ctrl_type .. ' > ' .. host_power_state_file)
    os.execute('echo ' .. power_ctrl_type .. ' > ' .. host_power_state_file)
end

function fructrl_proxy:ctor(object, multihost_obj, host_objs)
    self.chassis_fructrl_obj = object
    self.staggered_poweron_interval = object.StaggeredPowerOnInterval
    self.multihost_obj = multihost_obj
    self.host_objs = host_objs
    self.power_on_cq = cq.new(check_action_equal)
    self.restart_cq = cq.new(check_action_equal)
    self.cycle_cq = cq.new(check_action_equal)
end

function fructrl_proxy:all_host_power_on(ctx, RestartCause)
    for i = 1, self.multihost_obj:get_host_count() do
        self.power_on_cq:push(make_action(i, RestartCause, ctx))
    end
    self:create_power_on_task()
end

function fructrl_proxy:one_host_power_on(system_id, RestartCause, ctx)
    self.power_on_cq:push(make_action(system_id, RestartCause, ctx))
    self:create_power_on_task()
end

function fructrl_proxy:all_host_restart(ctx, RestartCause)
    for i = 1, self.multihost_obj:get_host_count() do
        self.restart_cq:push(make_action(i, RestartCause, ctx))
        log:mass("[fructrl proxy] host %s push restart to queue, cause: %s", i)
    end
    self:create_restart_task()
end

function fructrl_proxy:one_host_restart(system_id, ctx, RestartCause)
    self.restart_cq:push(make_action(system_id, RestartCause, ctx))
    self:create_restart_task()
end


function fructrl_proxy:one_host_cycle(system_id, ctx, RestartCause, operate_type)
    self.restart_cq:push(make_action(system_id, RestartCause, ctx, operate_type))
    self:create_cycle_task()
end

function fructrl_proxy:all_host_power_off(ctx, RestartCause, type)
    for i = 1, self.multihost_obj:get_host_count() do
        -- 先将当前host等待的power on 和 restart在队列中删除
        self.power_on_cq:delete({system_id = i})
        self.restart_cq:delete({system_id = i})
        log:mass("[fructrl proxy] host %s delete poweron and restart from queue, cause: %s", i)

        if self.host_objs[i] then
            self:set_host_power_ctrl(i, type)
            log:debug("[chassis power ctrl] host %s push power off, cause: %s", i, RestartCause)
        else
            log:error("[chassis power ctrl] host %s power off failed ", i)
        end
    end
end

function fructrl_proxy:one_host_power_off(system_id, ctx, power_off_type, RestartCause)
    self.power_on_cq:delete({system_id = system_id})
    self.restart_cq:delete({system_id = system_id})
    if not self.host_objs[system_id] then
        error(m_error_custom.ActionNotSupported('%OperateType'))
    end

    self:set_host_power_ctrl(system_id, power_off_type)
    log:debug("[fructrl proxy] host %s call graceful shutdown, cause: %s", system_id, RestartCause)
end

function fructrl_proxy:all_host_nmi(ctx, RestartCause)
    for i = 1, self.multihost_obj:get_host_count() do
        if self.host_objs[i] then
            self:set_host_power_ctrl(i, m_enums.PowerCtrlType.Nmi)
            log:debug("[fructrl proxy] host %s call nmi, cause: %s", i)
        end
    end
end

function fructrl_proxy:one_host_nmi(system_id, ctx, RestartCause)
    if not self.host_objs[system_id] then
        error(m_error_custom.ActionNotSupported('%OperateType'))
    end

    self:set_host_power_ctrl(system_id, m_enums.PowerCtrlType.Nmi)
end

function fructrl_proxy:all_host_set_poweron_lock(PwrOnLocked, Timeout, AppName, Reason)
    for i = 1, self.multihost_obj:get_host_count() do
        if self.host_objs[i] and self.host_objs[i].fructrl then
            self.host_objs[i].fructrl.power_on_lock:poweron_lock_entrance(PwrOnLocked, Timeout, AppName, Reason)
            log:info("[fructrl proxy] host %s set poweron lock, pwronlocked=%s, timeout=%d, appname=%s, reason=%s",
                i, PwrOnLocked, Timeout, AppName, Reason)
        end
    end
    return 0
end

function fructrl_proxy:all_host_set_poweron_strategy(stragety)
    for i = 1, self.multihost_obj:get_host_count() do
        if self.host_objs[i] and self.host_objs[i].fructrl then
            self.host_objs[i].fructrl:set_PowerOnStrategy(stragety)
            log:notice("[fructrl proxy] host %s set poweron strategy to %s", i, stragety)
        end
    end
    return 0
end

function fructrl_proxy:all_host_set_poweron_strategy_exceptions(Reason, Execute, EffectivePeriod, Priority)
    for i = 1, self.multihost_obj:get_host_count() do
        if self.host_objs[i] and self.host_objs[i].pwr_restore then
            self.host_objs[i].pwr_restore:update_pwr_restore(Reason, Execute, EffectivePeriod, tostring(Priority))
            log:notice("[fructrl proxy] host %s set poweron strategy exceptions to %s, period %s, priority %s",
                i, tostring(Execute), EffectivePeriod, Priority)
        end
    end
    return 0
end

function fructrl_proxy:create_power_on_task()
    if self.power_on_task then
        return
    end
    self.power_on_task = true

    skynet.fork_once(function()
        local power_action
        while true do
            power_action = self.power_on_cq:pop()
            if not power_action then
                self.power_on_task = false
                return
            end

            -- 对应host上电操作
            if self.host_objs[power_action.system_id] then
                self:set_host_power_ctrl(power_action.system_id, m_enums.PowerCtrlType.On)
                log:debug("[fructrl proxy] host %s call power on, cause: %s",
                    power_action.system_id, power_action.op_cause)
            else
                log:error("[fructrl proxy] host %s power on failed ", power_action.system_id)
            end
            skynet.sleep(self.staggered_poweron_interval * 100)
        end
    end)
end

function fructrl_proxy:create_restart_task()
    if self.restart_process_task then
        return
    end
    self.restart_process_task = true

    skynet.fork_once(function()
        local power_action
        while true do
            power_action = self.restart_cq:pop()
            if not power_action then
                self.restart_process_task = false
                return
            end
            -- 对应host restart操作
            if self.host_objs[power_action.system_id] then
                self:set_host_power_ctrl(power_action.system_id, m_enums.PowerCtrlType.ForceRestart)
                log:debug("[fructrl proxy] host %s call restart, cause: %s",
                        power_action.system_id, power_action.op_cause)
            else
                log:error("[fructrl proxy] host %s restart failed ", power_action.system_id)
            end

            skynet.sleep(self.staggered_poweron_interval * 100)
        end
    end)
end

function fructrl_proxy:create_cycle_task()
    if self.cycle_process_task then
        log:notice("[fructrl proxy] duplicate power cycle task")
        return
    end
    self.cycle_process_task = true

    skynet.fork_once(function()
        local power_action
        while true do
            power_action = self.cycle_cq:pop()
            if not power_action then
                self.cycle_process_task = false
                log:notice("[fructrl proxy] cycle queue is empty")
                return
            end
            -- 对应host cycle操作
            if self.host_objs[power_action.system_id] then
                self:set_host_power_ctrl(power_action.system_id, m_enums.PowerCtrlType.PowerCycle)
                log:debug("[fructrl proxy] host %s call cycle, cause: %s", power_action.system_id,
                    power_action.op_cause)
                skynet.sleep(self.staggered_poweron_interval * 100)
            else
                log:error("[fructrl proxy] host %s %s failed ", power_action.system_id,
                    m_enums.PowerCtrlType.PowerCycle)
            end
        end
    end)
end

function fructrl_proxy:wait_all_host_power_on_finished()
    skynet.sleep(10)
end

return fructrl_proxy