-- 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 common_def = require 'common_def'
local class = require 'mc.class'
local singleton = require 'mc.singleton'
local array_collection = require 'array.array_collection'

local volume_dump = class()

function volume_dump:ctor()
end

local function dump_basic_info(fp_w, volume)
    fp_w:write(string.format("%-40s : %s\n", "Target ID", volume.VolumeId))
    fp_w:write(string.format("%-40s : %s\n", "Name", volume.VolumeName))
    fp_w:write(string.format("%-40s : %s\n", "Type", common_def.RAID_LEVEL_DESC[volume.RAIDType] or "N/A"))
    fp_w:write(string.format("%-40s : %s\n", "State", common_def.LD_STATE_DESC[volume.State]))
end

local LD_POLICY_STR = {
    ["NoReadAhead"] = 'No Read Ahead',
    ["ReadAhead"] = 'Read Ahead',
    ["ReadAheadAdaptive"] = 'Read Ahead Adaptive',

    ["WriteThrough"] = 'Write Through',
    ["WriteBackWithBBU"] = 'Write Back with BBU',
    ["WriteBack"] = 'Write Back',

    ["CachedIO"] = 'Cached IO',
    ["DirectIO"] = 'Direct IO',

    ["ReadWrite"] = "Read Write",
    ["ReadOnly"] = "Read Only",
    ["Blocked"] = "Blocked",
    ["Hidden"] = "Hidden"
}

local LD_CACHE_LINE_SIZE_STR<const> = {
    [1] = '64 KB',
    [4] = '256 KB'
}

local function dump_default_policy(fp_w, volume)
    fp_w:write(string.format("%-40s : %s\n", "Default Read Policy", LD_POLICY_STR[volume.DefaultReadPolicy] or "N/A"))
    fp_w:write(string.format("%-40s : %s\n", "Default Write Policy", LD_POLICY_STR[volume.DefaultWritePolicy] or "N/A"))
    fp_w:write(string.format("%-40s : %s\n", "Default Cache Policy", LD_POLICY_STR[volume.DefaultCachePolicy] or "N/A"))
end

local function dump_current_policy(fp_w, volume)
    fp_w:write(string.format("%-40s : %s\n", "Current Read Policy", LD_POLICY_STR[volume.CurrentReadPolicy] or "N/A"))
    fp_w:write(string.format("%-40s : %s\n", "Current Write Policy", LD_POLICY_STR[volume.CurrentWritePolicy] or "N/A"))
    fp_w:write(string.format("%-40s : %s\n", "Current Cache Policy", LD_POLICY_STR[volume.CurrentCachePolicy] or "N/A"))
end

local function dump_access_policy(fp_w, volume)
    fp_w:write(string.format("%-40s : %s\n", "Access Policy", LD_POLICY_STR[volume.AccessPolicy] or "N/A"))
end

local function int2str(num, invalid_num)
    if num == invalid_num then
        return "N/A"
    end
    return num
end

local function dump_span_info(fp_w, volume)
    fp_w:write(string.format("%-40s : %s\n", "Span depth", int2str(volume.SpanCount, common_def.INVALID_U8)))
    fp_w:write(string.format("%-40s : %s\n", "Number of drives per span",
        int2str(volume.NumDrivePerSpan, common_def.INVALID_U8)))
end

local function trans_stripsize(stripsize)
    if stripsize == 0 then
        return "N/A"
    end
    if stripsize == 512 then
        return "512 B"
    end
    if stripsize / 512 <= (1 << 10) then
        return string.format("%s KB", stripsize / 1024)
    end
    return string.format("%s MB", stripsize / 1024 / 1024)
end

local function trans_capacity_size(capacity_size_byte)
    local capacity_basic_unit = 1024
    local capacity_size_mb = capacity_size_byte / capacity_basic_unit / capacity_basic_unit
    if capacity_size_mb == common_def.INVALID_U32 then
        return "N/A"
    elseif capacity_size_mb < capacity_basic_unit then
        return string.format("%s MB", capacity_size_mb)
    elseif capacity_size_mb < (capacity_basic_unit * capacity_basic_unit) then
        return string.format("%.3f GB", capacity_size_mb / capacity_basic_unit)
    elseif capacity_size_mb < (capacity_basic_unit * capacity_basic_unit * capacity_basic_unit) then
        return string.format("%.3f TB", capacity_size_mb / (capacity_basic_unit * capacity_basic_unit))
    else
        return string.format("%.3f PB",
            capacity_size_mb / (capacity_basic_unit * capacity_basic_unit * capacity_basic_unit))
    end
end

local ACCELERATION_METHOD_STR<const> = {
    ['None'] = 'None',
    ['ControllerCache'] = 'Controller Cache',
    ['IOBypass'] = 'IO Bypass',
    ['maxCache'] = 'maxCache'
}

local function trans_acceleration_method(acceleration_method)
    if not ACCELERATION_METHOD_STR[acceleration_method] then
        return 'Unknown'
    end
    return ACCELERATION_METHOD_STR[acceleration_method]
end

local bool_str = {
    [0] = "No",
    [1] = "Yes"
}

local function int2boolstr(num)
    if bool_str[num] then
        return bool_str[num]
    else
        return "N/A"
    end
end

local function dump_volume_info(fp_w, volume)
    fp_w:write(string.format("%-40s : %s\n", "Strip Size", trans_stripsize(volume.OptimumIOSizeBytes)))
    fp_w:write(string.format("%-40s : %s\n", "Total Size", trans_capacity_size(volume.CapacityBytes)))
    fp_w:write(string.format("%-40s : %s\n", "Disk Cache Policy", common_def.LD_DCP_STR[volume.DriveCachePolicy]))
    fp_w:write(string.format("%-40s : %s\n", "Init State", common_def.LD_INIT_TYPE_STR[volume.InitializationMode]))
    fp_w:write(string.format("%-40s : %s\n", "BGI Enabled", int2boolstr(volume.BGIEnable)))
    fp_w:write(string.format("%-40s : %s\n", "Current in FGI", int2boolstr(volume.CurrentForegroundInitState)))
    fp_w:write(string.format("%-40s : %s\n", "FGI progress",
        volume.ForegroundInitProgress < 100 and volume.ForegroundInitProgress or "N/A"))
    fp_w:write(string.format("%-40s : %s\n", "Consistency Checking", int2boolstr(volume.ConsistencyCheck)))
    fp_w:write(string.format("%-40s : %s\n", "Bootable", int2boolstr(volume.BootEnable)))
    fp_w:write(string.format("%-40s : %s\n", "BootPriority", common_def.PD_BOOT_PRIORITY[volume.BootPriority]))
    fp_w:write(string.format("%-40s : %s\n", "Used for Secondary Cache", int2boolstr(volume.SSDCachingEnable)))
end

local function dump_pd_in_span(fp_w, span_pd_list)
    local drive_list = {}
    for _, drive_name in pairs(span_pd_list) do
        if not drive_list[drive_name] then
            drive_list[drive_name] = true
            drive_list[#drive_list + 1] = string.match(drive_name, "%d+")
        end
    end
    for idx, drive_id in ipairs(drive_list) do
        fp_w:write(drive_id)
        if idx ~= #drive_list then
            fp_w:write(",")
        end
    end
end

local function dump_single_span(fp_w, volume)
    fp_w:write(string.format("%-40s : ", "PD participating in LD (ID#)"))
    if not next(volume.RefDriveList) then
        fp_w:write("N/A\n")
        return
    end
    dump_pd_in_span(fp_w, volume.RefDriveList)
    fp_w:write("\n")
end

local function dump_multi_span(fp_w, volume)
    fp_w:write(string.format("%-40s : ", "PD participating in LD (ID#)"))
    if not next(volume.RefDiskArrayList) then
        fp_w:write("N/A\n")
        return
    end
    local array_list = {}
    local using_sub_array  = false
    for _, array_id in pairs(volume.RefDiskArrayList) do
        array_list[array_id] = array_collection.get_instance():get_ctrl_array_by_id(volume.RefControllerId, array_id)
        if array_id > 0x8000 then
            using_sub_array = true
        end
    end

    for array_id, array in pairs(array_list) do
        if (using_sub_array and array_id > 0x8000) or not using_sub_array  then
            fp_w:write(string.format("\n%-20sSpan%s - ", "", array_id))
            dump_pd_in_span(fp_w, array.RefDrives)
        end
    end
end

local function dump_participate_pds(fp_w, volume)
    if volume.SpanCount == 1 then
        dump_single_span(fp_w, volume)
    else
        dump_multi_span(fp_w, volume)
    end
    fp_w:write("\n")
end

local function dump_spared_pds(fp_w, volume)
    fp_w:write(string.format("%-40s : ", "Dedicated Hot Spare PD (ID#)"))
    if not next(volume.HotSpareDriveList) then
        fp_w:write("N/A\n")
    end

    dump_pd_in_span(fp_w, volume.HotSpareDriveList)
    fp_w:write("\n")
end

local function dump_accelerator(fp_w, volume)
    fp_w:write(string.format("%-40s : ", "Acceleration Method"))
    fp_w:write(trans_acceleration_method(volume.AccelerationMethod))
    fp_w:write("\n")
end

local function dump_cache_line_size(fp_w, volume)
    fp_w:write(string.format("%-40s : ", "Cache Line Size"))
    if LD_CACHE_LINE_SIZE_STR[volume.CacheLineSizeKiB] then
        fp_w:write(string.format("%s\n", LD_CACHE_LINE_SIZE_STR[volume.CacheLineSizeKiB]))
        return
    end
    fp_w:write("Unknown\n")
end

local function dump_rebuild_progress(fp_w, volume)
    fp_w:write(string.format("%-40s : ", "Rebuild in Progress"))
    if volume.RebuildState == 0 then
        fp_w:write("No\n")
        return
    end
    if volume.RebuildState == 255 then
        fp_w:write("N/A\n")
        return
    end
    if volume.RebuildState ~= 1 then
        fp_w:write("Unknown\n")
        return
    end
    if volume.RebuildProgress == 255 then
        fp_w:write("Yes (Unknown Progress)\n")
        return
    end
    fp_w:write(string.format("Yes (%s%%)\n", volume.RebuildProgress))
end

function volume_dump:dump_info(fp_w, volume)
    fp_w:write("----------------------------------------------------------------------\n")
    dump_basic_info(fp_w, volume)
    dump_default_policy(fp_w, volume)
    dump_current_policy(fp_w, volume)
    dump_access_policy(fp_w, volume)
    dump_span_info(fp_w, volume)
    dump_volume_info(fp_w, volume)
    dump_participate_pds(fp_w, volume)
    dump_spared_pds(fp_w, volume)
    dump_accelerator(fp_w, volume)
    dump_cache_line_size(fp_w, volume)
    dump_rebuild_progress(fp_w, volume)
    fp_w:write("----------------------------------------------------------------------\n\n")
end

return singleton(volume_dump)