-- 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 battery_collection = require 'battery.battery_collection'
local volume_collection = require 'volume.volume_collection'
local array_collection = require 'array.array_collection'
local ctrl_commu_loss_monitor = require 'ctrl_commu_loss_monitor'
local BIT_OFFSET_MEMORY_CORRECTABLE_ERROR = 1
local BIT_OFFSET_MEMORY_UNCORRECTABLE_ERROR = 2
local BIT_OFFSET_ECC_ERROR = 3
local BIT_OFFSET_COMMUNICATION_LOST = 4

local function dump_controller_health(fault_code)
    local health_str = string.format("%-45s : ", "Controller Health")
    if fault_code == 0 then
        return string.format("%s%s\n", health_str, "Normal")
    end
    if fault_code == 0xFFFF then
        return string.format("%s%s\n", health_str, "N/A")
    end
    health_str = string.format("%sAbnormal\n\tDetails:\n", health_str)
    if fault_code & 1 ~= 0 then
        health_str = string.format("%s\t\tMemory correctable errors\n", health_str)
    end
    if fault_code & (1 << BIT_OFFSET_MEMORY_CORRECTABLE_ERROR) ~= 0 then
        health_str = string.format("%s\t\tMemory uncorrectable errors\n", health_str)
    end
    if fault_code & (1 << BIT_OFFSET_MEMORY_UNCORRECTABLE_ERROR) ~= 0 then
        health_str = string.format("%s\t\tMemory ECC errors\n", health_str)
    end
    if fault_code & (1 << BIT_OFFSET_ECC_ERROR) ~= 0 then
        health_str = string.format("%s\t\tNVRAM uncorrectable errors\n", health_str)
    end
    if fault_code & (1 << BIT_OFFSET_COMMUNICATION_LOST) ~= 0 then
        health_str = string.format("%s\t\tRaid communication loss\n", health_str)
    end
    health_str = string.format("%s\n\n", health_str)
    return health_str
end

local function trans_controller_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 sam_str <const> = {
    [0] = "Failure",
    [1] = "Predictive"
}

local function trans_controller_spare_activation_mode(spare_activation_mode)
    if sam_str[spare_activation_mode] then
        return sam_str[spare_activation_mode]
    else
        return "N/A"
    end
end

local bool_str <const> = {
    [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 int2str(num, invalid_num)
    if num == invalid_num then
        return "N/A"
    end
    return num
end

local function trans_controller_pcie_link_width(pcie_link_width)
    if pcie_link_width == "N/A" then
        return "N/A"
    end
    return string.format("%s bit(s)/link(s)", string.match(pcie_link_width, "%d+"))
end

local function trans_controller_no_battery_write_cache_enabled(no_battery_write_cache_enabled)
    if no_battery_write_cache_enabled == 0 then
        return 'Enabled'
    end
    if no_battery_write_cache_enabled == 1 then
        return 'Disabled'
    end
    return 'N/A'
end

local function trans_controller_read_cache_percent(read_cache_percent)
    if read_cache_percent == common_def.INVALID_U8 then
        return 'N/A'
    end
    return tostring(read_cache_percent)
end

local function trans_controller_write_cache_policy(write_cache_policy)
    return write_cache_policy ~= '' and write_cache_policy or 'N/A'
end

local function get_controller_boot_devices(fp_w, BootDevices)
    if BootDevices[1] == common_def.INVALID_STRING or BootDevices[1] == "None" then
        return
    end
    local str = string.format("%-45s : %s\n", "Primary Boot Device", BootDevices[1])

    if BootDevices[2] == common_def.INVALID_STRING or BootDevices[2] == "None" then
        if str ~= "" then
            fp_w:write(str)
        end
        return
    end

    str = string.format("%-45s : %s\n%-45s : %s\n", "Primary Boot Device", BootDevices[1],
        "Secondary Boot Device", BootDevices[2])
    if str ~= "" then
        fp_w:write(str)
    end
end

local controller_dump = class()

function controller_dump:ctor()

end

function controller_dump:dump_basic_info(fp_w, controller)
    fp_w:write(string.format("%-45s : %s\n", "Controller Name", controller.ControllerName))
    fp_w:write(string.format("%-45s : %s\n", "Controller Type", controller.Type))
    fp_w:write(string.format("%-45s : %s\n", "Component Name", controller.DeviceName))
    fp_w:write(string.format("%-45s : %s\n", "Support Out-of-Band Management",
        controller.OOBSupport == 1 and "Yes" or "No"))
    fp_w:write(string.format("%-45s : %s\n", "Controller Mode", controller.WorkMode))
end

function controller_dump:dump_oob_info(fp_w, controller)
    fp_w:write(string.format("%s", dump_controller_health(controller.FaultCode)))
    fp_w:write(string.format("%-45s : %s\n", "Firmware Version", controller.FirmwareVersion))
    fp_w:write(string.format("%-45s : %s\n", "NVDATA Version", controller.NVDataVersion))
    fp_w:write(string.format("%-45s : %s\n", "Hardware Revision", controller.HardwareRevision))
    if controller.MemorySizeMiB == common_def.INVALID_U16 then
        fp_w:write(string.format("%-45s : %s\n", "Memory Size", "N/A"))
    else
        fp_w:write(string.format("%-45s : %s MB\n", "Memory Size", controller.MemorySizeMiB))
    end
    fp_w:write(string.format("%-45s : %s\n", "Device Interface", controller.DeviceInterface))
    fp_w:write(string.format("%-45s : %s\n", "SAS Address", controller.SASAddr))
    fp_w:write(string.format("%-45s : %s\n", "Minimum Strip Size Supported",
        trans_controller_stripsize(controller.MinStripSizeBytes)))
    fp_w:write(string.format("%-45s : %s\n", "Maximum Strip Size Supported",
        trans_controller_stripsize(controller.MaxStripSizeBytes)))
    fp_w:write(string.format("%-45s : %s\n", "Spare Activation Mode",
        trans_controller_spare_activation_mode(0xFF)))
    fp_w:write(string.format("%-45s : %s\n", "Host bus link Width",
        trans_controller_pcie_link_width(controller.PCIeLinkWidth)))

    fp_w:write(string.format("%-45s : %s\n", "No-Battery Write Cache",
        trans_controller_no_battery_write_cache_enabled(controller.NoBatteryWriteCacheEnabled)))
    fp_w:write(string.format("%-45s : %s\n", "Read Cache Percentage",
        trans_controller_read_cache_percent(controller.ReadCachePercent)))
    fp_w:write(string.format("%-45s : %s\n", "Configured Drives Write Cache Policy",
        trans_controller_write_cache_policy(controller.ConfiguredDriveWriteCachePolicy)))
    fp_w:write(string.format("%-45s : %s\n", "Unonfigured Drives Write Cache Policy",
        trans_controller_write_cache_policy(controller.UnconfiguredDriveWriteCachePolicy)))
    fp_w:write(string.format("%-45s : %s\n", "HBA Drives Write Cache Policy",
        trans_controller_write_cache_policy(controller.HBADriveWriteCachePolicy)))

    fp_w:write(string.format("%-45s : %s\n", "Controller Cache Is Pinned", int2boolstr(controller.CachePinnedState)))

    fp_w:write(string.format("%-45s : %s\n", "Maintain PD Fail History across Reboot",
        int2boolstr(controller.MaintainPDFailHistrory)))
    fp_w:write(string.format("%-45s : %s\n", "Copyback Enabled", int2boolstr(controller.CopyBackState)))
    fp_w:write(string.format("%-45s : %s\n", "Copyback Enabled on SMART error",
        int2boolstr(controller.SmarterCopyBackState)))
    fp_w:write(string.format("%-45s : %s\n", "JBOD Enabled", int2boolstr(controller.JBODState)))

    fp_w:write(string.format("%-45s : %s\n", "DDR ECC Count", int2str(controller.DDREccCount)))
    fp_w:write(string.format("%-45s : %s\n", "Serial Number", controller.SerialNumber))
    fp_w:write(string.format("%-45s : %s\n", "Part Number", controller.BOMNumber))

    get_controller_boot_devices(fp_w, controller.BootDevices)
end

local battery_status_str <const> = {
    [0] = "Absent",
    [common_def.INVALID_U8] = "N/A"
}

local function dump_bbu_health_details(fp_w, battery_obj)
    if battery_obj.HealthStatus == 0 then
        fp_w:write("Normal\n")
        return
    end
    fp_w:write("Abnormal\n\tDetails : ")

    local detail_str = ""
    if battery_obj.voltage_low == 1 then
        detail_str = string.format("\t\t%s\n", "Voltage Low")
    end
    if battery_obj.replacepack == 1 then
        detail_str = string.format("%s\t\t%s\n", detail_str, "Battery Needs To Be Replaced")
    end
    if battery_obj.learn_cycle_failed == 1 then
        detail_str = string.format("%s\t\t%s\n", detail_str, "Learn Cycle Failed")
    end
    if battery_obj.learn_cycle_timeout == 1 then
        detail_str = string.format("%s\t\t%s\n", detail_str, "Learn Cycle Timeout")
    end
    if battery_obj.predictive_failure == 1 then
        detail_str = string.format("%s\t\t%s\n", detail_str, "Pack is about to fail")
    end
    if battery_obj.remaining_capacity_low == 1 then
        detail_str = string.format("%s\t\t%s\n", detail_str, "Remaining Capacity Low")
    end
    if battery_obj.no_space == 1 then
        detail_str = string.format("%s\t\t%s\n", detail_str, "No Space for Cache Offload")
    end
    if battery_obj.failed == 1 then
        detail_str = string.format("%s\t\t%s\n", detail_str, "Failed")
    end

    fp_w:write(string.format("%s\n", detail_str))
end

local function dump_bbu_heal_status(fp_w, controller, battery_obj)
    if ctrl_commu_loss_monitor.get_instance():get_ctrl_commu_loss(controller.Id) == common_def.ctrl_commu_loss or
        controller.CommunicationLoss == common_def.ctrl_commu_loss then
        fp_w:write(string.format("%-45s : %s\n", "BBU Health", "N/A"))
        fp_w:write(string.format("%-45s : ", "BBU Previous Health"))
        dump_bbu_health_details(fp_w, battery_obj)
        return
    end

    fp_w:write(string.format("%-45s : ", "BBU Health"))
    dump_bbu_health_details(fp_w, battery_obj)
end

function controller_dump:dump_battery_info(fp_w, controller)
    local battery_obj = battery_collection.get_instance():get_capcacitance_by_controller_id(controller.Id)
    if not battery_obj then
        return
    end

    if battery_status_str[battery_obj.State] then
        fp_w:write(string.format("\n%-45s : %s\n", "BBU Status",battery_status_str[battery_obj.State]))
        return
    end
    fp_w:write(string.format("\n%-45s : %s\n", "BBU Status", "Present"))
    fp_w:write(string.format("%-45s : %s\n", "BBU Type", battery_obj.Name))
    dump_bbu_heal_status(fp_w, controller, battery_obj)
end

function controller_dump:dump_phy_err(fp_w, controller)
    fp_w:write(string.format("\n%-45s : ", "PHY Status"))
    local phy_children = controller:get_phy_children()
    local available = false
    for k, v in pairs(phy_children) do
        if v.PhyId ~= common_def.INVALID_U8 then
            fp_w:write(string.format("\n\tPHY #%u :\n", v.PhyId))
            fp_w:write(string.format("\t\t%-30s: %u\n", "Invalid Dword Count", v.InvalidDwordCount))
            fp_w:write(string.format("\t\t%-30s: %u\n", "Loss Dword Sync Count", v.LossDwordSyncCount))
            fp_w:write(string.format("\t\t%-30s: %u\n", "PHY Reset Problem Count", v.PhyResetProblemCount))
            fp_w:write(string.format("\t\t%-30s: %u\n", "Running Disparity Error Count", v.RunningDisparityErrorCount))
            available = true
        end        
    end

    if not available then
        fp_w:write("Unavailable\n")
    end
end

function controller_dump:dump_info(fp_w, controller)
    self:dump_basic_info(fp_w, controller)
    if controller.OOBSupport == 1 then
        self:dump_oob_info(fp_w, controller)
        self:dump_battery_info(fp_w, controller)
        self:dump_phy_err(fp_w, controller)

        fp_w:write("Logical Drive Information\n")
        fp_w:write("---------------------------\n")
        volume_collection.get_instance():dump_info(fp_w, controller.Id)
        fp_w:write("Disk Array Information\n")
        fp_w:write("-------------------------\n")
        array_collection.get_instance():dump_info(fp_w, controller.Id)
    end
end

return singleton(controller_dump)