-- Copyright (c) 2025 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 dirty_test_def = require 'dirty_test.dirty_test_def'
local class = require 'mc.class'
local comp_code = ipmi.types.Cc

local test_object = class()
function test_object:ctor(object_id, presence_state, npu_id, bus, logical_id, op_type)
    self.state = dirty_test_def.dirty_state.PENDING
    self.test_object_id = object_id
    self.duration_second = 0
    self.npu_id = npu_id
    self.logical_id = logical_id
    self.presence = presence_state
    self.support_dirty_test = nil -- nil:未查询、false:不支持、true:支持
    self.is_dirty = 'Unknown'
    self.dirty_distance = 0
    self.bus = bus
    self.type = op_type
end

local channel_type = enums.ChannelType
local GET_ARM_NPU_CMD<const> = 0x17
local DMP_LAST_REQ<const> = 0x80

local dirty_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, reserve:1/unit:8>>'},
    ['init'] = {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, reserve1:1/unit:8, ' .. 
        'duration_second:2/unit:8, reserve2:4/unit:8>>'}
}

function test_object:assemble_ipmi_request(operation_pram)
    local valid_op_id = 0
    if self.logical_id and self.logical_id ~= 255 then
        valid_op_id = self.logical_id
    end
    local data = nil
    if operation_pram.dirty_cmd == dirty_test_def.cmd.GET_CAPABILITY or
        operation_pram.dirty_cmd == dirty_test_def.cmd.GET_RESULT or
        operation_pram.dirty_cmd == dirty_test_def.cmd.START or
        operation_pram.dirty_cmd == dirty_test_def.cmd.CLOSE then
        log:info('assemble_ipmi_request common, dirty_cmd %d', operation_pram.dirty_cmd)
        data = bs.new(dirty_test_cmd_request['common'].ipmi_request_info):pack({
            cmd = GET_ARM_NPU_CMD, lun = DMP_LAST_REQ, request_parameter = 2,
            op_cmd = 0xa9, op_fun = 0x06, offset = 0, data_length = 4,
            operation_type = operation_pram.dirty_cmd, optical_module_num = 1, optical_module_id = valid_op_id, reserve = 0})
    elseif operation_pram.dirty_cmd == dirty_test_def.cmd.INIT then
        log:info('assemble_ipmi_request init, dirty_cmd %d', operation_pram.dirty_cmd)
        data = bs.new(dirty_test_cmd_request['init'].ipmi_request_info):pack({
            cmd = GET_ARM_NPU_CMD, lun = DMP_LAST_REQ, request_parameter = 2,
            op_cmd = 0xa9, op_fun = 0x06, offset = 0, data_length = 10,
            operation_type = operation_pram.dirty_cmd, optical_module_num = 1, optical_module_id = valid_op_id, reserve1 = 0,
            duration_second = operation_pram.test_time, reserve2 = 0})
    else
        log:error('assemble_ipmi_request: dirty_cmd %d invalid', operation_pram.dirty_cmd)
    end
    return data
end

local dirty_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_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, dirty_status:1/little-unit:8, dirty_result:1/little-unit:8, ' ..
        'bit_map_max:1/little-unit:8, reserve1:1/little-unit:8, ' ..
        'bit_map_second:1/little-unit:8, reserve2:1/little-unit:8, ' ..
        'bit_map_tx_warn:1/little-unit:8, reserve3:1/little-unit:8, ' ..
        'bit_map_rx_warn:1/little-unit:8, reserve4:1/little-unit:8, ' ..
        'max_reflection_pos1:32/little-float, max_reflection_pos2:32/little-float, max_reflection_pos3:32/little-float, max_reflection_pos4:32/little-float, ' ..
        'max_reflection_pos5:32/little-float, max_reflection_pos6:32/little-float, max_reflection_pos7:32/little-float, max_reflection_pos8:32/little-float, ' ..
        'second_reflection_pos1:32/little-float, second_reflection_pos2:32/little-float, second_reflection_pos3:32/little-float, second_reflection_pos4:32/little-float, ' ..
        'second_reflection_pos5:32/little-float, second_reflection_pos6:32/little-float, second_reflection_pos7:32/little-float, second_reflection_pos8:32/little-float, tail/string>>'}
}

local function assemble_ipmi_respond(operation_pram)
    local data = nil
    if operation_pram.dirty_cmd == dirty_test_def.cmd.GET_CAPABILITY or
        operation_pram.dirty_cmd == dirty_test_def.cmd.INIT or
        operation_pram.dirty_cmd == dirty_test_def.cmd.START or
        operation_pram.dirty_cmd == dirty_test_def.cmd.CLOSE then
        data = bs.new(dirty_test_cmd_respond['common'].ipmi_respond_info)
    elseif operation_pram.dirty_cmd == dirty_test_def.cmd.GET_RESULT then
        data = bs.new(dirty_test_cmd_respond['get_result'].ipmi_respond_info)
    else
        log:error('assemble_ipmi_respond: dirty_cmd %d invalid', operation_pram.dirty_cmd)
    end
    return data
end

function test_object:send_dirty_ipmi_to_hdk(operation_pram)
    if operation_pram == nil or operation_pram.dirty_cmd == nil then
        log:debug("dirty test ipmi input pram is invalid")
        return nil
    end
    local dirty_req = self:assemble_ipmi_request(operation_pram)
    if dirty_req == nil then
        log:debug("assemble ipmi request fail, dirty cmd %d", operation_pram.dirty_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 = dirty_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('dirty ipmi request failed, npuid = %s, comp_code = 0x%x, dirty_cmd = %s', self.npu_id, result, operation_pram.dirty_cmd)
        return nil
    end
    local dirty_respond_bs = assemble_ipmi_respond(operation_pram)
    if dirty_respond_bs == nil then
        log:debug("assemble ipmi respond fail, dirty cmd %d", operation_pram.dirty_cmd)
        return nil
    end
    local rsp = dirty_respond_bs:unpack(payload)
    if not rsp then
        log:debug('unpack respond failed, dirty cmd %d', operation_pram.dirty_cmd)
        return nil
    end
    log:debug(rsp)
    return rsp
end

function test_object:update_dirty_test_supported()
    local operation_pram = {
        dirty_cmd = dirty_test_def.cmd.GET_CAPABILITY
    }
    local rsp = self:send_dirty_ipmi_to_hdk(operation_pram)
    if rsp == nil then
        self.support_dirty_test = nil
    elseif rsp.exe_result == dirty_test_def.dirty_support_type.SUPPORT then
        self.support_dirty_test = true
    else
        self.support_dirty_test = false
    end
end

function test_object:update_dirty_info(rsp)
    if rsp.bit_map_max == 0 then
        self.is_dirty = 'NotDetected'
        self.dirty_distance = '0'
    else
        self.is_dirty = 'Detected'
        local min_distance
        local pos
        -- 取赃物通道里面的最小距离
        for bit_num = 0, 7 do
            if rsp.bit_map_max & (1 << bit_num) == (1 << bit_num) then
                pos = 'max_reflection_pos' .. (bit_num + 1)
                if not min_distance or min_distance > rsp[pos] then
                    min_distance = rsp[pos]
                end
            end
        end
        self.dirty_distance = tostring(min_distance / 10)
    end
end

function test_object:init_dirty_test(duration_second)
    if not duration_second then
        log:error('init input invalid, DurationSeconds is needed')
        return dirty_test_def.rsp_code.FAILED
    end
    self.duration_second = duration_second
    local operation_pram = {
        dirty_cmd = dirty_test_def.cmd.INIT,
        test_time = duration_second
    }
    return self:send_dirty_ipmi_to_hdk(operation_pram)
end

function test_object:start_dirty_test()
    local operation_pram = {
        dirty_cmd = dirty_test_def.cmd.START
    }
    return self:send_dirty_ipmi_to_hdk(operation_pram)
end

function test_object:close_dirty_test()
    local operation_pram = {
        dirty_cmd = dirty_test_def.cmd.CLOSE
    }
    return self:send_dirty_ipmi_to_hdk(operation_pram)
end

function test_object:get_dirty_test_result()
    local operation_pram = {
        dirty_cmd = dirty_test_def.cmd.GET_RESULT
    }
    local rsp = self:send_dirty_ipmi_to_hdk(operation_pram)
    if rsp == nil then
        return
    end
    -- 更新脏污测试状态
    if dirty_test_def.result_code[rsp.dirty_result] then
        self.state = dirty_test_def.dirty_state[dirty_test_def.result_code[rsp.dirty_result]]
    else
        self.state = dirty_test_def.dirty_state.UNKNOWN
    end
    -- 更新脏污测试数据
    if self.state == dirty_test_def.dirty_state.PENDING then
        self:update_dirty_info(rsp)
    end
end

return test_object