-- 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 signal = require 'mc.signal'
local log = require 'mc.logging'
local sml = require 'sml'
local c_object = require 'mc.orm.object'
local common_def = require 'common_def'
local ctrl_commu_loss_monitor = require 'ctrl_commu_loss_monitor'
local c_storageconfig = require 'storageconfig.storageconfig_object'
local volume_dump = require 'volume.volume_dump'
local c_drive = require 'drive.drive_object'

-- 逻辑盘条带大小，0=512, 1=1K, 2=2K, 3=4K, 7=64K, 8=128K，9=256K，10=512K，11=1MB, etc.
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 TASK_UPDATE <const> = 'update'

local c_volume = c_object('Volume')
local function make_volume_key(controller_id, volume_id)
    return string.format('%d:%d', controller_id, volume_id)
end

function c_volume:ctor(obj, position)
    self.obj = obj
    self.RefController = position
    self.VolumeId = obj.Id
    self.key = make_volume_key(self.RefController, obj.Id)
    self.on_update = signal.new()
    self.on_update_spare_drive_list = signal.new()
    self.on_update_ref_drives = signal.new()
    self.on_update_array_by_volume = signal.new()
    self.array_list = {}
    self.optimal_drive_list = {}  -- 正常状态下的成员盘列表
end

function c_volume:init()
    c_volume.super.init(self)
    self.SpanCount = common_def.INVALID_U8
    -- rpc也会发送信息，所以必须保留
    self.on_update:on(function(volume_info)
        self:update_volume_basic_info(volume_info)
        self:update_volume_cache_info(volume_info)
    end)
end

-- 因为对象会被删除，不需要enbale_request来限制
function c_volume:get_volume_info()
    local ok, ret = pcall(sml.get_ctrl_init_state, self.RefController)
    if not ok or ret ~= 2 then
        return
    end
    ok, ret = pcall(sml.get_ld_info, { Priority = 'Secondary' }, self.RefController, self.Id)
    if not ok then
        if ret ~= common_def.SML_ERR_CTRL_STATUS_INVALID and ret ~= common_def.SML_ERR_NULL_INFTERFACE then
            ctrl_commu_loss_monitor.get_instance():update(true, self.RefController)
        end
        return
    end
    ctrl_commu_loss_monitor.get_instance():update(false, self.RefController)
    self.on_update:emit(ret)
end

function c_volume:update_volume_basic_info(volume_info)
    -- 支持获取逻辑盘的名称
    self.VolumeName = volume_info.name
    -- 支持获取逻辑盘的状态

    if self.State ~= volume_info.drive_state then
        log:notice('Volume:%s change state from %s to %s', self.Id, self.State, volume_info.drive_state)
        self:check_drive_alarm(volume_info.drive_state)
        self.State = volume_info.drive_state
        self:check_abnormal_volume_exists()
    end
    -- 支持获取逻辑盘的Raid级别
    self.RAIDType = volume_info.raid_level
    -- 支持获取逻辑盘的默认读策略
    self.DefaultReadPolicy = common_def.READ_POLICY_LIST[volume_info.default_read_policy]
    -- 支持获取逻辑盘的默认写策略
    self.DefaultWritePolicy = common_def.WRITE_PLOICY_LIST[volume_info.default_write_policy]
    -- 支持获取逻辑盘当前读策略
    self.CurrentReadPolicy = common_def.READ_POLICY_LIST[volume_info.current_read_policy]
    -- 支持获取逻辑盘当前写策略
    self.CurrentWritePolicy = common_def.WRITE_PLOICY_LIST[volume_info.current_write_policy]
    -- 支持获取逻辑盘的容量
    self.CapacityBytes = volume_info.size * 1024 *1024
    -- 支持获取逻辑盘的硬盘缓存策略
    self.DriveCachePolicy = volume_info.disk_cache_policy
    -- 支持获取逻辑盘的条带大小，V2自定义转换到V3的redfish标准
    local strip_size_info = volume_info.strip_size
    self.OptimumIOSizeBytes = LD_STRIP_SIZE[strip_size_info]

    -- 支持获取逻辑盘的最大可设置容量
    self.MaxResizableSizeBytes = volume_info.max_resizeable_size == common_def.INVALID_U32 and
        volume_info.max_resizeable_size or (volume_info.max_resizeable_size * 1024 * 1024)
    -- 支持获取逻辑盘的加速方法
    self.AccelerationMethod = common_def.LD_ACCELERATION_METHOD_LIST[volume_info.accelerator]
    -- 支持获取逻辑盘缓存行大小
    self.CacheLineSizeKiB = volume_info.cache_line_size
    -- 支持获取启动优先级
    self.BootPriority = volume_info.boot_priority
    -- 支持获取是否为启动盘
    self.BootEnable = volume_info.bootable
    -- 支持支持获取逻辑盘的BGI使能状态
    self.BGIEnable = volume_info.bgi_enabled
    -- 支持获取逻辑盘的一致性检查状态
    self.ConsistencyCheck = volume_info.consistent_check
    -- 支持获取逻辑盘的访问策略
    self.AccessPolicy = common_def.ACCESS_POLICY_LIST[volume_info.access_policy]
    -- 支持获取逻辑盘的初始化方式
    self.InitializationMode = volume_info.init_state
    -- 支持获取逻辑盘的初始化状态
    self.CurrentForegroundInitState = volume_info.current_fgi_state
    -- 支持获取逻辑盘的初始化进度
    self.ForegroundInitProgress = volume_info.fgi
    -- 指定驱动器阵列中每个Span的成员的个数
    self.NumDrivePerSpan = volume_info.num_drive_per_span
    self.SpanCount = volume_info.span_depth
    -- 支持获取逻辑盘的重构状态
    self.RebuildState = volume_info.progress_info.rebuild_state

    -- 支持获取逻辑盘的重构进度
    self.RebuildProgress = volume_info.progress_info.rebuild_progress

    -- 支持获取逻辑盘的关联硬盘阵列
    self.RefDiskArrayList = volume_info.ld_ref_array
    -- 支持获取逻辑盘的热备盘列表
    self.on_update_spare_drive_list:emit(self, volume_info.spare_pd_ids)
    self.on_update_ref_drives:emit(self)
    self.on_update_array_by_volume:emit(self)
end

local CACHE_POLICY_LIST = {'CachedIO', 'DirectIO'}
function c_volume:update_volume_cache_info(volume_info)
    -- 支持获取逻辑盘是否是CacheCade逻辑盘
    self.SSDCachecadeVolume = volume_info.is_sscd
    -- -- 支持获取逻辑盘是否启用CacheCade
    local ok, ret = pcall(sml.get_ld_sscd_caching_enable, self.RefController, self.VolumeId)
    if ok then
        self.SSDCachingEnable = ret
    end
    -- -- 支持获取CacheCade逻辑盘关联的普通逻辑盘
    if self.SSDCachecadeVolume == 1 then
        ok, ret = pcall(sml.get_sscd_associated_ld_list, self.RefController, self.VolumeId)
        if ok then
            self.AssociatedVolumes = ret
        end
    end
    -- -- 支持获取逻辑盘关联的CacheCade逻辑盘
    if self.SSDCachingEnable == 1 then
        ok, ret = pcall(sml.get_ld_associated_sscd_list, self.RefController, self.VolumeId)
        if ok then
            self.AssociatedCacheCadeVolume = ret
        end
    end
    -- 支持获取逻辑盘的默认cache策略
    self.DefaultCachePolicy = CACHE_POLICY_LIST[volume_info.default_cache_policy + 1]
    -- 支持获取逻辑盘的当前cache策略
    self.CurrentCachePolicy = CACHE_POLICY_LIST[volume_info.current_cache_policy + 1]
end

function c_volume:is_abnormal()
    if self.State == common_def.LD_STATE.PARTIALLY_DEGRADED or
        self.State == common_def.LD_STATE.DEGRADED or
        self.State == common_def.LD_STATE.OFFLINE or
        self.State == common_def.LD_STATE.FAILED or
        self.State == common_def.LD_STATE.UNSUPPORTED_ON_THIS_CONTROLLER or
        self.State == common_def.LD_STATE.DRVIE_IMPROPERLY_CONNECTED or
        self.State == common_def.LD_STATE.INTERIM_RECOVERY or
        self.State == common_def.LD_STATE.WRONG_DRIVE_REPLACED then
        return true
    else
        return false
    end
end

function c_volume:check_abnormal_volume_exists()
    local assert = self.collection:fold(function(acc, obj, key)
        if obj:is_abnormal() then
            return true, true  -- 只要有1个异常的逻辑盘，就上报告警
        else
            return false
        end
    end)
    c_storageconfig.get_instance():generate_abnormal_volume_exsits(assert)
end

-- 逻辑盘热备重构完成后,消除告警
function c_volume:check_drive_alarm(cur_state)
    if self.State ~= common_def.LD_STATE.DEGRADED or cur_state ~= common_def.LD_STATE.OPTIMAL then
        return
    end
    c_drive.collection:fold(function(acc, obj, key)
        for _, volume_idx in pairs(obj.RefVolumeList) do
            if volume_idx == self.Id and obj.in_degraded_array then
                obj:generate_in_failed_array(false, 'OOB', common_def.LD_STATE.DEGRADED)
            end
        end
    end, {})
end

function c_volume:dump_info(fp_w)
    volume_dump.get_instance():dump_info(fp_w, self)
end

return c_volume
