-- 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 class = require 'mc.class'
local c_handler_base = require 'bma.handles.handler_base'
local c_nvme = require 'nvme.nvme_object'
local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local def = require 'common_def'

local is_nil_or_null = c_handler_base.is_nil_or_null
local default_na = c_handler_base.default_na
local update_if_not_config = c_handler_base.update_if_not_config


local c_handler_nvme = class(c_handler_base)

function c_handler_nvme:ctor()
end

function c_handler_nvme:init()
    self:regist_odata_type('OemPCIE_SSD')

    self:regist_class_type(c_nvme)

    c_nvme.on_add_object:on(function(object)
        if object.SerialNumber ~= 'N/A' then
            -- 已存在 SN 的NVME直接刷新 BMA 资源
            self:match_resource(object)
        end

        -- 监听 SerialNumber变化， nvme硬盘可以通过vpd获取到。
        object:listen('SerialNumber', function(_, value)
            log:info('c_nvme: serial number update: sn=%s, slot=%d', value, object.Slot)

            -- 刷新 BMA 资源
            self:match_resource(object)
        end)
    end)
end

function c_handler_nvme:find_object(_, data)
    if not data.SerialNumber then
        return nil
    end

    -- 先通过 BDF 信息匹配
    local SN = data.SerialNumber
    local object = c_nvme.collection:find(function(obj)
        return obj.SerialNumber == SN
    end)
    if object then
        return object
    end

    return nil
end

function c_handler_nvme:update_capacity_mb(obj, data)
    if is_nil_or_null(data.CapacityBytes) then
        return
    end
    log:debug("Update nvme capacityBytes(%s)", data.CapacityBytes)

    obj.CapacityMiB = data.CapacityBytes // (1024 * 1024)
end

function c_handler_nvme:update_speed_gb(obj, data)
    log:debug("Update nvme negotiated speed Gbs(%s)", data.NegotiatedSpeedGbs)

    if is_nil_or_null(data.NegotiatedSpeedGbs) then
        return
    end
    for k, v in ipairs(def.PD_PHYSICAL_SPEED) do
        local speed_str = string.match(v, "%d+%.?%d*")
        if speed_str and data.NegotiatedSpeedGbs == tonumber(speed_str) then
            obj.NegotiatedSpeedGbs  = k
            break
        end
    end
end

function c_handler_nvme:update_smart_info(obj, data)
    local smart_info = data.SMARTInfo
    if not smart_info then
        return
    end

    log:debug("Update nvme smart info(PowerOnHours: %s, MediaErrorCount: %s, AvailableSpare: %s)",
        smart_info.PowerOnHours, smart_info.MediaErrorCount, smart_info.AvailableSpare)
    if not is_nil_or_null(smart_info.PowerOnHours) then
        obj.PowerOnHours = smart_info.PowerOnHours
    end
    if not is_nil_or_null(smart_info.MediaErrorCount) then
        obj.MediaErrorCount = smart_info.MediaErrorCount
    end

    if not is_nil_or_null(smart_info.AvailableSpare) then
        obj.SpareBlockPercentage = smart_info.AvailableSpare
    end
end

function c_handler_nvme:update_nvme(obj, data)
    log:notice("Update nvme(%s) info by bma", obj.SerialNumber)
    self:update_capacity_mb(obj, data)
    self:update_speed_gb(obj, data)
    self:update_smart_info(obj, data)

    log:debug("Update nvme info(revision: %s)",data.FirmwareVersion)
    update_if_not_config(obj.Revision, default_na(data.FirmwareVersion))
end

-- BMA断开后只恢复从BMA获取的动态信息为默认值
function c_handler_nvme:reset_nvme(obj)
    log:notice("Reset nvme info by bma")
    obj.PowerOnHours = def.INVALID_U32
    obj.MediaErrorCount = def.INVALID_U32
    obj.NegotiatedSpeedGbs = def.INVALID_U8
    obj.SpareBlockPercentage = def.INVALID_U8
end

function c_handler_nvme:add(_, data, object)
    self:update_nvme(object, data)
end

function c_handler_nvme:update(_, data, object)
    self:update_nvme(object, data)
end

function c_handler_nvme:delete(_, _, object)
    self:reset_nvme(object)
end

function c_handler_nvme:reset()
    for object, _ in pairs(self.objects) do
        self:reset_nvme(object)
    end
end

return singleton(c_handler_nvme)
