-- 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 ipmi = require 'ipmi'
local bs = require 'mc.bitstring'
local log = require 'mc.logging'
local enums = require 'ipmi.enums'
local prbs_test_def = require 'prbs_test.prbs_test_def'
local skynet = require 'skynet'

local comp_code = ipmi.types.Cc
local test_object = {}
test_object.__index = test_object
local channel_type = enums.ChannelType
local GET_ARM_NPU_CMD<const> = 0x17
local DMP_LAST_REQ<const> = 0x80

local prbs_test_cmd_request = {
    ['common'] = {ipmi_request_info = '<<0xDB0700:3/unit:8, cmd:1/unit:8, lun:1/unit:8, ' ..
        'request_parameter:1/unit:8, op_cmd:1/unit:8, op_fun:1/unit:8, offset:4/unit:8, ' ..
        'data_length:4/unit:8, operation_type:1/unit:8, optical_module_num:1/unit:8, optical_module_id:1/unit:8>>'},
    ['config'] = {ipmi_request_info = '<<0xDB0700:3/unit:8, cmd:1/unit:8, lun:1/unit:8,' ..
        'request_parameter:1/unit:8, op_cmd:1/unit:8, op_fun:1/unit:8, offset:4/unit:8,' ..
        'data_length:4/unit:8, operation_type:1/unit:8, config_item:1/unit:8, test_time:4/unit:8,' ..
        'prbs_type:1/unit:8, optical_module_num:1/unit:8, optical_module_id:1/unit:8>>'},
}

local function assemble_ipmi_request(operation_pram, logical_id)
    local valid_op_id = 0
    if logical_id and logical_id ~= 255 then
        valid_op_id = logical_id
    end
    local data = nil
    if operation_pram.prbs_cmd == prbs_test_def.cmd.GET_STATE or
        operation_pram.prbs_cmd == prbs_test_def.cmd.CLEAR_STATISTICS or
        operation_pram.prbs_cmd == prbs_test_def.cmd.GET_RESULT or
        operation_pram.prbs_cmd == prbs_test_def.cmd.CLOSE then
        log:info('assemble_ipmi_request common, prbs_cmd %d', operation_pram.prbs_cmd)
        data = bs.new(prbs_test_cmd_request['common'].ipmi_request_info):pack({
            cmd = GET_ARM_NPU_CMD, lun = DMP_LAST_REQ, request_parameter = 2,
            op_cmd = 0xa4, op_fun = 0x06, offset = 0, data_length = 3,
            operation_type = operation_pram.prbs_cmd, optical_module_num = 1, optical_module_id = valid_op_id})
    elseif operation_pram.prbs_cmd == prbs_test_def.cmd.CONFIG then
        log:info('assemble_ipmi_request config, prbs_cmd %d', operation_pram.prbs_cmd)
        data = bs.new(prbs_test_cmd_request['config'].ipmi_request_info):pack({
            cmd = GET_ARM_NPU_CMD, lun = DMP_LAST_REQ, request_parameter = 2,
            op_cmd = 0xa4, op_fun = 0x06, offset = 0, data_length = 9,
            operation_type = operation_pram.prbs_cmd, config_item = operation_pram.config_item,
            test_time = operation_pram.test_time, prbs_type = operation_pram.prbs_type,
            optical_module_num = 1, optical_module_id = valid_op_id})
    else
        log:error('assemble_ipmi_request: prbs_cmd %d invalid', operation_pram.prbs_cmd)
    end
    return data
end

local prbs_test_cmd_respond = {
    ['common'] = {ipmi_respond_info = '<<error_code:1/unit:8, opcode:2/little-unit:8, total_length:4/little-unit:8,' ..
        'length:4/little-unit:8, optical_module_num:1/little-unit:8, exe_result:1/little-unit:8, tail/string>>'},
    ['get_state'] = {ipmi_respond_info = '<<error_code:1/unit:8, opcode:2/little-unit:8,' ..
        'total_length:4/little-unit:8, length:4/little-unit:8, optical_module_num:1/little-unit:8,' ..
        'supported_test_type:1/little-unit:8, test_state:1/little-unit:8, tail/string>>'},
    ['get_result'] = {ipmi_respond_info = '<<error_code:1/unit:8, opcode:2/little-unit:8,' ..
        'total_length:4/little-unit:8, length:4/little-unit:8, optical_module_num:1/little-unit:8,' ..
        'exe_result:1/little-unit:8, error_bit_rate1:32/little-float, error_bit_rate2:32/little-float,' ..
        'error_bit_rate3:32/little-float, error_bit_rate4:32/little-float, error_bit_rate5:32/little-float,' ..
        'error_bit_rate6:32/little-float, error_bit_rate7:32/little-float, error_bit_rate8:32/little-float,' ..
        'rx_lost_of_lock:1/little-unit:8, tx_lost_of_lock:1/little-unit:8, tail/string>>'},
}

local function assemble_ipmi_respond(operation_pram)
    local data = nil
    if operation_pram.prbs_cmd == prbs_test_def.cmd.CONFIG or
        operation_pram.prbs_cmd == prbs_test_def.cmd.CLEAR_STATISTICS or
        operation_pram.prbs_cmd == prbs_test_def.cmd.CLOSE then
        data = bs.new(prbs_test_cmd_respond['common'].ipmi_respond_info)
    elseif operation_pram.prbs_cmd == prbs_test_def.cmd.GET_STATE then
        data = bs.new(prbs_test_cmd_respond['get_state'].ipmi_respond_info)
    elseif operation_pram.prbs_cmd == prbs_test_def.cmd.GET_RESULT then
        data = bs.new(prbs_test_cmd_respond['get_result'].ipmi_respond_info)
    else
        log:error('assemble_ipmi_respond: prbs_cmd %d invalid', operation_pram.prbs_cmd)
    end
    return data
end

function test_object.new(object_id, presence_state, npu_id, bus, logical_id)
    return setmetatable({
        update_self_running = false,
        clear_task_running = false,
        clear_result = nil,
        state = prbs_test_def.prbs_state.PENDING,
        test_object_id = object_id,
        npu_id = npu_id,
        logical_id = logical_id,
        presence = presence_state,
        -- nil:未查询是否支持prbs test 
        -- false:不支持prbs test 
        -- true:支持prbs test 
        support_prbs_test = nil,
        bit_error_rate = {0, 0, 0, 0, 0, 0, 0, 0},
        loss_of_lock = {0, 0, 0, 0, 0, 0, 0, 0},
        bus = bus
    }, test_object)
end

function test_object:send_prbs_ipmi_to_hdk(operation_pram)
    if operation_pram == nil or operation_pram.prbs_cmd == nil then
        log:debug("input pram is invalid")
        return nil
    end
    local prbs_req = assemble_ipmi_request(operation_pram, self.logical_id)
    if prbs_req == nil then
        log:debug("assemble ipmi request fail, prbs cmd %d", operation_pram.prbs_cmd)
        return nil
    end
    local ok, result, payload = pcall(ipmi.request, self.bus, {channel_type.CT_ME:value(), self.npu_id},
        {DestNetFn = 0x30, Cmd = 0x98, Payload = prbs_req})
    if not ok then
        log:debug('Send request by ipmi fail, err_message:%s', result)
        return nil
    end
    if result ~= comp_code.Success then
        log:debug('prbs ipmi request failed, npuid = %s, comp_code = 0x%x, prbs_cmd = %s',
            self.npu_id, result, operation_pram.prbs_cmd)
        return nil
    end
    local prbs_respond_bs = assemble_ipmi_respond(operation_pram)
    if prbs_respond_bs == nil then
        log:debug("assemble ipmi respond fail, prbs cmd %d", operation_pram.prbs_cmd)
        return nil
    end
    local rsp = prbs_respond_bs:unpack(payload)
    if not rsp then
        log:debug('unpack respond failed, prbs cmd %d', operation_pram.prbs_cmd)
        return nil
    end
    log:debug(rsp)
    return rsp
end

function test_object:update(result)
    if self.state == prbs_test_def.prbs_state.PENDING then
        self.bit_error_rate = {0, 0, 0, 0, 0, 0, 0, 0}
        self.loss_of_lock = {0, 0, 0, 0, 0, 0, 0, 0}
    elseif self.state == prbs_test_def.prbs_state.RUNNING and result then
        self.bit_error_rate = {result.error_bit_rate1, result.error_bit_rate2, result.error_bit_rate3,
            result.error_bit_rate4, result.error_bit_rate5, result.error_bit_rate6, result.error_bit_rate7,
            result.error_bit_rate8}
        local lost_of_lock = result.rx_lost_of_lock | result.tx_lost_of_lock
        self.loss_of_lock = {lost_of_lock & 1, (lost_of_lock >> 1) & 1,
            (lost_of_lock >> 2) & 1, (lost_of_lock >> 3) & 1,
            (lost_of_lock >> 4) & 1, (lost_of_lock >> 5) & 1,
            (lost_of_lock >> 6) & 1, (lost_of_lock >> 7) & 1}
    end
end

function test_object:get_prbs_test_supported()
    local operation_pram = {
        prbs_cmd = prbs_test_def.cmd.GET_STATE
    }
    local rsp = self:send_prbs_ipmi_to_hdk(operation_pram)
    if rsp == nil then
        self.support_prbs_test = nil
        return nil
    end
    if rsp.supported_test_type == prbs_test_def.support_type.CMIS_DEFINE then
        self.support_prbs_test = true
        return true
    end
    self.support_prbs_test = false
    return false
end

function test_object:get_prbs_test_state()
    local operation_pram = {
        prbs_cmd = prbs_test_def.cmd.GET_STATE
    }
    local rsp = self:send_prbs_ipmi_to_hdk(operation_pram)
    if rsp == nil then
        -- 获取失败，状态为未知
        return prbs_test_def.prbs_state.UNKNOWN
    end
    if rsp.test_state == prbs_test_def.get_state_result.PENDING then
        return prbs_test_def.prbs_state.PENDING
    end
    return prbs_test_def.prbs_state.RUNNING
end

function test_object:close_prbs_test()
    local operation_pram = {
        prbs_cmd = prbs_test_def.cmd.CLOSE
    }
    local rsp = self:send_prbs_ipmi_to_hdk(operation_pram)
    if rsp == nil or rsp.exe_result ~= prbs_test_def.common_result.SUCCESS then
        return prbs_test_def.common_result.FAILED
    end
    return prbs_test_def.common_result.SUCCESS
end

function test_object:update_optical_module_info()
    -- 更新状态
    self.state = self:get_prbs_test_state()
    -- 更新测试统计
    local operation_pram = {
        prbs_cmd = prbs_test_def.cmd.GET_RESULT
    }
    local rsp = self:send_prbs_ipmi_to_hdk(operation_pram)
    if rsp == nil then
        return
    end
    if rsp.exe_result ~= prbs_test_def.common_result.SUCCESS then
        log:error("test object config exe_result is %s", rsp.exe_result)
        return
    end
    self:update(rsp)
end

local function clear_task(test_obj)
    test_obj.clear_task_running = true
    test_obj.clear_result = prbs_test_def.common_result.FAILED
    -- 清除HDK数据
    local operation_pram = {
        prbs_cmd = prbs_test_def.cmd.CLEAR_STATISTICS
    }
    local rsp = test_obj:send_prbs_ipmi_to_hdk(operation_pram)
    if rsp == nil or rsp.exe_result ~= prbs_test_def.common_result.SUCCESS then
        test_obj.clear_result = prbs_test_def.common_result.FAILED
        test_obj.clear_task_running = false
        return
    end

    -- 清除本地缓存
    test_obj.state = prbs_test_def.prbs_state.PENDING
    test_obj:update(nil)
    test_obj.clear_result = prbs_test_def.common_result.SUCCESS
    test_obj.clear_task_running = false
end

function test_object:creat_clear_task()
    if self.clear_task_running then
        return
    end
    log:info("test object %s clear_task create.", self.test_object_id)
    skynet.fork_once(clear_task, self)
end

function test_object:update_running(state)
    if self.update_self_running == state then
        return
    end
    log:info("test object %s test_self_running update.", self.test_object_id)
    self.update_self_running = state
end

return test_object