-- 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 singleton = require 'mc.singleton'
local task_mgmt = require 'mc.mdb.task_mgmt'
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local drive_collection = require 'drive.drive_collection'
local nvme_utils = require 'nvme.utils'
local nvme_mi_command = require 'nvme.nvme_mi_protocol.nvme_mi_command'
local nvme_mi_admin = require 'nvme.nvme_mi_protocol.nvme_mi_admin_command'
local base_msg = require 'messages.base'
local custom_error = require 'messages.custom'
local create_code = task_mgmt.create_code

local STANDARD_PROTOCOL = 0
local max_export_task = 1 -- 最大任务数

local rpc_service_nvme_drive = class()

function rpc_service_nvme_drive:get_nvme_drive_count()
    local ds = drive_collection.get_instance()
    if not ds.nvme_list then
        log:error('[Storage] nvme list is nil')
        return comp_code.UnspecifiedError
    end
    local nvme_len = #ds.nvme_list
    return nvme_len
end

function rpc_service_nvme_drive:get_nvme_drive_rawdata(index)
    local ds = drive_collection.get_instance()
    if not ds.nvme_list then
        log:error('[Storage] nvme list is nil')
        return tostring(comp_code.UnspecifiedError)
    end
    local nvme_len = #ds.nvme_list

    if index > nvme_len - 1 then
        log:error('[Storage] index is out of range, index:%s, len:%s', index, nvme_len)
        return tostring(comp_code.ParmOutOfRange)
    end
    local data
    local nvme = ds.nvme_list[index + 1]
    if nvme then
        data = nvme:get_nvme_info()
    end
    if not data then
        log:error('[Storage] get data failed')
        return tostring(comp_code.UnspecifiedError)
    end
    return data
end

local function parse_drive_id(path)
    if not path then
        return
    end
    local drive_id = path:match('Drive_(%d+)_%d+')
    if drive_id then
        return drive_id - 1
    end
end

local function get_nvme_obj(path, name)
    local id = parse_drive_id(path)
    local nvme_list = drive_collection.get_instance().nvme_list
    if not nvme_list or not next(nvme_list) or not id then
        log:error('match nvme obj failed, %s %s', nvme_list, id)
        error(base_msg.ActionNotSupported(name))
    end
    for _, obj in ipairs(nvme_list) do
        if id == obj.Slot and obj.nvme_mi_mctp_obj and obj.nvme_mi_mctp_obj.nvme_mi_obj then
            return obj
        end
    end
    log:error('match nvme%s obj failed', id)
    error(base_msg.ActionNotSupported(name))
end

function rpc_service_nvme_drive:send_nvmemi_command(path, opcode, dw0, dw1, data)
    local obj = get_nvme_obj(path, 'SendNvmeMICommand')
    log:notice('sending NVMe%s nvme_mi_command packets, opcode %s, dw0 %s, dw1 %s',obj.Slot, opcode, dw0, dw1)
    local status, nvmersq, rsp_data = nvme_mi_command.send_raw_nvme_mi_command(obj, {opcode, dw0, dw1, data})
    if not status or not nvmersq or not rsp_data then
        error(base_msg.ActionNotSupported("SendNvmeMICommand"))
    end
    return status, nvmersq, rsp_data
end

function rpc_service_nvme_drive:get_telemetry_data(path, bus, data_type)
    if max_export_task <= 0 then
        error(custom_error.DuplicateExportingErr())
    end
    max_export_task = max_export_task - 1
    local ok, obj = pcall(function ()
        return get_nvme_obj(path, 'GetTelemetryData')
    end)
    if not ok then
        max_export_task = max_export_task + 1
        error(base_msg.ActionNotSupported('GetTelemetryData'))
    end
    local name = 'Export NVMe Telemetry Data Task'
    local timeout_min = 60
    local create_status, err, task_id = task_mgmt.create_task(bus, name, path, timeout_min)
    if create_status ~= create_code.TASK_CREATE_SUCCESSFUL then
        max_export_task = max_export_task + 1
        log:error('create task %s failed', name)
        error(err)
    end
    skynet.fork(function ()
        log:notice('start dump telemetry log')
        ok, err = pcall(function()
            nvme_mi_admin.get_telemetry_log(obj, data_type, task_id)
        end)
        if not ok then
            log:error('dump telemetry log failed: %s', err)
        end
        max_export_task = max_export_task + 1
        log:notice('finish dump telemetry log')
    end)
    return task_id
end

function rpc_service_nvme_drive:get_smart_info(path, vendor)
    local ret
    local obj = get_nvme_obj(path, 'GetSmartInfo')
    log:notice('sending NVMe%s SmartLog packets, vendor: %s', obj.Slot, vendor)
    if vendor == STANDARD_PROTOCOL then
        obj.nvme_mi_mctp_obj.queue(function ()
            pcall(function ()
                ret = obj.nvme_mi_mctp_obj.nvme_mi_obj:SmartLog():value()
            end)
        end)
    else
        ret = nvme_mi_admin.get_add_smart_log(obj, vendor)
    end
    if not ret then
        error(base_msg.ActionNotSupported("GetSmartInfo"))
    end
    local rsp = nvme_utils.little_endian_table(ret)
    if not next(rsp) then
        error(base_msg.ActionNotSupported("GetSmartInfo"))
    end

    return rsp
end

function rpc_service_nvme_drive:get_identify_data(path, cns_value)
    local obj = get_nvme_obj(path, 'GetIdentifyData')
    local rsp
    log:notice('sending NVMe%s GetIdentifyData packets, cns_value: %s', obj.Slot, cns_value)
    if cns_value == 0 then
        rsp = nvme_mi_admin.get_identify_data(obj, cns_value)
    end
    if not rsp then
        log:error('get identify data failed, cns: %s, rsp: %s', cns_value, rsp)
        error(base_msg.ActionNotSupported("GetIdentifyData"))
    end
    return rsp
end

return singleton(rpc_service_nvme_drive)