-- 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 class = require 'mc.class'
local log = require 'mc.logging'
local utils = require 'mc.utils'
local mdb = require 'mc.mdb'
local chassis_def = require 'def.chassis_def'
local skynet = require 'skynet'
local base_messages = require 'messages.base'
local manufacture_app = class()

local STATUS_COMPLETE <const> = "Complete"
local STATUS_TESTING <const> = "Testing"
local STATUS_UNSTART <const> = "Unstart"

local RESULT_SUCCEED <const> = "Succeed"
local RESULT_FAILED <const> = "Failed"
local RESULT_NON <const> = "Non"

local CHASSIS_COVER = chassis_def.COVER_CODE


function manufacture_app.new(app)
    return setmetatable({ app = app }, manufacture_app)
end

function manufacture_app:init()
    self:register_rpc_methods()
    local chassis_service = self.app.chassis_service
    self.chassis_collection = chassis_service.chassis_collection

    -- I2cLed灯测试
    self.led_service = self.app.led_service
    self.led_i2c_service = self.app.led_i2c_service
    self.physical_led_test_flag = false
    self.physical_led_on_test_flag = false
    self.i2cled_test_task = false
    self.i2cled_on_test_task = false

    --按钮测试
    self.button_test_flag = {}
    self.button_test_result = {}

    -- 数码管测试
    self.led_tube_service = self.app.led_tube_service
    -- 启停测试和防呆测试共用一套标志位
    self.led_tube_test_flag = false
    self.led_tube_test_task = false

    self.led_tube_intelligence_test_flag = false
end

function manufacture_app:get_mds_obj(obj)
    for _, v in pairs(self.app.dft_obj_list) do
        if v.Type == obj.Type and v.Id == obj.Id and
            v.Slot == obj.Slot and v.DeviceNum == obj.DeviceNum then
            return v
        end
    end
    return nil
end

local intrusion_ac_off = CHASSIS_COVER.COVER_UNKOWN_VALUE
function manufacture_app:dft_chassis_cover_satus_start(obj)
    obj.Status = STATUS_UNSTART
    -- V2中获取cover_status实质上是获取ac_off寄存器的状态, 实质上只检测了AC下电开箱检测
    if self.chassis_collection and #self.chassis_collection > 0 then
        intrusion_ac_off = self.chassis_collection[1]:get_intrusion_ac_off()
    end
    if intrusion_ac_off ~= nil then
        obj.Status = STATUS_COMPLETE
    end
end

function manufacture_app:dft_chassis_cover_satus_stop(obj)
    intrusion_ac_off = CHASSIS_COVER.COVER_UNKOWN_VALUE
    obj.Status = STATUS_COMPLETE
end

function manufacture_app:dft_chassis_cover_satus_get_result(obj)
    obj.Status = STATUS_COMPLETE
    local intrusion_flag = self.chassis_collection[1]:get_cover_status()
    -- V3机型都支持下电开箱检测，只需根据是否有过开箱动作判断
    if self.chassis_collection and #self.chassis_collection > 0 then
        if intrusion_flag == CHASSIS_COVER.OPENED then
            return RESULT_SUCCEED, CHASSIS_COVER.OPEN_STR
        elseif intrusion_flag == CHASSIS_COVER.CLOSED then
            return RESULT_SUCCEED, CHASSIS_COVER.CLOSE_STR
        else
            obj.Status = STATUS_UNSTART
            return RESULT_SUCCEED, "Chassis cover status error"
        end
    else
        log:error('[chassis] dft: Get chassis fail')
    end
    return RESULT_FAILED, intrusion_flag == CHASSIS_COVER.OPENED and "Chassis cover opened" or "Chassis cover closed"
end

-- I2cLed指示灯启停测试
local LED_OFF <const> = 0
local LED_ON <const> = 0xff

function manufacture_app:register_physical_led_test()
    self.app:ImplDftPysicalLedManufactureStart(function(obj, ...)
        obj.Status = STATUS_TESTING
        self.physical_led_test_flag = true
        -- 装备模式寄存器置位
        self:get_mds_obj(obj).DftEnable = 1
        self:task_i2c_led_dft_test()
    end)

    self.app:ImplDftPysicalLedManufactureStop(function(obj, ...)
        obj.Status = STATUS_UNSTART
        self.physical_led_test_flag = false
        self:get_mds_obj(obj).DftEnable = 0
    end)

    self.app:ImplDftPysicalLedManufactureGetResult(function(obj, ...)
        return self:get_led_test_result(obj)
    end)
end

function manufacture_app:task_i2c_led_dft_test()
    if self.i2cled_test_task then
        return
    end
    for _, obj in pairs(self.led_i2c_service.led_i2c_list) do
        skynet.fork_loop({count = 0}, function()
            while self.physical_led_test_flag do
                self.led_i2c_service.is_dft_testing = true
                self.i2cled_test_task = true

                obj:set_state(LED_ON)
                skynet.sleep(50) -- 点亮 50*10ms
                
                obj:set_state(LED_OFF)
                skynet.sleep(50) -- 熄灭 50*10ms
            end
            self.led_i2c_service.is_dft_testing = false
            self.i2cled_test_task = false
        end)
    end
end

function manufacture_app:get_led_test_result(obj)
    if not self.physical_led_test_flag and not self.physical_led_on_test_flag then
        obj.Status = STATUS_UNSTART
        return RESULT_NON, ""
    end
    obj.Status = STATUS_COMPLETE
    return RESULT_NON, ""
end

-- I2cLed指示灯防呆测试
local PARAM_LED_ON <const> = 0    -- 0:常亮
local PARAM_LED_BLINK <const> = 1 -- 1:闪烁

local function get_led_intelligence_test_mode(obj)
    local mode = PARAM_LED_BLINK
    if obj.Param and #obj.Param >= 1 then
        mode = string.unpack('B', obj.Param)
    end
    return mode
end

function manufacture_app:turn_off_i2c_led()
    for _, obj in pairs(self.led_i2c_service.led_i2c_list) do
        obj:set_state(LED_OFF)
    end
end

function manufacture_app:register_led_intelligence_test()
    self.app:ImplDftLedIntelligenceManufactureStart(function(obj, ...)
        local mode = get_led_intelligence_test_mode(obj)
        if mode ~= PARAM_LED_ON and mode ~= PARAM_LED_BLINK then
            log:error('[chassis] DftLedIntelligence Start, The param is invalid.')
            return
        end
        obj.Status = STATUS_TESTING
        if mode == PARAM_LED_BLINK then
            self.physical_led_on_test_flag = false
            self.physical_led_test_flag = true

            self:get_mds_obj(obj).DftEnable = 1
            self:task_i2c_led_dft_test()
        else
            self.physical_led_test_flag = false
            self.physical_led_on_test_flag = true

            -- 点亮UID灯
            self.led_service:set_led(chassis_def.UIDLEDSTATE.LED_ON)

            self:task_i2c_led_on_dft_test()
        end
    end)

    self.app:ImplDftLedIntelligenceManufactureStop(function(obj, ...)
        local mode = get_led_intelligence_test_mode(obj)
        if mode ~= PARAM_LED_ON and mode ~= PARAM_LED_BLINK then
            log:error('[chassis] DftLedIntelligence Start, The param is invalid.')
            return
        end

        obj.Status = STATUS_UNSTART
        self.physical_led_test_flag = false
        self.physical_led_on_test_flag = false
        -- 将点的灯恢复原状
        self:turn_off_i2c_led()
        if mode == PARAM_LED_BLINK then
            self:get_mds_obj(obj).DftEnable = 0
        else
            self.led_service:set_led(chassis_def.UIDLEDSTATE.LED_OFF)
        end
    end)

    self.app:ImplDftLedIntelligenceManufactureGetResult(function(obj, ...)
        return self:get_led_test_result(obj)
    end)
end

function manufacture_app:task_i2c_led_on_dft_test()
    if self.i2cled_on_test_task then
        return
    end
    skynet.fork(function()
        while self.physical_led_on_test_flag do
            self.i2cled_on_test_task = true
            for _, obj in pairs(self.led_i2c_service.led_i2c_list) do
                obj:set_state(LED_ON)
            end
            skynet.sleep(50) -- 50*10ms
        end
        self.i2cled_on_test_task = false
    end)
end

-- Led数码管启停测试
function manufacture_app:register_led_tube_test()
    self.app:ImplDftLedTubeManufactureStart(function(obj, ...)
        obj.Status = STATUS_TESTING
        self.led_tube_test_flag = true
        self.led_tube_service.test_flag = true
        self:task_led_tube_dft_test()
    end)
    self.app:ImplDftLedTubeManufactureStop(function(obj, ...)
        obj.Status = STATUS_UNSTART
        self.led_tube_test_flag = false
        self.led_tube_service.test_flag = false
    end)
    self.app:ImplDftLedTubeManufactureGetResult(function(obj, ...)
        return self:get_led_tube_test_result(obj)
    end)
end

local LIGHT_ON_DATA <const> = '888'
local LIGHT_OFF_DATA <const> = '   '

function manufacture_app:task_led_tube_dft_test(param)
    if self.led_tube_test_task then
        return
    end
    skynet.fork(function()
        while self.led_tube_test_flag do
            self.led_tube_test_task = true
            -- 默认全亮1s，否则按参数显示
            self.led_tube_service.led_tube_object:set_all_tube_value(param or LIGHT_ON_DATA)
            skynet.sleep(100)

            -- 全灭1s
            self.led_tube_service.led_tube_object:set_all_tube_value(LIGHT_OFF_DATA)
            skynet.sleep(100)
        end
        self.led_tube_test_task = false
    end)
end

function manufacture_app:get_led_tube_test_result(obj)
    obj.Status = STATUS_COMPLETE
    return RESULT_NON, ""
end

-- Led数码管防呆测试
function manufacture_app:register_led_tube_intelligence_test()
    self.app:ImplDftLedTubeIntelligenceManufactureStart(function(obj, ...)
        -- 存在测试任务，直接退出
        if self.led_tube_test_task then
            return
        end

        if not obj.Param or #obj.Param == 0 then
            obj.Status = STATUS_TESTING
            self.led_tube_test_flag = true
            self.led_tube_service.test_flag = true
            self:task_led_tube_dft_test()
        else
            local param = ''
            -- 参数校验
            for i = 1, #obj.Param do
                local val = string.unpack('B', obj.Param, i)
                if val > 9 then
                    log:error('[chassis] DftLedTubeIntelligence Start, The param %s is invalid', val)
                    error(base_messages.InternalError())
                end
                param = param .. string.char(val + 0x30)
            end
            obj.Status = STATUS_TESTING
            self.led_tube_test_flag = true
            self.led_tube_service.test_flag = true
            -- 参数长度为 1 时，格式化为三个相同的数字显示
            self:task_led_tube_dft_test(string.sub(string.format("%s%s%s", param, param, param), 1, 3))
        end
    end)
    self.app:ImplDftLedTubeIntelligenceManufactureStop(function(obj, ...)
        obj.Status = STATUS_UNSTART
        self.led_tube_test_flag = false
        self.led_tube_service.test_flag = false
    end)
    self.app:ImplDftLedTubeIntelligenceManufactureGetResult(function(obj, ...)
        return self:get_led_tube_test_result(obj)
    end)
end

function manufacture_app:register_rpc_methods()
    self:register_chassis_cover_satus_test()
    self:register_physical_led_test()
    self:register_led_intelligence_test()
    self:register_uid_button_test()
    self:register_led_tube_test()
    self:register_led_tube_intelligence_test()
    self:register_button_test()
end

function manufacture_app:register_chassis_cover_satus_test()
    self.app:ImplDftChassisCoverManufactureStart(function(obj, ...)
        return self:dft_chassis_cover_satus_start(obj)
    end)
    self.app:ImplDftChassisCoverManufactureStop(function(obj, ...)
        return self:dft_chassis_cover_satus_stop(obj)
    end)
    self.app:ImplDftChassisCoverManufactureGetResult(function(obj, ...)
        return self:dft_chassis_cover_satus_get_result(obj)
    end)
end

function manufacture_app:register_uid_button_test()
    self.app:ImplDftUidButtonManufactureStart(function(obj, ...)
        return self:dft_uid_button_start(obj)
    end)
    self.app:ImplDftUidButtonManufactureStop(function(obj, ...)
        return self:dft_uid_button_stop(obj)
    end)
    self.app:ImplDftUidButtonManufactureGetResult(function(obj, ...)
        return self:dft_uid_button_get_result(obj)
    end)
end

function manufacture_app:dft_uid_button_start(obj)
    local chassis = self.app.chassis_service:get_chassis_obj()
    if not chassis then
        log:error("dft uid button start fail, chassis obj is nil")
        return
    end
    obj.Status = STATUS_TESTING
    chassis:set_prop('UidButtonTestFlag', true)
    chassis:set_prop('UidButtonTestResult', 255)
end

function manufacture_app:dft_uid_button_stop(obj)
    local chassis = self.app.chassis_service:get_chassis_obj()
    if not chassis then
        log:error("dft uid button stop fail, chassis obj is nil")
        return
    end
    obj.Status = STATUS_UNSTART
    chassis:set_prop('UidButtonTestFlag', false)
end

function manufacture_app:dft_uid_button_get_result(obj)
    obj.Status = STATUS_COMPLETE
    local chassis = self.app.chassis_service:get_chassis_obj()
    if not chassis or not chassis:get_prop('UidButtonTestFlag') then
        log:error("get uidbutton obj failed or uidbutton test stop")
        return RESULT_NON, ""
    end
    if chassis:get_prop('UidButtonTestResult') == 0 then
        log:info("uidbutton test success")
        return RESULT_SUCCEED, ""
    end
    log:error("get uidbutton obj failed")
    return RESULT_FAILED, ""
end

--通用按钮测试
function manufacture_app:register_button_test()
    self.app:ImplDftButtonTestManufactureStart(function(obj, ...)
        local private_obj = self:get_mds_obj(obj)
        if not private_obj then
            log:error("dft button start fail, private obj is nil")
            return
        end
        return self:dft_button_start(obj, private_obj)
    end)
    self.app:ImplDftButtonTestManufactureStop(function(obj, ...)
        return self:dft_button_stop(obj)
    end)
    self.app:ImplDftButtonTestManufactureGetResult(function(obj, ...)
        return self:dft_button_get_result(obj)
    end)
end
 
--检测按钮是否已被按下
local function get_press_event(private_obj)
    local ok = false
    local result
    local count = 0
    while count < 3 do
        ok, result = pcall(function() return private_obj.PressEvent == private_obj.ExpectedValue end)
        if ok then
            return result
        end
        skynet.sleep(10)
        count = count + 1
    end
    log:error("get press event failed")
    return false
end
 
local function clear_press_event(private_obj)
    local ok = false
    local count = 0
    while count < 3 do
        ok = pcall(function() private_obj.ClearPressEvent = 0 end)
        if ok then
            return true
        end
        skynet.sleep(10)
        count = count + 1
    end
    return false
end
 
function manufacture_app:dft_button_start(obj, private_obj)
    if obj.Status == STATUS_TESTING then
        log:notice("button test is already running")
        return
    end
    obj.Status = STATUS_TESTING
    local index = string.format("%d_%d_%d", obj.Id, obj.Slot, obj.DeviceNum)
    self.button_test_result[index] = RESULT_NON
    local is_cleared = clear_press_event(private_obj)
    if not is_cleared then
        log:error("clear press event failed before test")
        return
    end
    skynet.fork(function()
        self.button_test_flag[index] = true
        local is_pressed
        while self.button_test_flag[index] do
            is_pressed = get_press_event(private_obj)
            if not is_pressed then
                goto continue
            end
            self.button_test_result[index] = RESULT_SUCCEED
            is_cleared = clear_press_event(private_obj)
            if not is_cleared then
                log:error("clear press event failed after test")
            end
            self.button_test_flag[index] = false
            ::continue::
            skynet.sleep(100)
        end
    end)
end
 
function manufacture_app:dft_button_stop(obj)
    obj.Status = STATUS_UNSTART
    local index = string.format("%d_%d_%d", obj.Id, obj.Slot, obj.DeviceNum)
    self.button_test_flag[index] = false
    self.button_test_result[index] = nil
end
 
function manufacture_app:dft_button_get_result(obj)
    obj.Status = STATUS_COMPLETE
    local index = string.format("%d_%d_%d", obj.Id, obj.Slot, obj.DeviceNum)
    if self.button_test_result[index] == nil then
        log:error("not start button test or button test stop")
        return RESULT_NON, ""
    end
    if self.button_test_result[index] == RESULT_SUCCEED then
        return RESULT_SUCCEED, ""
    end
    return RESULT_FAILED, ""
end

return manufacture_app
