-- 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: 设置和获取上下电次数测试
local ipmi_set_test_count_cmd = require 'manufacture.ipmi.cmds.SetTestCount'
local ipmi_get_test_count_cmd = require 'manufacture.ipmi.cmds.GetTestCount'
local mc_ipmi = require 'ipmi'
local log = require 'mc.logging'
local factory = require 'factory'
local bs = require 'mc.bitstring'
local skynet = require 'skynet'

local cc = mc_ipmi.types.Cc

local STATUS_COMPLETE<const> = 0x00
local STATUS_TESTING<const> = 0x80

local ipmi_handler = {}
ipmi_handler.power_cycle_cnt = 0
ipmi_handler.cold_reset_cnt = 0
ipmi_handler.force_power_cycle_cnt = 0
ipmi_handler.force_power_cycle_status = STATUS_COMPLETE

local function get_power_status()
    local manufacture_service = factory.get_obj("manufacture_service")
    local power_status = manufacture_service:get_power_status()
    if not power_status then
        log:error("DFT fail to get power status")
        return
    end
    log:notice("DFT force power cycle testing, power status is %s", power_status)
    return power_status
end

local function power_on()
    local manufacture_service = factory.get_obj("manufacture_service")
    local ok = pcall(manufacture_service.power_on, manufacture_service)
    if not ok then
        log:error("DFT fail to force power cycle test start")
    else
        log:notice("DFT force power cycle testing, execute power on")
    end
end

local function power_off()
    local manufacture_service = factory.get_obj("manufacture_service")
    local ok = pcall(manufacture_service.force_power_off, manufacture_service)
    if not ok then
        log:error("DFT fail to force power cycle - force power off")
    else
        log:notice("DFT force power cycle testing, execute power off")
    end
end

function ipmi_handler.task_force_power_cycle(test_count, poweron_interval_minutes, poweroff_interval_minutes)
    local poweron_interval = poweron_interval_minutes * 60
    local poweroff_interval = poweroff_interval_minutes * 60
    local power_status = get_power_status()
    if power_status == "OFF" then
        power_on()
        skynet.sleep(poweron_interval * 100)
    end
    local i = 0
    while i < test_count do
        power_off()
        local retry_time = 0
        power_status = get_power_status()
        while power_status == "ON" do
            skynet.sleep(10 * 100) -- 每隔10s，重新去获取一次上下电状态
            power_status = get_power_status()
            retry_time = retry_time + 1
            if retry_time * 10 >= poweron_interval then
                ipmi_handler.force_power_cycle_status = STATUS_COMPLETE
                log:error("DFT force power cycle %d : force power off failed", i)
                return
            end
        end
        skynet.sleep((poweron_interval - retry_time * 10) * 100)
        power_on()
        retry_time = 0
        power_status = get_power_status()
        while power_status == "OFF" do
            skynet.sleep(10 * 100) -- 每隔10s，重新去获取一次上下电状态
            power_status = get_power_status()
            retry_time = retry_time + 1
            if retry_time * 10 >= poweroff_interval then
                ipmi_handler.force_power_cycle_status = STATUS_COMPLETE
                log:error("DFT force power cycle %d : force power off failed", i)
                return
            end
        end
        skynet.sleep((poweroff_interval - retry_time * 10) * 100)
        i = i + 1
        ipmi_handler.force_power_cycle_cnt = i
    end
    ipmi_handler.force_power_cycle_status = STATUS_COMPLETE
    log:notice("DFT force power cycle test successfully, count = %d", test_count)
end

local test_count_code = bs.new("<<TestCount>>")
function ipmi_handler.power_cycle(req, ctx)
    local tail = test_count_code:unpack(req.Tail)
    ipmi_handler.power_cycle_cnt = tail.TestCount
    mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT power cycle count to (%d) successfully", tail.TestCount)
    return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.Success)
end

function ipmi_handler.cold_reset(req, ctx)
    local tail = test_count_code:unpack(req.Tail)
    ipmi_handler.cold_reset_cnt = tail.TestCount
    mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT cold reset count to (%d) successfully", tail.TestCount)
    return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.Success)
end

local force_power_cycle_code = bs.new("<<TestCount, PowerOnInterval, PowerOffInterval>>")
function ipmi_handler.force_power_cycle(req, ctx)
    local tail = force_power_cycle_code:unpack(req.Tail)
    if ipmi_handler.force_power_cycle_status == STATUS_TESTING then
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT force power cycle count to (%d) failed", tail.TestCount)
        return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.Busy)
    end
    if tail.PowerOnInterval == 0 or tail.PowerOffInterval == 0 then
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT force power cycle count to (%d) failed", tail.TestCount)
    end
    ipmi_handler.power_cycle_cnt = tail.TestCount
    ipmi_handler.force_power_cycle_status = STATUS_TESTING
    ipmi_handler.force_power_cycle_cnt = 0
    skynet.fork(function()
        ipmi_handler.task_force_power_cycle(tail.TestCount, tail.PowerOnInterval, tail.PowerOffInterval)
    end)
    mc_ipmi.ipmi_operation_log(ctx,
        'manufacture', "Set DFT force power cycle count to (%d) successfully", tail.TestCount)
    return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.Success)
end

function ipmi_handler.ac_cycle(req, ctx)
    -- 非装备模式下不应答
    local manufacture_service = factory.get_obj("manufacture_service")
    local dftenbale = manufacture_service:get_dft_enable()
    if dftenbale == 0 then
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT ac cycle failed")
        log:debug("Set DFT ac cycle channel type is unsupport")
        return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.DataNotAvailable)
    end
    -- 非bt通道命令不应答
    if ctx.ChanType ~= 3 and ctx.Instance ~= 0 then
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT ac cycle failed")
        log:debug("Set DFT ac cycle channel type is unsupport")
        return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.DataNotAvailable)
    end
    mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT ac cycle successfully")
    manufacture_service = factory.get_obj("manufacture_service")
    manufacture_service:ac()
    return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.Success)
end

ipmi_handler.func = {
    [0] = ipmi_handler.power_cycle,
    [1] = ipmi_handler.cold_reset,
    [2] = ipmi_handler.force_power_cycle,
    [3] = ipmi_handler.ac_cycle
}

local req_tail_code = bs.new("<<TestType, Tail/string>>")
function ipmi_handler.set_test_count(req, ctx)
    log:info("set test count")
    if req.FruId ~= 0 then
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT test count failed")
        return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.DataNotAvailable)
    end
    local tail = req_tail_code:unpack(req.Tail)
    if not ipmi_handler.func[tail.TestType] then
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DFT test count failed")
        return ipmi_set_test_count_cmd.SetTestCountRsp.new(cc.DataNotAvailable)
    end
    return ipmi_handler.func[tail.TestType](tail, ctx)
end

function ipmi_handler.get_test_count(req, ctx)
    log:info("get test count")
    if req.FruId ~= 0 then
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Get DFT test count failed")
        return ipmi_get_test_count_cmd.GetTestCountRsp.new(cc.DataNotAvailable)
    end
    if req.TestType == 0 then
        return ipmi_get_test_count_cmd.GetTestCountRsp.new(cc.Success, ipmi_handler.power_cycle_cnt)
    elseif req.TestType == 1 then
        return ipmi_get_test_count_cmd.GetTestCountRsp.new(cc.Success, ipmi_handler.cold_reset_cnt)
    elseif req.TestType == 2 then
        return ipmi_get_test_count_cmd.GetTestCountRsp.new(cc.Success, ipmi_handler.force_power_cycle_cnt)
    else
        return ipmi_get_test_count_cmd.GetTestCountRsp.new(cc.DataNotAvailable)
    end
end

return ipmi_handler