-- 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 singleton = require 'mc.singleton'
local class = require 'mc.class'
local signal = require 'mc.signal'
local log = require 'mc.logging'
local context = require 'mc.context'
local c_initiator = require 'mc.initiator'

local c_object = require 'object_manager.object'
local common_def = require 'common_def'
local STORAGE_MGNT = common_def.STORAGE_MGNT_STATE
local c_tasks = require 'tasks'
local sml = require 'sml'
local skynet = require 'skynet'

local TASK_ALL_CTRL_LOADED <const> = 'all_ctrl_loaded'

local c_storageconfig_service = class()
local c_storageconfig_object = c_object('StorageConfig', {'position'})

function c_storageconfig_service:ctor(storage_app)
    self.bus = storage_app.bus
    self.db = storage_app.db
    self.power_on = 0
    self.smbios_status = 0
    self.power_state = ''
    self.obj = nil
    self.configuration_ready_mask = 0
    self.controller_mask = 0
    self.on_update_flag = signal.new()
    self.percentage_update_signal = signal.new()
    self.ctrl_no_oob = {}  -- 不支持带外管理的卡，必须是默认配置的
    self.ctrl_no_3004 = {}  --非3004（支持带外管理）的卡
    self.ctrl_3004 = {}  --3004（支持带外管理）的卡
    self.last_ctrl_count = 0
    self.ctrl_count = 0
    self.all_ctrl_loaded = false
    self.task = nil
    self.need_sync = false  -- 当服务更新时，可能存在StorageConfig对象尚未加载的可能
    self.storageconfig_info = {
        StorageConfigReady = STORAGE_MGNT.STORAGE_MGNT_NOT_READY
    }
    self.ctrl_index_list = {}
end

function c_storageconfig_service:change_smbios_status(status)
    self.smbios_status = status
end

function c_storageconfig_service:get_config_table()
    local db_info = self.db.GlobalStorageConfigTable({Id = "GlobalStorageConfigTable"})
    if db_info then
        return db_info
    end
    self.db.GlobalStorageConfigTable({
        Id = "GlobalStorageConfigTable", DiskPartitionUsagePercentageThreshold = 100
    }):save()
    return self.db.GlobalStorageConfigTable({Id = "GlobalStorageConfigTable"})
end

function c_storageconfig_service:persistence_disk_partition(partition)
    pcall(function()
        local db_info = self:get_config_table()
        db_info.DiskPartitionUsagePercentageThreshold = partition
        db_info:save()
        log:notice('[storage] set disk partition threshold(%s) to db', partition)
    end)
end

function c_storageconfig_service:update_disk_threshold_from_db(obj)
    pcall(function()
        local db_info = self:get_config_table()
        obj.DiskPartitionUsagePercentageThreshold = db_info.DiskPartitionUsagePercentageThreshold
        log:notice('[storage] update disk partition threshold(%s) from db', obj.DiskPartitionUsagePercentageThreshold)
    end)
end

function c_storageconfig_service:on_add_object(storageconfig_object)
    log:info('c_storageconfig_object.on_add_object')
    if not self.obj then
        self.obj = storageconfig_object
        self:update_disk_threshold_from_db(self.obj)
        self.percentage_update_signal:emit(storageconfig_object.DiskPartitionUsagePercentageThreshold)
        self:update_object_info()
        storageconfig_object.property_changed:on(function(name, value, sender)
            if name ~= 'DiskPartitionUsagePercentageThreshold' then
                return
            end
            local ctx = context.get_context()
            local fmt = 'Set hard disk partition utilization threshold to (%d) successfully'
            local initiator = ctx and ctx:get_initiator() or c_initiator.new('N/A', 'N/A', 'N/A')
            log:operation(initiator, 'storage', fmt, value)
            self:persistence_disk_partition(value)
            self.percentage_update_signal:emit(value) -- 阈值变更触发告警检测
        end)
    end
end

function c_storageconfig_service:init()
    c_storageconfig_object.on_add_object:on(function(storageconfig_object)
        self:on_add_object(storageconfig_object)
    end)

    c_storageconfig_object.on_delete_object:on(function()
        if self.obj then
            self.obj = nil
        end
    end)
    self.on_update_flag:on(function(value)
        local obj
        if not self.obj then
            self.need_sync = true
            obj = self.storageconfig_info
            log:error('No c_storageconfig_object')
        else
            self.need_sync = false
            obj = self.obj
        end
        if self.power_on == 0 then
            obj.StorageConfigReady = STORAGE_MGNT.STORAGE_MGNT_NOT_READY
            return
        end
        if self.controller_mask == 0 then
            obj.StorageConfigReady = STORAGE_MGNT.STORAGE_MGNT_NO_CTRLS
            return
        end
        if value == STORAGE_MGNT.STORAGE_MGNT_OOB_NOT_SUPPORT then
            if obj.StorageConfigReady ~= value then
                obj.StorageConfigReady = value
            end
            return
        end
        if self.configuration_ready_mask ~= self.controller_mask then
            if obj.StorageConfigReady ~= STORAGE_MGNT.STORAGE_MGNT_OOB_NOT_SUPPORT then
                obj.StorageConfigReady = STORAGE_MGNT.STORAGE_MGNT_CTRL_ABNOMAL
                return
            end
            return
        end

        if obj.StorageConfigReady ~= value then
            obj.StorageConfigReady = value
        end
    end)
end

function c_storageconfig_service:update_object_info()
    if not self.need_sync then
        return
    end
    self.obj.StorageConfigReady = self.storageconfig_info.StorageConfigReady
end

function c_storageconfig_service:change_power_state(state)
    self.power_on = state
end

function c_storageconfig_service:change_smbios_status(status)
    self.smbios_status = status
end

function c_storageconfig_service:ctrl_count_changed()
    local changed = (self.ctrl_count ~= self.last_ctrl_count)
    self.last_ctrl_count = self.ctrl_count
    return changed
end

function c_storageconfig_service:start_ctrl_loaded_check()
    if self.task then
        return
    end

    -- 间隔2秒，连续15次查询控制器数量是否变化。如果不再变化，说明控制器加载完成
    local retry_count = 0
    self.task = c_tasks.get_instance():new_task(TASK_ALL_CTRL_LOADED):loop(function(task)
        if self:ctrl_count_changed() then
            retry_count = 0
        else
            retry_count = retry_count + 1
        end

        if retry_count == 15 then
            if self.ctrl_count > 0 then
                self.all_ctrl_loaded = true
            end
            task:stop()
            self.task = nil
        end
    end):set_timeout_ms(2000)
end

function c_storageconfig_service:add_or_del_controller(obj, value)
    if value then
        self.ctrl_count = self.ctrl_count + 1
        table.insert(self.ctrl_index_list, obj.Id)
        self:start_ctrl_loaded_check()
    else
        self.ctrl_count = self.ctrl_count - 1
    end
    if obj.OOBSupport ~= 1 then  -- 这里的不支持带外管理必须是默认配置的，而不是运行过程中动态修改的
        self.ctrl_no_oob[obj.Id] = value
    elseif obj.TypeId == common_def.LSI_3004_WITH_IMR then
        self.ctrl_3004[obj.Id] = value
    else
        self.ctrl_no_3004[obj.Id] = value
    end
end

-- 组件整体的维度，非单个控制器的维度
function c_storageconfig_service:support_oob()
    -- 如果存在不支持带外管理的卡
    if next(self.ctrl_no_oob) then
        -- 并且存在非3004（支持带外管理）的卡，认为整体不支持
        if next(self.ctrl_no_3004) then
            return false
        -- 并且只存在3004（支持带外管理）的卡，认为整体支持
        elseif next(self.ctrl_3004) then
            return true
        -- 并且支持带外管理的卡都不存在，认为整体不支持
        else
            return false  
        end
    -- 如果只存在支持带外管理的卡，认为整体支持
    elseif next(self.ctrl_no_3004) or next(self.ctrl_3004) then
        return true
    -- 如果不存在卡，认为整体不支持
    else
        return false
    end
end

-- 获取当前状态是否已支持创建逻辑盘或修改配置的标志
function c_storageconfig_service:get_set_configuration_ready_flag(ctrl_id)
    -- 获取全局状态，只要有一张卡完成信息更新就可以开始配置
    if ctrl_id == common_def.INVALID_U8 then
        return self.configuration_ready_mask ~= 0
    end
    -- 获取某张卡是否完成更新满足配置条件  
    if ctrl_id >= common_def.SML_MAX_RAID_CONTROLLER then
        return false
    end

    local tmp_val = (1 << ctrl_id) & self.configuration_ready_mask;

    return tmp_val ~= 0
end

function c_storageconfig_service:generate_abnormal_volume_exsits(assert)
    local value = assert and 1 or 0
    if self.obj then
        self.obj.VolumesStateAbnormal = value
    end
end

function c_storageconfig_service:clear_all_controller_info()
    skynet.fork(function()
        log:notice('begin clear_all_controller_info')
        local ok, ret = pcall(function()
            return sml.clear_all_controller_info(self.ctrl_index_list[1] or 0)
        end)
 
        log:notice('clear all controller info %s, ret is %s', ok and 'successfully' or 'failed', ret)
    end)
end

return singleton(c_storageconfig_service)
