-- 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 controller_collection = require 'controller.controller_collection'
local drive_collection = require 'drive.drive_collection'
local class = require 'mc.class'
local singleton = require 'mc.singleton'
local common_def = require 'common_def'
local CTRL_OPTION = common_def.CTRL_OPTION
local method_misc = require 'method_misc'

local param_check = class()


-- 根据控制器id判断是否是某类卡
function param_check:test_controller_vendor_by_ctrl_id(controller_id, vendor_id)
    local controller_obj = controller_collection:get_by_controller_id(controller_id)
    if vendor_id == common_def.VENDER_LSI and
       controller_obj.TypeId < common_def.PMC_3152_8I_SMART_RAID then
        return true
    elseif vendor_id == common_def.VENDER_PMC and
           (controller_obj.TypeId == common_def.PMC_3152_8I_SMART_RAID or
            controller_obj.TypeId == common_def.PMC_2100_8I_SMART_HBA) then
        return true
    elseif vendor_id == common_def.VENDER_HUAWEI and
           (controller_obj.TypeId >= common_def.HI1880_SP186_M_16i and
            controller_obj.TypeId <= common_def.HI1880_SP186_M_8i) then
        return true
    end
    return false
end

-- 检查创建逻辑盘的参数
function param_check:check_ld_params(controller_id, name, capacity, capacity_unit, strip_size,
    read_policy, write_policy, io_policy, access_policy, disk_cache_policy, init_type)
    if not self:check_ld_capacity(capacity, capacity_unit) then
        return common_def.SM_CODE_CAPACITY_OUT_OF_RANGE
    end
    if not self:check_ld_strip_size(controller_id, strip_size) then
        return common_def.SM_CODE_STRIP_SIZE_OUT_OF_RANGE
    end
    if not self:check_ld_policy(controller_id, 'ReadPolicy', read_policy) then
        return common_def.SM_CODE_READ_POLCIY_OUT_OF_RANGE
    end
    if not self:check_ld_policy(controller_id, 'WritePolicy', write_policy) then
        return common_def.SM_CODE_WRITE_POLICY_OUT_OF_RANGE
    end
    if not self:check_ld_policy(controller_id, 'IOPolicy', io_policy) then
        return common_def.SM_CODE_IO_POLICY_OUT_OF_RANGE
    end
    if not self:check_ld_policy(controller_id, 'AccessPolicy', access_policy) then
        return common_def.SM_CODE_ACCESS_POLICY_OUT_OF_RANGE
    end
    if not self:check_ld_policy(controller_id, 'DriveCachePolicy', disk_cache_policy) then
        return common_def.SM_CODE_DISK_CAHCE_POLICY_OUT_OF_RANGE
    end
    if not self:check_ld_init_type(init_type) then
        return common_def.SM_CODE_INIT_TYPE_OUT_OF_RANGE
    end
    local controller_obj = controller_collection:get_by_controller_id(controller_id)
    local name_retval = method_misc:check_volume_name(name, controller_obj.TypeId)
    if name_retval ~= common_def.SM_CODE_SUCCESS then
        return name_retval
    end
    return common_def.SM_CODE_SUCCESS
end

function param_check:check_ld_capacity(capacity, capacity_unit)
    if capacity ~= common_def.STORAGE_INFO_INVALID_DWORD then
        if (capacity_unit == common_def.SML_LD_CAPACITY_UNIT_GB and capacity >= 0x400000) or
           (capacity_unit == common_def.SML_LD_CAPACITY_UNIT_TB and capacity >= 0x1000) or
           (capacity_unit ~= common_def.SML_LD_CAPACITY_UNIT_MB and
            capacity_unit ~= common_def.SML_LD_CAPACITY_UNIT_GB and
            capacity_unit ~= common_def.SML_LD_CAPACITY_UNIT_TB) or
           (capacity == 0) then
            return false
        end
    end
    return true
end

function param_check:check_ld_strip_size(controller_id, strip_size_info)
    local LD_STRIP_SIZE<const> = {
        [0] = 512,
        [1] = 1024,
        [2] = 2048,
        [3] = 4096,
        [4] = 8192,
        [5] = 16384,
        [6] = 32768,
        [7] = 65536,
        [8] = 131072,
        [9] = 262144,
        [10] = 524288,
        [11] = 1048576
    }
    local controller_obj = controller_collection:get_by_controller_id(controller_id)
    local strip_size = LD_STRIP_SIZE[strip_size_info]
    if strip_size_info ~= common_def.STORAGE_INFO_INVALID_BYTE then
        if controller_obj.MinStripSizeBytes ~= common_def.STORAGE_INFO_INVALID_DWORD and
           controller_obj.MaxStripSizeBytes ~= common_def.STORAGE_INFO_INVALID_DWORD then 
            return strip_size >= controller_obj.MinStripSizeBytes and
                   strip_size <= controller_obj.MaxStripSizeBytes
        end
    end
    return true
end

function param_check:check_ld_policy(controller_id, policy_name, policy)
    if policy == common_def.STORAGE_INFO_INVALID_BYTE then
        return true
    end
    local controller_obj = controller_collection:get_by_controller_id(controller_id)
    local switch_policy = {
        ['ReadPolicy'] = {common_def.READ_POLICY_LIST, controller_obj.ReadPolicyWritable,
                          controller_obj.DefaultReadPolicy, controller_obj.SupportedReadPolicyList,
                          {common_def.VENDER_PMC}},
        ['WritePolicy'] = {common_def.WRITE_PLOICY_LIST, controller_obj.WritePolicyWritable,
                          controller_obj.DefaultWritePolicy, controller_obj.SupportedWritePolicyList, {}},
        ['IOPolicy'] = {common_def.IO_PLOICY_LIST, controller_obj.IOPolicyWritable,
                          controller_obj.DefaultIOPolicy, controller_obj.SupportedIOPolicyList, {}},
        ['AccessPolicy'] = {common_def.ACCESS_POLICY_LIST, controller_obj.AccessPolicyWritable,
                          controller_obj.DefaultAccessPolicy, controller_obj.SupportedAccessPolicyList, {}},
        ['DriveCachePolicy'] = {common_def.DRIVE_CACHE_POLICY_LIST, controller_obj.DriveCachePolicyWritable,
                          controller_obj.DefaultDriveCachePolicy, controller_obj.SupportedDriveCachePolicyList,
                          {common_def.VENDER_PMC}},
    }
    local policy_info = switch_policy[policy_name]
    for _, v in ipairs(policy_info[5]) do
        if self:test_controller_vendor_by_ctrl_id(controller_id, v) then
            return true
        end
    end
    -- 检查参数选项是否预设支持
    local policy_s = policy_info[1][policy]
    if not policy_info[2] then
        return policy_s == policy_info[3]
    end
    for _, v in pairs(policy_info[4]) do
        if policy_s == v then
            return true
        end
    end
    return false
end

function param_check:check_ld_init_type(init_type)
    return init_type < common_def.LD_INIT_STATE_BUTT or init_type == common_def.STORAGE_INFO_INVALID_BYTE
end

-- 转换capacity
function param_check:convert_ld_capacity_in_array(capacity, capacity_unit)
    if capacity == common_def.STORAGE_INFO_INVALID_DWORD then
        return capacity
    else 
        return self:convert_ld_capacity_to_megabytes(capacity, capacity_unit)
    end
end

function param_check:convert_ld_capacity_to_megabytes(capacity, capacity_unit)
    if capacity_unit == common_def.SML_LD_CAPACITY_UNIT_GB then
        return capacity * 1024
    elseif capacity_unit == common_def.SML_LD_CAPACITY_UNIT_TB then
        return capacity * 1024 * 1024
    else
        return capacity
    end
end

-- 检查选择的硬盘是否满足条件
function param_check:check_drive_list(controller_id, drive_lists)
    local drive_list = {string.byte(drive_lists, 1, #drive_lists)}
    local block_size = 0
    local drive_obj = {}
    for k, v in pairs(drive_list) do
        drive_obj = drive_collection:get_drive_by_id(controller_id, v)
        if block_size == 0 then
            block_size = drive_obj.BlockSizeBytes
        else
            if block_size ~= drive_obj.BlockSizeBytes then
                return common_def.SML_ERR_CONFIG_BLOCK_SIZE_NOT_SAME
            end
        end
    end
    return common_def.SM_CODE_SUCCESS
end

-- 检查在新阵列上创建逻辑盘参数
function param_check:check_ld_data_created_in_new_array
    (controller_id, drive_lists, raid_type, span_depth, name, capacity, capacity_unit, strip_size,
    read_policy, write_policy, io_policy, access_policy, disk_cache_policy, init_type)
    local convert_span_depth = self:check_and_get_ld_span_depth(controller_id, raid_type, #drive_lists, span_depth)
    if not convert_span_depth then
        return common_def.SM_CODE_INVALID_RAID_LEVEL
    end
    if not self:check_ld_raid_level_and_span_depth(raid_type, convert_span_depth) then
        return common_def.SM_CODE_INVALID_SPAN_DEPTH
    end
    if not self:check_drive_count(controller_id, raid_type, convert_span_depth, #drive_lists) then
        return common_def.SM_CODE_INVALID_PD_COUNT
    end
    local check_ld_params_retval = self:check_ld_params(controller_id, name, capacity, capacity_unit, strip_size,
    read_policy, write_policy, io_policy, access_policy, disk_cache_policy, init_type)
    if check_ld_params_retval ~= common_def.SM_CODE_SUCCESS then
        return check_ld_params_retval
    end

    if raid_type ~= common_def.RAID_LEVEL_0 then
        return self:check_drive_list(controller_id, drive_lists)
    end
    return common_def.SM_CODE_SUCCESS
end

function param_check:check_and_get_ld_span_depth(controller_id, raid_type, drive_count, span_depth)
    local MIN_SPAN_DEPTH = 2
    local switch = {
        [common_def.RAID_LEVEL_0] = function() return 1 end,
        [common_def.RAID_LEVEL_1] = function() return 1 end,
        [common_def.RAID_LEVEL_5] = function() return 1 end,
        [common_def.RAID_LEVEL_6] = function() return 1 end,
        [common_def.RAID_LEVEL_1ADM] = function() return 1 end,
        [common_def.RAID_LEVEL_1TRIPLE] = function() return 1 end,
        [common_def.RAID_LEVEL_10] = function()
            if self:test_controller_vendor_by_ctrl_id(controller_id, common_def.VENDER_PMC) then
                return math.max(drive_count // 2, MIN_SPAN_DEPTH)
            else
                return 2
            end
        end,
        [common_def.RAID_LEVEL_50] = function() return 2 end,
        [common_def.RAID_LEVEL_60] = function() return 2 end,
        [common_def.RAID_LEVEL_10ADM] = function() 
            return math.max(drive_count // 3, MIN_SPAN_DEPTH)
        end,
        [common_def.RAID_LEVEL_10TRIPLE] = function()
            return math.max(drive_count // 3, MIN_SPAN_DEPTH)
        end
    }
    if span_depth == common_def.STORAGE_INFO_INVALID_BYTE then
        if switch[raid_type] then
            return switch[raid_type]()
        else
            return nil
        end
    end
    return span_depth
end

function param_check:check_ld_raid_level_and_span_depth(raid_type, span_depth)
    if ((raid_type == common_def.RAID_LEVEL_0 or
         raid_type == common_def.RAID_LEVEL_1 or
         raid_type == common_def.RAID_LEVEL_5 or
         raid_type == common_def.RAID_LEVEL_6 or
         raid_type == common_def.RAID_LEVEL_1ADM or
         raid_type == common_def.RAID_LEVEL_1TRIPLE) and
         span_depth ~= 1) or
       ((raid_type == common_def.RAID_LEVEL_10 or
         raid_type == common_def.RAID_LEVEL_50 or
         raid_type == common_def.RAID_LEVEL_60 or
         raid_type == common_def.RAID_LEVEL_10ADM or
         raid_type == common_def.RAID_LEVEL_10TRIPLE) and
         span_depth < 2) then
        return false
    end
    return true
end

function param_check:check_drive_count(controller_id, raid_type, span_depth, drive_count)
    if span_depth > 1 and (drive_count <= span_depth or drive_count % span_depth ~= 0) then
        return false
    end
    if raid_type == common_def.RAID_LEVEL_1ADM or raid_type == common_def.RAID_LEVEL_1TRIPLE then
        if drive_count ~= 3 then
            return false
        end
    end
    if raid_type == common_def.RAID_LEVEL_10ADM or raid_type == common_def.RAID_LEVEL_10TRIPLE then
        if drive_count < 6 or drive_count % 3 ~= 0 then
            return false
        end
    end
    if raid_type == common_def.RAID_LEVEL_5 and drive_count < 3 then
        return false
    end
    if raid_type == common_def.RAID_LEVEL_50 and drive_count < 6 then
        return false
    end
    return self:check_drive_count_by_ctrl(controller_id, raid_type, drive_count)
end

function param_check:check_raid1_drive_count(controller_id, drive_count)
    if self:test_controller_vendor_by_ctrl_id(controller_id, common_def.VENDER_PMC) then
        if drive_count ~= 2 then
            return false
        end
    elseif drive_count % 2 ~= 0 then
        return false
    end
    return true
end

function param_check:check_raid6_drive_count(controller_id, drive_count)
    local controller_obj = controller_collection:get_by_controller_id(controller_id)
    if self:test_controller_vendor_by_ctrl_id(controller_id, common_def.VENDER_PMC) or
        controller_obj.TypeId == common_def.LSI_3908_WITH_MR or
        controller_obj.TypeId == common_def.LSI_3916_WITH_MR then
        if drive_count < 4 then
            return false
        end
    elseif drive_count < 3 then
        return false
    end
    return true
end

function param_check:check_raid60_drive_count(controller_id, drive_count)
    local controller_obj = controller_collection:get_by_controller_id(controller_id)
    if self:test_controller_vendor_by_ctrl_id(controller_id, common_def.VENDER_PMC) or
        controller_obj.TypeId == common_def.LSI_3908_WITH_MR or
        controller_obj.TypeId == common_def.LSI_3916_WITH_MR then
        if drive_count < 8 then
            return false
        end
    elseif drive_count < 6 then
        return false
    end
    return true
end

function param_check:check_drive_count_by_ctrl(controller_id, raid_type, drive_count)
    if raid_type == common_def.RAID_LEVEL_1 then
        return self:check_raid1_drive_count(controller_id, drive_count)
    end
    if raid_type == common_def.RAID_LEVEL_6 then
        return self:check_raid6_drive_count(controller_id, drive_count)
    end
    if raid_type == common_def.RAID_LEVEL_60 then
        return self:check_raid60_drive_count(controller_id, drive_count)
    end
    return true
end

-- 将drive_id列表转换为pd_id列表
function param_check:convert_id_list_from_drive_to_pd(ref_ctrl_id, drive_lists)
    local trans_list = {string.byte(drive_lists, 1, #drive_lists)}
    for k, v in pairs(trans_list) do
        local drive_obj = drive_collection:get_drive_by_id(ref_ctrl_id, v)
        trans_list[k] = drive_obj.device_id
    end
    return trans_list
end

function param_check:check_ld_data_created_ssd_caching(controller_id, raid_type, drive_count, write_policy, name)
    local convert_span_depth = self:check_and_get_ld_span_depth(controller_id,
        raid_type, drive_count, common_def.STORAGE_INFO_INVALID_BYTE)
    if not convert_span_depth then
        return common_def.SM_CODE_INVALID_RAID_LEVEL
    end
    if not self:check_drive_count(controller_id, raid_type, convert_span_depth, drive_count) then
        return common_def.SM_CODE_INVALID_PD_COUNT
    end
    if not self:check_ld_policy(controller_id, 'WritePolicy', write_policy) then
        return common_def.SM_CODE_WRITE_POLICY_OUT_OF_RANGE
    end
    local controller_obj = controller_collection:get_by_controller_id(controller_id)
    local name_retval = method_misc:check_volume_name(name, controller_obj.TypeId)
    if name_retval ~= common_def.SM_CODE_SUCCESS then
        return name_retval
    end
    return common_def.SM_CODE_SUCCESS
end

return singleton(param_check)