-- 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 protocol = ProcessingFlow[2].Destination.Protocol
local media_type = ProcessingFlow[2].Destination.MediaType
local resource_id = ProcessingFlow[2].Destination.ResourceId
local rebuild_state = ProcessingFlow[2].Destination.RebuildState
local rebuild_progress = ProcessingFlow[2].Destination.RebuildProgress
local patrol_state = ProcessingFlow[2].Destination.PatrolState
local temperature_celsius = ProcessingFlow[2].Destination.TemperatureCelsius
local power_state = ProcessingFlow[2].Destination.PowerState
local hours = ProcessingFlow[2].Destination.PowerOnHours
local priority = ProcessingFlow[2].Destination.BootPriority
local enclosure_id = ProcessingFlow[2].Destination.EnclosureId
local level = tonumber(ProcessingFlow[7].Destination.RAIDType) or 255
local firmware_status = ProcessingFlow[2].Destination.FirmwareStatus
local media_error_count = ProcessingFlow[2].Destination.MediaErrorCount
local predicted_fail_count = ProcessingFlow[2].Destination.PredictedFailCount
local other_error_count = ProcessingFlow[2].Destination.OtherErrorCount
local estimated_remaining_lifespan = ProcessingFlow[9].Destination.EstimatedRemainingLifespan
local type_id = ProcessingFlow[5].Destination.TypeId
local total_free_space = ProcessingFlow[7].Destination.TotalFreeSpaceMiB
local disk_array_path = ProcessingFlow[6].Destination.Path
local drive_name = ProcessingFlow[2].Destination.Name
local volume_list = Input

local U32_MAX <const> = 4294967295
local DRIVE_ESTIMATED_LIFESPAN_UNSUPPORT <const> = 4294967294
local PCIE = 6
-- GetPath函数返回的无效路径
local INVALID_PATH<const> = ''

local vender_def = {
    VENDER_PMC = 1,
    VENDER_HISTORE = 2,
    PMC_3152_8I_SMART_RAID = 64,
    PMC_2100_8I_SMART_HBA = 65,
    HI1880_SP186_M_16i = 96,
    HI1880_SP186_M_8i = 113
}

local function is_pmc_controller(type_id)
    return type_id == vender_def.PMC_3152_8I_SMART_RAID or type_id == vender_def.PMC_2100_8I_SMART_HBA
end

local function is_histore_controller(type_id)
    return type_id >= vender_def.HI1880_SP186_M_16i and type_id <= vender_def.HI1880_SP186_M_8i
end

local VENDOR_MAP <const> = {
    [vender_def.VENDER_PMC] = is_pmc_controller,
    [vender_def.VENDER_HISTORE] = is_histore_controller
}

local function check_controller_vendor(type_id, vendor_id)
    local check_func = VENDOR_MAP[vendor_id]
    if not check_func then
        return false
    end

    return check_func(type_id)
end

-- key值表示固件状态，下同
local state_map <const> = {
    [0] = 'Enabled',
    [1] = 'Enabled',
    [2] = 'StandbySpare',
    [3] = 'StandbyOffline',
    [4] = 'Enabled',
    [5] = 'Enabled',
    [6] = 'Enabled',
    [7] = 'Enabled',
    [8] = 'Enabled',
    [9] = 'Disabled',
    [10] = 'Disabled',
    [11] = 'Disabled',
    [12] = 'Disabled',
    [13] = 'Enabled',
    [14] = 'StandbyOffline',
    [15] = 'StandbyOffline',
    [16] = 'Enabled',
    [17] = 'Enabled',
    [18] = 'Enabled',
    [19] = 'Enabled',
    [20] = 'Enabled',
    [21] = 'Enabled',
    [22] = 'Disabled',
    [23] = 'Enabled',
    [24] = 'Updating',
    [25] = 'Disabled',
    [26] = 'Updating'
}

local status_map <const> = {
    [0] = 'UnconfiguredGood',
    [1] = 'UnconfiguredBad',
    [2] = 'HotSpareDrive',
    [3] = 'Offline',
    [4] = 'Failed',
    [5] = 'Rebuilding',
    [6] = 'Online',
    [7] = 'GettingCopied',
    [8] = 'JBOD',
    [9] = 'UnconfiguredShielded',
    [10] = 'HotSpareShielded',
    [11] = 'ConfiguredShielded',
    [12] = 'Foreign',
    [13] = 'Active',
    [14] = 'Standby',
    [15] = 'Sleep',
    [16] = 'DSTInProgress',
    [17] = 'SMARTOfflineDataCollection',
    [18] = 'SCTCommand',
    [19] = 'Online',
    [20] = 'Raw',
    [21] = 'Ready',
    [22] = 'NotSupported',
    [23] = 'PredictiveFailure',
    [24] = 'Diagnosing',
    [25] = 'Incompatible',
    [26] = 'EraseInProgress'
}

local rebuild_state_switch <const> = {
    [0] = "DoneOrNotRebuilt",
    [1] = "Rebuilding"
}

local patrol_state_switch <const> = {
    [0] = "DoneOrNotPatrolled",
    [1] = "Patrolling"
}

local power_state_switch <const> = {
    [0] = "Spun Up",
    [1] = "Spun Down",
    [2] = "Transition"
}

local type_switch <const> = {
    [2] = "Disk",
    [3] = "Disk",
    [6] = "PCIe SSD Card"
}

local boot_priority_switch <const> = {
    [0] = "None",
    [1] = "Primary",
    [2] = "Secondary",
    [3] = "All"
}

local function get_spare_for_logicalDrives()
    local SpareforLogicalDrives = cjson.json_object_new_array()
    local HotspareType = ProcessingFlow[2].Destination.HotspareType
    local name = ProcessingFlow[2].Destination.Name
    local RefControllerId = ProcessingFlow[2].Destination.RefControllerId
    local tmp_drive
    if HotspareType ~= 2 and HotspareType ~= 3 then
        return SpareforLogicalDrives
    end
    for _, item in ipairs(volume_list) do
        if item.RefControllerId ~= RefControllerId then
            goto continue
        end
        for _, v in ipairs(item.HotSpareDriveList) do
            if v == name then
                tmp_drive = cjson.json_object_new_object()
                tmp_drive["@odata.id"] = "/redfish/v1/Systems/1/Storages/RAIDStorage" .. RefControllerId ..
                    "/Volumes/LogicalDrive" .. item.Id
                SpareforLogicalDrives[#SpareforLogicalDrives + 1] = tmp_drive
                goto continue
            end
        end
        ::continue::
    end
    return SpareforLogicalDrives
end

local function get_health()
    local health = ProcessingFlow[3].Destination.Health
    local health_severity = {
        [0] = { 'OK', 'Informational' },
        [1] = { 'Warning', 'Minor' },
        [2] = { 'Warning', 'Major' },
        [3] = { 'Critical', 'Critical' }
    }
    local hs = health_severity[health]
    return hs and hs[1] or cjson.null
end

local function get_val_or_null(val, default)
    return val == default and cjson.null or val
end

local function get_sas_smart_information()
    local smart_information = cjson.json_object_new_object()
    smart_information.MediaErrorCount = (media_error_count == 0xffffffff) and cjson.null or media_error_count
    smart_information.PrefailErrorCount = (predicted_fail_count == 0xffffffff) and cjson.null or predicted_fail_count
    smart_information.OtherErrorCount = (other_error_count == 0xffffffff) and cjson.null or other_error_count
    smart_information.DiskTemperatureCelsius = (temperature_celsius == 255) and cjson.null or temperature_celsius
    smart_information.DiskStripeTemperatureCelsius =
        get_val_or_null(ProcessingFlow[8].Destination.StripTemperatureCelsius, U32_MAX)

    smart_information.ElementsInGrownDefectList =
        get_val_or_null(ProcessingFlow[8].Destination.ElementsInGrownDefectList, U32_MAX)
    smart_information.ElementsInPrimaryDefectList =
        get_val_or_null(ProcessingFlow[8].Destination.ElementsInPrimaryDefectList, U32_MAX)
    smart_information.BlocksSentToInitiator =
        get_val_or_null(ProcessingFlow[8].Destination.BlocksSentToInitiator, U32_MAX)
    smart_information.BlocksReceivedFromInitiator =
        get_val_or_null(ProcessingFlow[8].Destination.BlocksReceivedFromInitiator, U32_MAX)
    smart_information.NumberOfMinutesUntilNextSMARTTest =
        get_val_or_null(ProcessingFlow[8].Destination.UntilNextInterSMARTTestMinutes, U32_MAX)
    smart_information.SequenceNumberOfLastPredFailEvent =
        get_val_or_null(ProcessingFlow[8].Destination.LastPrefailEventSeqNum, U32_MAX)
    smart_information.HealthStatus = get_health()
    smart_information.WeekAndYearOfManufacture =
        get_val_or_null(ProcessingFlow[8].Destination.ManufacturedInWeekOfYear, 'N/A')
    return smart_information
end

local function get_sas_smart_information_impl(oem_info)
    oem_info.SASSmartInformation = get_sas_smart_information()
    oem_info.SATASmartInformation = cjson.null
end

-- SATASmartInformation不支持
local function get_sata_smart_information_impl(oem_info)
    oem_info.SASSmartInformation = cjson.null
    oem_info.SATASmartInformation = cjson.null
end

local function get_nvme_smart_information()
    local smart_information = cjson.json_object_new_object()
    smart_information.MediaErrorCount = (media_error_count == 0xffffffff) and cjson.null or media_error_count
    return smart_information
end

local function get_nvme_smart_information_impl(oem_info)
    oem_info.NVMeSmartInformation = get_nvme_smart_information()
end

-- flag:true 表示需要span_count参与计算
local function get_cjson_array(src_array, flag, span_count)
    local arr = cjson.json_object_new_array()
    if not src_array then
        return arr
    end

    for _, v in pairs(src_array) do
        arr[#arr + 1] = flag and v * span_count or v
    end
    return arr
end

local function get_span_count()
    if not volume_list then
        return 1
    end
    for _, volume in pairs(volume_list) do
        if not volume.Drives then
            goto continue
        end
        for _, drive in pairs(volume.Drives) do
            if drive == drive_name then
                return volume.SpanCount and volume.SpanCount or 1
            end
        end
        ::continue::
    end

    return 1
end

local function get_related_array_info()
    if not disk_array_path or disk_array_path == INVALID_PATH then
        return cjson.null
    end

    local related_array_info = cjson.json_object_new_object()
    related_array_info.Members = get_cjson_array(ProcessingFlow[7].Destination.RefDrives, false, nil)

    local span_count = 1
    if not check_controller_vendor(type_id, vender_def.VENDER_PMC) and
        not check_controller_vendor(type_id, vender_def.VENDER_HISTORE) then
        span_count = get_span_count()
    end

    related_array_info.FreeSpaceMiBPerDrive = ProcessingFlow[7].Destination.AverageDriveFreeSpaceMiB or cjson.null
    related_array_info.TotalFreeSpaceMiB = cjson.null
    if total_free_space then
        related_array_info.TotalFreeSpaceMiB = total_free_space * span_count
    end
    related_array_info.FreeBlocksSpaceMiB = get_cjson_array(ProcessingFlow[7].Destination.FreeBlocksSpaceMiB,
        true, span_count)
    related_array_info.NumDrivePerSpan = ProcessingFlow[7].Destination.DriveNumPerSpan or cjson.null
    related_array_info.VolumeRaidLevel = level <= 60 and string.format('RAID%u', level) or cjson.null
    return related_array_info
end

local function get_drive_bdf()
    if protocol ~= PCIE then
        return cjson.null
    end

    return string.format('%04u:%02u:%02u.%u', ProcessingFlow[12].Destination.Segment,
        ProcessingFlow[12].Destination.DevBus, ProcessingFlow[12].Destination.DevDevice,
        ProcessingFlow[12].Destination.DevFunction)
end

local oem_info = cjson.json_object_new_object()

local related_array_info = get_related_array_info()

oem_info.DriveID = ProcessingFlow[2].Destination.Id
oem_info.RebuildState = rebuild_state_switch[rebuild_state] or cjson.null
oem_info.AssociatedResource =
    (protocol ~= 6 or resource_id == 0 or resource_id == 255) and cjson.null or string.format('CPU%s', resource_id)
oem_info.PatrolState = patrol_state_switch[patrol_state] or cjson.null
oem_info.TemperatureCelsius = (temperature_celsius == 255) and cjson.null or temperature_celsius
oem_info.FirmwareStatus = status_map[firmware_status] or cjson.null
oem_info.IsEPD = (ProcessingFlow[2].Destination.FirmwareStatus == 19)
oem_info.SpareforLogicalDrives = get_spare_for_logicalDrives() or cjson.null
oem_info.SASAddress =
    cjson.json_object_from_table({
        get_val_or_null(ProcessingFlow[2].Destination.SASAddress1, 'N/A'),
        get_val_or_null(ProcessingFlow[2].Destination.SASAddress2, 'N/A')})
oem_info.PowerState = power_state_switch[power_state] or cjson.null
oem_info.Type = type_switch[protocol] or cjson.null
oem_info.Position = 'HDDPlane'
oem_info.HoursOfPoweredUp = hours >= 65535 and cjson.null or tonumber(string.format('%.2f', hours))

local get_smart_information_func_map = {
    [2] = get_sas_smart_information_impl,
    [3] = get_sata_smart_information_impl,
    [6] = get_nvme_smart_information_impl
}

local smart_information_func = get_smart_information_func_map[protocol]
if not smart_information_func then
    oem_info.SASSmartInformation = cjson.null
    oem_info.SATASmartInformation = cjson.null
else
    smart_information_func(oem_info)
end

oem_info.CryptoEraseSupported = ProcessingFlow[5].Destination.CryptoEraseSupported
oem_info.RelatedArrayInfo = related_array_info
oem_info.SlotNumber =
    (ProcessingFlow[2].Destination.SlotNumber == 255) and cjson.null or ProcessingFlow[2].Destination.SlotNumber
oem_info.EnclosureDeviceId = enclosure_id >= 65535 and cjson.null or enclosure_id
oem_info.BootPriority = boot_priority_switch[priority] or cjson.null
oem_info.BootEnable = (priority == 1)
oem_info.EstimatedRemainingLifespan =
    (estimated_remaining_lifespan == DRIVE_ESTIMATED_LIFESPAN_UNSUPPORT or estimated_remaining_lifespan == U32_MAX) and
    cjson.null or estimated_remaining_lifespan

oem_info.BDF = get_drive_bdf()
if rebuild_state ~= 1 then
    oem_info.RebuildProgress = cjson.null
    return oem_info
end
if rebuild_progress ~= 255 then
    oem_info.RebuildProgress = string.format('%u%%', rebuild_progress)
    return oem_info
end
oem_info.RebuildProgress = 'UnknownRebuildProgress'
if check_controller_vendor(type_id, vender_def.VENDER_PMC) then
    oem_info.RebuildProgress = 'N/A'
end
return oem_info
