-- 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 class = require 'mc.class'
local singleton = require 'mc.singleton'
local method_misc = require 'method_misc'
local common_def = require 'common_def'
local error_engine = require 'error_engine'
local sml = require 'sml'
local log = require 'mc.logging'
local sync_task = require 'sync_task'
local task_prop = require 'mc.mdb.task_mgmt'
local skynet = require 'skynet'
local tasks = require 'tasks'

local controller_collection = require 'controller.controller_collection'
local volume_collection = require 'volume.volume_collection'
local volume_param_check = require 'volume.volume_param_check'

local task_state<const> = task_prop.state
local task_status<const> = task_prop.status
local SM_CODE_E<const> = error_engine.SM_CODE_E
local SML_ERR_CODE_E<const> = error_engine.SML_ERR_CODE_E
local VOLUME_QUICK_INIT_TYPE<const> = 1

local rpc_service_volume = class()

function rpc_service_volume:ctor(bus)
    self.bus = bus
end

function rpc_service_volume:volume_operate(operate_name, controller_id, volume_id, ctx, ...)
    local operations = {
        ["SetBootable"] = self.set_volume_bootable,
        ["SetName"] = self.set_volume_name,
        ["SetReadPolicy"] = self.set_volume_readpolicy,
        ["SetWritePolicy"] = self.set_volume_writepolicy,
        ["SetIOPolicy"] = self.set_volume_iopolicy,
        ["SetBGIEnable"] = self.set_volume_bgi_enable,
        ["SetAccessPolicy"] = self.set_volume_accesspolicy,
        ["SetDiskCachePolicy"] = self.set_volume_diskcachepolicy,
        ["StartFGI"] = self.start_volume_fgi,
        ["CancelFGI"] = self.cancel_volume_fgi,
        ["SetCachecadeEnable"] = self.set_volume_cache_enable,
        ["SetAccelerator"] = self.set_volume_accelerator,
        ["SetCapacitySize"] = self.set_volume_capacitysize,
        ["SetStripSize"] = self.set_volume_stripsize
    }
    log:notice('[Storage]Start %s of Volume%d(Controller%d)', operate_name, volume_id, controller_id)

    local controller_obj = controller_collection:get_by_controller_id(controller_id)
    if not controller_obj then
        error_engine.raise_volume_error(ctx, SML_ERR_CODE_E.SML_ERR_CTRL_INDEX_INVALID)
    end

    local volume_obj = volume_collection.get_instance():get_ctrl_volume_by_id(controller_id, volume_id)
    if not volume_obj then
        error_engine.raise_volume_error(ctx, SML_ERR_CODE_E.SML_ERR_LD_INVALID_TARGET_ID)
    end

    local ok, ret = pcall(operations[operate_name], controller_obj, volume_id, ctx, ...)
    if not ok and ret then
        if ret[1] ~= common_def.SML_ERR_REBOOT_REQUIRED then
            log:error('[Storage]Failed to %s, and return: %s', operate_name, ret[1])
        else
            log:notice('[Storage]Successfully %s of Volume%d(Controller%d), please restart OS.',
                operate_name, volume_id, controller_id)
        end
        error_engine.raise_volume_error(ctx, ret[1], ret[2], ret[3], ret[4])
    else
        log:notice('[Storage]Successfully %s of Volume%d(Controller%d)', operate_name, volume_id, controller_id)
    end

    -- 设置成功后刷新资源树上的信息
    ok, ret = pcall(sml.get_ld_info, {Priority = 'High'}, volume_obj.RefController, volume_obj.Id)
    if ok then
        volume_obj.on_update:emit(ret)
    end
end

local function volume_log_operation(ctx, obj, volume_id, ret, prop_name, prop_val)
    local log_operation_ret = 'successfully'
    if ret ~= common_def.RET_OK and ret ~= common_def.SML_ERR_REBOOT_REQUIRED then
        log_operation_ret = 'failed'
    end
    if ctx and ctx.get_initiator then
        log:operation(ctx:get_initiator(), 'storage',
            'Set RAID controller %d logical drive %d %s to %s %s', obj.Id,
            volume_id, prop_name, prop_val, log_operation_ret)
    end
end

-- 设置启动优先级
function rpc_service_volume.set_volume_bootable(controller_obj, volume_id, ctx, priority)
    if method_misc:check_boot_priority_param(priority, controller_obj.TypeId) ~= common_def.SM_CODE_SUCCESS then
        log:error("[Storage]BootPriority value invalid")
        error({common_def.SM_CODE_PARA_DATA_ILLEGAL, '%BootPriority', tostring(priority)})
    end
    local retval = sml.set_ld_boot_priority(controller_obj.Id, volume_id, priority)
    local boot_str = method_misc:get_boot_priority_op_desc(priority, controller_obj.TypeId)
    if retval ~= common_def.SM_CODE_SUCCESS then
        if ctx and ctx.get_initiator then
            log:operation(ctx:get_initiator(), 'storage', 'Failed to set RAID controller %d logical drive %u %s',
                controller_obj.Id, volume_id, boot_str)
        end
        error({retval, '%BootPriority', tostring(priority)})
    end
    if ctx and ctx.get_initiator then
        log:operation(ctx:get_initiator(), 'storage', 'Set RAID controller %d logical drive %u %s successfully',
            controller_obj.Id, volume_id, boot_str)
    end
end

-- 设置读策略
function rpc_service_volume.set_volume_readpolicy(controller_obj, volume_id, ctx, readpolicy)
    local retval = sml.set_ld_readpolicy(controller_obj.Id, volume_id, readpolicy)
    local rp_str = common_def.LD_RP_STR[readpolicy] or 'Unknown'
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'Read Policy', rp_str)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%ReadPolicy', tostring(readpolicy)})
    end
end

-- 设置写策略
function rpc_service_volume.set_volume_writepolicy(controller_obj, volume_id, ctx, writepolicy)
    if not writepolicy or writepolicy > common_def.LD_CACHE_WRITE_CACHE_IF_BAD_BBU then
        log:error('[Storage]WritePolicy value:%s is invalid', writepolicy)
        error({common_def.SM_CODE_PARA_DATA_ILLEGAL, '%WritePolicy', tostring(writepolicy)})
    end
    local retval = sml.set_ld_writepolicy(controller_obj.Id, volume_id, writepolicy)
    local wp_str = common_def.LD_WP_STR[writepolicy] or 'Unknown'
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'Write Policy', wp_str)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%WritePolicy', tostring(writepolicy)})
    end
end

-- 设置名称
function rpc_service_volume.set_volume_name(controller_obj, volume_id, ctx, name)
    local retval = method_misc:check_volume_name(name, controller_obj.TypeId)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%Name', name, method_misc:get_ld_name_max_length(controller_obj.TypeId)})
    end
    retval = sml.set_ld_name(controller_obj.Id, volume_id, name)
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'name', name)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%Name', name})
    end
end

-- 设置io策略
function rpc_service_volume.set_volume_iopolicy(controller_obj, volume_id, ctx, iopolicy)
    local retval = sml.set_ld_io_policy(controller_obj.Id, volume_id, iopolicy)
    local iop_str = common_def.LD_IOP_STR[iopolicy] or 'Unknown'
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'IO Policy', iop_str)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%IOPolicy', tostring(iopolicy)})
    end
end

-- 设置bgi使能
function rpc_service_volume.set_volume_bgi_enable(controller_obj, volume_id, ctx, bgi_enable)
    local retval = sml.set_ld_bgi_enable(controller_obj.Id, volume_id, bgi_enable)
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'BGI',
        bgi_enable == 1 and 'enabled' or 'disabled')
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%BGIEnable'})
    end
end

-- 设置获取策略
function rpc_service_volume.set_volume_accesspolicy(controller_obj, volume_id, ctx, accesspolicy)
    local retval = sml.set_ld_access_policy(controller_obj.Id, volume_id, accesspolicy)
    local ap_str = common_def.LD_AP_STR[accesspolicy] or 'Unknown'
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'Access Policy', ap_str)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%AccessPolicy', tostring(accesspolicy)})
    end
end

-- 设置硬盘cache策略
function rpc_service_volume.set_volume_diskcachepolicy(controller_obj, volume_id, ctx, diskcachepolicy)
    local retval = sml.set_ld_disk_cache_policy(controller_obj.Id, volume_id, diskcachepolicy)
    local dcp_str = common_def.LD_DCP_STR[diskcachepolicy] or 'Unknown'
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'Disk Cache Policy', dcp_str)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%DiskCachePolicy', tostring(diskcachepolicy)})
    end
end

-- 开始fgi
function rpc_service_volume.start_volume_fgi(controller_obj, volume_id, ctx, init_type)
    local retval = sml.start_ld_fgi(controller_obj.Id, volume_id, init_type)
    local log_operation_ret = retval == 0 and 'successfully' or 'failed'
    local init_type_str = common_def.LD_INIT_TYPE_STR[init_type] or 'Unknown'
    if ctx and ctx.get_initiator then
        log:operation(ctx:get_initiator(), 'storage', 'Start RAID controller %d logical drive %d FGI with %s %s',
            controller_obj.Id, volume_id, init_type_str, log_operation_ret)
    end
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, 'StartFGI', tostring(init_type)})
    end
end

-- 取消fgi
function rpc_service_volume.cancel_volume_fgi(controller_obj, volume_id, ctx)
    local retval = sml.cancel_ld_fgi(controller_obj.Id, volume_id)
    local log_operation_ret = retval == 0 and 'successfully' or 'failed'
    if ctx and ctx.get_initiator then
        log:operation(ctx:get_initiator(), 'storage', 'Cancel RAID controller %d logical drive %d FGI %s',
            controller_obj.Id, volume_id, log_operation_ret)
    end
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, 'CancelFGI', tostring(volume_id)})
    end
end


-- 设置cache使能
function rpc_service_volume.set_volume_cache_enable(controller_obj, volume_id, ctx, associate)
    local retval = sml.set_ld_cachecade(controller_obj.Id, volume_id, associate)
    local log_operation_ret = retval == 0 and 'successfully' or 'failed'
    if ctx and ctx.get_initiator then
        log:operation(ctx:get_initiator(), 'storage',
            'Set RAID controller %d logical drive %d SSD caching %s %s', controller_obj.Id,
            volume_id, associate == 1 and 'enabled' or 'disabled', log_operation_ret)
    end
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%AssociateCachecade', tostring(associate)})
    end
end

-- 设置加速方法
function rpc_service_volume.set_volume_accelerator(controller_obj, volume_id, ctx, accelerator)
    local retval = sml.set_ld_accelerator(controller_obj.Id, volume_id, accelerator)
    local acc_str = common_def.LD_ACCELERATOR_STR[accelerator] or 'Unknown'
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'Accelerator Method', acc_str)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, '%Accelerator', tostring(accelerator)})
    end
end

local function get_capacity_desc(capacity, capacity_unit)
    if capacity == common_def.STORAGE_INFO_INVALID_DWORD then
        return 'Maximum'
    end
    if capacity_unit == common_def.SML_LD_CAPACITY_UNIT_MB then
        return string.format('%s%s', capacity, 'MB')
    end
    if capacity_unit == common_def.SML_LD_CAPACITY_UNIT_GB then
        return string.format('%s%s', capacity, 'GB')
    end
    if capacity_unit == common_def.SML_LD_CAPACITY_UNIT_TB then
        return string.format('%s%s', capacity, 'TB')
    end
    return ''
end

-- 设置容量
function rpc_service_volume.set_volume_capacitysize(controller_obj, volume_id, ctx, capacity, capacity_unit)
    if not volume_param_check:check_ld_capacity(capacity, capacity_unit) then
        error({common_def.SM_CODE_CAPACITY_OUT_OF_RANGE, 'CapacitySize'})
    end
    local convert_capacity = volume_param_check:convert_ld_capacity_in_array(capacity, capacity_unit)
    local retval = sml.set_ld_capacity_size(controller_obj.Id, volume_id, convert_capacity)
    local capacity_desc = get_capacity_desc(capacity, capacity_unit)
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'Capacity', capacity_desc)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, 'CapacitySize', tostring(convert_capacity)})
    end
end

local function get_strip_size_desc(strip_size)
    -- 对应1MB
    if strip_size == 11 then
        return '1MB'
    end

    return tostring(2 ^ (strip_size - 1)) .. 'KB'
end

-- 设置条带大小
function rpc_service_volume.set_volume_stripsize(controller_obj, volume_id, ctx, stripsize)
    if not volume_param_check:check_ld_strip_size(controller_obj.Id, stripsize) then
        error({common_def.SM_CODE_STRIP_SIZE_OUT_OF_RANGE, 'OptimumIOSizeBytes',
            common_def.LD_STRIP_SIZE_STR[stripsize]})
    end
    local retval = sml.set_ld_strip_size(controller_obj.Id, volume_id, stripsize)
    local strip_size_desc = get_strip_size_desc(stripsize)
    volume_log_operation(ctx, controller_obj, volume_id, retval, 'Strip Size', strip_size_desc)
    if retval ~= common_def.SM_CODE_SUCCESS then
        error({retval, 'OptimumIOSizeBytes', common_def.LD_STRIP_SIZE_STR[stripsize]})
    end
end

local task_list = {}
local function create_start_fgi_task(self, controller, volume_id, volume_path, init_type)
    local task_name = string.format('RaidController%d_LogicalDrive%d_init', controller.Id, volume_id)
    -- 同名的任务只能保留一个
    if task_list[task_name] then
        if sync_task.get_task_running(task_list[task_name]) then
            error({SM_CODE_E.SM_CODE_OPERATION_IN_PROGRESS, 'StartFGI'})
        else
            sync_task.destroy_task(task_list[task_name])
            task_list[task_name] = nil
        end
    end
    local task_idx = sync_task.create_task(self.bus, task_name, volume_path)
    task_list[task_name] = task_idx
    return task_idx
end

local function quick_init_volume_task(self, task_idx, controller, volume_id, ctx, init_type)
    local ok, ret = pcall(self.start_volume_fgi, controller, volume_id, ctx, init_type)
    if not ok then
        sync_task.update_task_prop(task_idx, {State = task_state.Exception, Status = task_status.Error})
        error({ret, 'StartFGI'})
    end
    sync_task.update_task_prop(task_idx, {Progress = 100, State = task_state.Completed})
end

local function full_init_volume_task_proc(task_id, ctrl, volume_idx, volume)
    local before_success = false
    local get_progress_failed_count = 0

    -- 创建任务更新任务机制中的Progress属性
    local update_task_name = string.format('UpdateFGIProgress.C%d:V%d', ctrl.Id, volume_idx)
    tasks.get_instance():new_task(update_task_name):loop(function(task)
        if not volume then
            sync_task.update_task_prop(task_id, {State = task_state.Exception})
            task:stop()
        end
        local progress = volume.ForegroundInitProgress
        if volume.CurrentForegroundInitState ~= 1 or progress == 0xFF then
            get_progress_failed_count = get_progress_failed_count + 1
            -- 控制器正常刷新属性间隔是30S，这里置为40S
            if get_progress_failed_count > 4 and before_success then
                --如果之前成功过，现在失败，认为是完成状态
                sync_task.update_task_prop(task_id, {Progress = 100})
                task:stop()
                return
            end
            if get_progress_failed_count > 4 then
                sync_task.update_task_prop(task_id, {State = task_state.Cancelled})
                task:stop()
            end
            goto continue
        end
        before_success = true
        get_progress_failed_count = 0 --读成功了将计数器清空
        if progress <= 100 and progress >= 0 then
            sync_task.update_task_prop(task_id, {Progress = progress})
        else
            sync_task.update_task_prop(task_id, {State = task_state.Exception})
            task:stop()
        end

        if progress == 100 then
            task:stop()
        end
        ::continue::
    end):set_timeout_ms(10000)
end

function rpc_service_volume:full_init_volume(task_id, ctrl, volume_idx, contex, type)
    sync_task.update_task_prop(task_id, {State = task_state.Running})
    local ok, ret = pcall(self.start_volume_fgi, ctrl, volume_idx, contex, type)
    if not ok then
        sync_task.update_task_prop(task_id, {State = task_state.Exception, Status = task_status.Error})
        error({ret, 'StartFGI'})
    end
    local volume = volume_collection.get_instance():get_ctrl_volume_by_id(ctrl.Id, volume_idx)
    volume.CurrentForegroundInitState = 1 --命令执行成功时即任务逻辑盘已进入初始化状态
    full_init_volume_task_proc(task_id, ctrl, volume_idx, volume)
end

local function full_init_volume_task(self, task_idx, controller, volume_id, ctx, init_type)
    skynet.fork(function()
        self:full_init_volume(task_idx, controller, volume_id, ctx, init_type)
    end)
end

-- 使用任务机制开始前台初始化
function rpc_service_volume.start_volume_fgi_task(self, controller, volume_id, volume_path, ctx, init_type)
    local task_idx = create_start_fgi_task(self, controller, volume_id, volume_path, init_type)
    if init_type == VOLUME_QUICK_INIT_TYPE then
        quick_init_volume_task(self, task_idx, controller, volume_id, ctx, init_type)
        return task_idx
    end
    full_init_volume_task(self, task_idx, controller, volume_id, ctx, init_type)
    return task_idx
end

-- 取消前台初始化，关闭相关的任务
function rpc_service_volume.cancel_volume_fgi_task(self, controller, volume_id, volume_path, ...)
    local task_name = string.format('RaidController%d_LogicalDrive%d_init', controller.Id, volume_id)
    local update_task_name = string.format('UpdateFGIProgress.C%d:V%d', controller.Id, volume_id)
    local task_id = task_list[task_name]
    if not task_list[task_name] then
        return common_def.RET_OK
    end

    local ok, ret = pcall(self.cancel_volume_fgi, controller, volume_id)
    if not ok then
        error({ret, 'CancelFGI'})
    end

    -- 将任务机制中的任务状态设为Canceled，进度更新为0
    sync_task.update_task_prop(task_id, {State = task_state.Cancelled, Progress = 0})

    -- 关闭更新任务机制中的Progress属性的任务
    local update_task = tasks.get_instance():get_task(update_task_name)
    if update_task then
        update_task:stop()
    end

    return common_def.RET_OK
end

function rpc_service_volume:volume_task_operate(operate_name, volume_path, controller_id, volume_id, ...)
    local operations = {
        ["StartFGI"] = self.start_volume_fgi_task,
        ["CancelFGI"] = self.cancel_volume_fgi_task
    }
    log:notice('[Storage]Start %s of Volume%d', operate_name, volume_id)
    local controller = controller_collection:get_by_controller_id(controller_id)

    local ok, ret = pcall(operations[operate_name], self, controller, volume_id, volume_path, ...)
    if not ok and ret then
        if ret[1] ~= common_def.SML_ERR_REBOOT_REQUIRED then
            log:error('[Storage]Failed to %s, and return: %s', operate_name, ret[1])
            return
        end
        log:notice('[Storage]Successfully %s of Volume%d, please restart OS.', operate_name, volume_id)
    else
        log:notice('[Storage]Successfully %s of Volume%d.', operate_name, volume_id)
    end
    return ret
end

return singleton(rpc_service_volume)