-- 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 worker = require 'worker.core'
local skynet = require 'skynet'
local sml_core = require 'sml.core'
local c_ctrl_info = require 'sml.ctrl_info'
local c_pd_info = require 'sml.pd_info'
local c_pd_sas_smart_info = require 'sml.pd_sas_smart_info'
local c_ld_info = require 'sml.ld_info'
local c_array_info = require 'sml.array_info'
local c_battery_info = require 'sml.battery_info'
local common_def = require 'common_def'
local ctx = require 'mc.context'
local log = require 'mc.logging'

local c_ctrl_exp_sas_phy_err= require 'sml.ctrl_exp_sas_phy_err_info'
local c_diagnose_link_phy_error_info = require 'sml.diagnose_link_phy_error_info'
local c_ctrl_sas_phy_err_count_info = require 'sml.ctrl_sas_phy_err_count_info'


local sml_mt = {}
sml_mt.__index = sml_mt

sml_mt.register_ctrl_oob = sml_core.register_ctrl_oob
sml_mt.mctp_infos = sml_core.mctp_infos
local sml = setmetatable({ callback = worker.new(1) }, sml_mt)

function sml_mt:__gc()
    self.callback:send('stop', true)
    self.callback:join()
end


local i2c_chip_map = {}
local mctp_ctrl_map = {}

-- sch_prio可选参数：{Priority = 'Secondary'} 或者 {Priority = 'High'}
local function timeout_call(service, type, funcname, sch_prio, ctrl_idx, ...)
    local i2c_chip = i2c_chip_map[ctrl_idx]
    if i2c_chip then
        -- i2c 通道转发到 hwproxy 的插件处理
        local args = skynet.packstring(ctrl_idx, ...)
        return skynet.unpack(i2c_chip:PluginRequestEx(ctx.get_context_or_default(), 'sml', funcname, args, sch_prio))
    end

    if not mctp_ctrl_map[ctrl_idx] and ctrl_idx ~= common_def.INVALID_U8 then
        mctp_ctrl_map[ctrl_idx] = string.format('install_mctp_writeread_cb_%s', ctrl_idx)
        sml.callback:send(mctp_ctrl_map[ctrl_idx], true)
    end

    local co = coroutine.running()
    local ret

    skynet.fork(function(...)
        ret = table.pack(skynet.call(...))
        if co then
            skynet.wakeup(co)
        end
    end, service, type, funcname, ctrl_idx, ...)

    skynet.wait()
    co = nil -- prevent wakeup after call

    if not ret then
        -- timeout
        error(string.format('sml_call timout, funcname: %s', funcname))
    end

    if ret[1] then
        return table.unpack(ret, 2, ret.n)
    else
        error(table.unpack(ret, 2, ret.n))
    end
end

---@param ctrl_index integer
---@param type_id integer
---@param operation integer
---@param data table
function sml_mt.register_controller(ctrl_index, type_id, operation, data)
    return timeout_call('.smld', 'lua', 'register_controller', {Priority = 'High'}, ctrl_index,
        type_id, operation, data)
end

---@param ctrl_index integer
---@param device_id integer
---@param operation integer
---@param data string
function sml_mt.pd_operation(ctrl_index, device_id, operation, data)
    return timeout_call('.smld', 'lua', 'pd_operation', {Priority = 'High'}, ctrl_index,
        device_id, operation, data)
end

---@param ctrl_index integer
---@param device_id integer
function sml_mt.pd_log_get_drive_log(ctrl_index, protocol, device_id, basic_info)
    local u8 = {basic_info.log_source, basic_info.log_rotate_num, basic_info.pd_power_state,
                basic_info.pd_interface_type, basic_info.pd_media_type, basic_info.pd_slot_number,
                basic_info.pd_remnant_media_wearout}
    local u16 = {basic_info.pd_enclosure_id}
    local string = {basic_info.pd_device_name, basic_info.pd_interface_type_str, basic_info.pd_manufacturer,
                    basic_info.pd_serial_number, basic_info.pd_model}
    return timeout_call('.smld', 'lua', 'pd_log_get_drive_log', {Priority = 'High'}, ctrl_index,
        protocol, device_id, u8, u16, string)
end

function sml_mt.pd_get_smart_info_str(ctrl_index, device_id, hdd_intf_type)
    return timeout_call('.smld', 'lua', 'pd_get_smart_info_str', {Priority = 'High'}, ctrl_index,
        device_id, hdd_intf_type)
end

function sml_mt.pd_get_io_diagnose_info(ctrl_index, protocol, device_id, basic_info)
    local u8 = {basic_info.log_source, basic_info.log_rotate_num, basic_info.pd_power_state,
                basic_info.pd_interface_type, basic_info.pd_media_type, basic_info.pd_rebuild_progress,
                basic_info.pd_health}
    local u32 = {basic_info.pd_prefail_error_count, basic_info.pd_media_error_count, basic_info.pd_other_error_count}
    local string = {basic_info.pd_device_name, basic_info.pd_interface_type_str, basic_info.pd_manufacturer,
                    basic_info.pd_serial_number, basic_info.pd_model}
    return timeout_call('.smld', 'lua', 'pd_get_io_diagnose_info', {Priority = 'High'}, ctrl_index,
        protocol, device_id, u8, u32, string)
end

function sml_mt.pd_log_write_subhealthy_info(ctrl_index, estimated_remaining_lifespan, basic_info)
    local u8 = {basic_info.update_support_flag, basic_info.hw_defined_valid_flag, basic_info.vendor_valid_flag,
                basic_info.first_start_flag}
    local u64 = {basic_info.vendor_nand_write, basic_info.vendor_host_write, basic_info.hw_defined_nand_write_l,
                basic_info.hw_defined_nand_write_h, basic_info.hw_defined_host_write_l,
                basic_info.hw_defined_host_write_h, basic_info.last_nand_written, basic_info.last_host_written}
    local string = { basic_info.pd_device_name, basic_info.pd_serial_number }
    return timeout_call('.smld', 'lua', 'pd_log_write_subhealthy_info', {Priority = 'High'}, ctrl_index,
        estimated_remaining_lifespan, u8, u64, string)
end

---@param ctrl_index integer
---@param device_id integer
function sml_mt.pd_diag_by_smart_log(ctrl_index, device_id)
    return timeout_call('.smld', 'lua', 'pd_diag_by_smart_log', {Priority = 'High'}, ctrl_index, device_id)
end

---@param ctrl_index integer
---@param device_id integer
function sml_mt.pd_diag_by_ext_error_log(ctrl_index, device_id)
    return timeout_call('.smld', 'lua', 'pd_diag_by_ext_error_log', {Priority = 'High'}, ctrl_index, device_id)
end

---@param ctrl_index integer
---@param device_id integer
---@param in_progress integer
function sml_mt.pd_diag_by_ext_self_test_log(ctrl_index, device_id, in_progress)
    return timeout_call('.smld', 'lua', 'pd_diag_by_ext_self_test_log', {Priority = 'High'}, ctrl_index,
        device_id, in_progress)
end

---@param ctrl_index integer
---@param device_id integer
function sml_mt.pd_diag_get_sata_self_test_status(ctrl_index, device_id)
    return timeout_call('.smld', 'lua', 'pd_diag_get_sata_self_test_status', {Priority = 'High'}, ctrl_index, device_id)
end

---@param ctrl_index integer
---@param pd_device_id integer
---@param encl_device_id integer
function sml_mt.pd_diag_encl_comm_error(ctrl_index, pd_device_id, encl_device_id)
    return timeout_call('.smld', 'lua', 'pd_diag_encl_comm_error', {Priority = 'High'}, ctrl_index, pd_device_id,
        encl_device_id)
end

---@param ctrl_index integer
---@param pd_device_id integer
function sml_mt.pd_diag_sense_error(ctrl_index, pd_device_id)
    return timeout_call('.smld', 'lua', 'pd_diag_sense_error', {Priority = 'High'}, ctrl_index, pd_device_id)
end

---@param ctrl_index integer
---@param src_dir string
---@param log_type integer
---@param log_name string
function sml_mt.dump_ctrl_single_log(ctrl_index, src_dir, log_type, log_name)
    return timeout_call('.smld', 'lua', 'dump_ctrl_single_log', {Priority = 'High'}, ctrl_index,
        src_dir, log_type, log_name)
end

---@param ctrl_index integer
---@param operation integer
---@param data integer
function sml_mt.ctrl_operation(ctrl_index, operation, data)
    return timeout_call('.smld', 'lua', 'ctrl_operation', {Priority = 'High'}, ctrl_index, operation, data)
end

---@class t_pd_item
---@field device_id integer
---@field slot_num integer
---@field enclosure_id integer

---@param ctrl_index integer
---@return t_pd_item[]
function sml_mt.get_ctrl_pd_list(ctrl_index)
    return timeout_call('.smld', 'lua', 'get_ctrl_pd_list', {Priority = 'Secondary'}, ctrl_index)
end

---@param ctrl_index integer
function sml_mt.get_ctrl_sas_phy_err(ctrl_index, force_update)
    return timeout_call('.smld', 'lua', 'get_ctrl_sas_phy_err', {Priority = 'Secondary'}, ctrl_index, force_update)
end

---@param ctrl_index integer
---@return integer[]
function sml_mt.get_ctrl_ld_list(ctrl_index)
    return timeout_call('.smld', 'lua', 'get_ctrl_ld_list', {Priority = 'High'}, ctrl_index)
end

function sml_mt.get_ctrl_array_list(ctrl_index)
    return timeout_call('.smld', 'lua', 'get_ctrl_array_list', {Priority = 'High'}, ctrl_index)
end

local ctrl_inited = {}
function sml_mt.update_ctrl_init_state(ctrl_index)
    ctrl_inited[ctrl_index] = timeout_call('.smld', 'lua', 'get_ctrl_init_state', {Priority = 'Secondary'}, ctrl_index)
    return ctrl_inited[ctrl_index]
end

function sml_mt.get_ctrl_init_state(ctrl_index)
    if not ctrl_inited[ctrl_index] then
        error(common_def.SML_ERR_CTRL_STATUS_INVALID)
    end
    return ctrl_inited[ctrl_index]
end

function sml_mt.clear_all_controller_info(ctrl_index)
    return timeout_call('.smld', 'lua', 'clear_all_controller_info', {Priority = 'High'}, ctrl_index)
end

---@param raid_index integer
---@return string
function sml_mt.get_ctrl_sas_addr(raid_index)
    return timeout_call('.smld', 'lua', 'get_ctrl_sas_addr', {Priority = 'Secondary'}, raid_index)
end

---@param ctrl_index integer
---@param pd_device_id integer
---@return c_pd_info
function sml_mt.get_pd_info(sch_prio, ctrl_index, pd_device_id)
    local info = timeout_call('.smld', 'lua', 'get_pd_info', sch_prio, ctrl_index, pd_device_id)
    return c_pd_info.new(info)
end

---@param ctrl_index integer
---@param pd_device_id integer
---@return c_pd_sas_smart_info
function sml_mt.get_pd_sas_smart_info(ctrl_index, pd_device_id)
    local info = timeout_call('.smld', 'lua', 'get_pd_sas_smart_info', {Priority = 'Secondary'}, ctrl_index,
        pd_device_id)
    return c_pd_sas_smart_info.new(info)
end


---@param ctrl_index integer
---@param ld_target_id integer
---@return t_ld_info
function sml_mt.get_ld_info(sch_prio, ctrl_index, ld_target_id)
    local info = timeout_call('.smld', 'lua', 'get_ld_info', sch_prio, ctrl_index, ld_target_id)
    return c_ld_info.new(info)
end

---@param ctrl_index integer
---@param array_target_id integer
---@return c_array_info
function sml_mt.get_array_info(ctrl_index, array_target_id)
    local info = timeout_call('.smld', 'lua', 'get_array_info', {Priority = 'Secondary'}, ctrl_index, array_target_id)
    return c_array_info.new(info)
end

---@param ctrl_index integer
---@return c_battery_info
function sml_mt.get_battery_info(ctrl_index)
    local info = timeout_call('.smld', 'lua', 'get_battery_info', {Priority = 'Secondary'}, ctrl_index)
    return c_battery_info.new(info)
end

---@param ctrl_index integer
---@return c_ctrl_info
function sml_mt.get_ctrl_info(sch_prio, ctrl_index)
    local info = timeout_call('.smld', 'lua', 'get_ctrl_info', sch_prio, ctrl_index)
    return c_ctrl_info.new(info)
end

---@param ctrl_index integer
---@return integer
function sml_mt.get_ctrl_faultcode(ctrl_index)
    return timeout_call('.smld', 'lua', 'get_ctrl_faultcode', {Priority = 'Secondary'}, ctrl_index)
end

---@param raid_index integer
---@return integer
---@return integer
function sml_mt.get_ctrl_boot_devices(raid_index)
    return timeout_call('.smld', 'lua', 'get_ctrl_boot_devices', {Priority = 'Secondary'}, raid_index)
end

---@param controller_id integer
---@param volume_id integer
---@param priority integer
---@return integer
function sml_mt.set_ld_boot_priority(controller_id, volume_id, priority)
    return timeout_call('.smld', 'lua', 'set_ld_boot_priority', {Priority = 'High'}, controller_id, volume_id, priority)
end

---@param controller_id integer
---@param volume_id integer
---@param readpolicy integer
---@return integer
function sml_mt.set_ld_readpolicy(controller_id, volume_id, readpolicy)
    return timeout_call('.smld', 'lua', 'set_ld_readpolicy', {Priority = 'High'}, controller_id, volume_id, readpolicy)
end

---@param controller_id integer
---@param volume_id integer
---@param writepolicy integer
---@return integer
function sml_mt.set_ld_writepolicy(controller_id, volume_id, writepolicy)
    return timeout_call('.smld', 'lua', 'set_ld_writepolicy', {Priority = 'High'}, controller_id,
        volume_id, writepolicy)
end

---@param controller_id integer
---@param volume_id integer
---@param name string
---@return integer
function sml_mt.set_ld_name(controller_id, volume_id, name)
    return timeout_call('.smld', 'lua', 'set_ld_name', {Priority = 'High'}, controller_id, volume_id, name)
end

---@param controller_id integer
---@param volume_id integer
---@param io_policy integer
---@return integer
function sml_mt.set_ld_io_policy(controller_id, volume_id, io_policy)
    return timeout_call('.smld', 'lua', 'set_ld_io_policy', {Priority = 'High'}, controller_id, volume_id, io_policy)
end

---@param controller_id integer
---@param volume_id integer
---@param bgi_enable integer
---@return integer
function sml_mt.set_ld_bgi_enable(controller_id, volume_id, bgi_enable)
    return timeout_call('.smld', 'lua', 'set_ld_bgi_enable', {Priority = 'High'}, controller_id, volume_id, bgi_enable)
end

---@param controller_id integer
---@param volume_id integer
---@param access_policy integer
---@return integer
function sml_mt.set_ld_access_policy(controller_id, volume_id, access_policy)
    return timeout_call('.smld', 'lua', 'set_ld_access_policy', {Priority = 'High'}, controller_id,
        volume_id, access_policy)
end

---@param controller_id integer
---@param volume_id integer
---@param disk_cache_policy integer
---@return integer
function sml_mt.set_ld_disk_cache_policy(controller_id, volume_id, disk_cache_policy)
    return timeout_call('.smld', 'lua', 'set_ld_disk_cache_policy', {Priority = 'High'}, controller_id,
        volume_id, disk_cache_policy)
end

---@param controller_id integer
---@param volume_id integer
---@param associate integer
---@return integer
function sml_mt.set_ld_cachecade(controller_id, volume_id, associate)
    return timeout_call('.smld', 'lua', 'set_ld_cachecade', {Priority = 'High'}, controller_id, volume_id, associate)
end

---@param controller_id integer
---@param volume_id integer
---@param accelerator integer
---@return integer
function sml_mt.set_ld_accelerator(controller_id, volume_id, accelerator)
    return timeout_call('.smld', 'lua', 'set_ld_accelerator', {Priority = 'High'}, controller_id,
        volume_id, accelerator)
end

---@param controller_id integer
---@param volume_id integer
---@param capacity_size integer
---@return integer
function sml_mt.set_ld_capacity_size(controller_id, volume_id, capacity_size)
    return timeout_call('.smld', 'lua', 'set_ld_capacity_size', {Priority = 'High'}, controller_id,
        volume_id, capacity_size)
end

---@param controller_id integer
---@param volume_id integer
---@param strip_size integer
---@return integer
function sml_mt.set_ld_strip_size(controller_id, volume_id, strip_size)
    return timeout_call('.smld', 'lua', 'set_ld_strip_size', {Priority = 'High'}, controller_id, volume_id, strip_size)
end

---@param controller_id integer
---@param volume_id integer
---@param init_type integer
---@return integer
function sml_mt.start_ld_fgi(controller_id, volume_id, init_type)
    return timeout_call('.smld', 'lua', 'start_ld_fgi', {Priority = 'High'}, controller_id, volume_id, init_type)
end

---@param controller_id integer
---@param volume_id integer
---@return integer
function sml_mt.cancel_ld_fgi(controller_id, volume_id)
    return timeout_call('.smld', 'lua', 'cancel_ld_fgi', {Priority = 'High'}, controller_id, volume_id)
end

---@return integer
function sml_mt.add_ld_on_exist_array(...)
    return timeout_call('.smld', 'lua', 'add_ld_on_exist_array', {Priority = 'High'}, ...)
end

---@return integer
function sml_mt.create_ld_on_new_array(...)
    return timeout_call('.smld', 'lua', 'create_ld_on_new_array', {Priority = 'High'}, ...)
end

---@return integer
function sml_mt.create_ld_as_cachecade(...)
    return timeout_call('.smld', 'lua', 'create_ld_as_cachecade', {Priority = 'High'}, ...)
end

---@return integer
function sml_mt.delete_volume(controller_id, volume_id)
    return timeout_call('.smld', 'lua', 'delete_volume', {Priority = 'High'}, controller_id, volume_id)
end

---@return integer
function sml_mt.get_ld_sscd_caching_enable(controller_id, volume_id)
    return timeout_call('.smld', 'lua', 'get_ld_sscd_caching_enable', {Priority = 'Secondary'}, controller_id,
        volume_id)
end

---@return integer
function sml_mt.get_sscd_associated_ld_list(controller_id, volume_id)
    return timeout_call('.smld', 'lua', 'get_sscd_associated_ld_list', {Priority = 'Secondary'}, controller_id,
        volume_id)
end

---@return integer
function sml_mt.get_ld_associated_sscd_list(controller_id, volume_id)
    return timeout_call('.smld', 'lua', 'get_ld_associated_sscd_list', {Priority = 'Secondary'}, controller_id,
        volume_id)
end

---@param ctrl_index integer
---@param force_update integer
---@return c_ctrl_sas_phy_err_count
function sml_mt.get_ctrl_sas_phy_err_count(ctrl_index, force_update)
    local info = timeout_call('.smld', 'lua', 'get_ctrl_exp_phy_err_info', ctrl_index, force_update)
    return c_ctrl_sas_phy_err_count_info.new(info)
end

---@param serial_threshold integer
---@param recent_threshold integer
---@return c_diagnose_link_phy_error
function sml_mt.pd_diag_link_phy_error(ctrl_index, pd_device_id, encl_device_id, pd_error,
    serial_threshold, recent_threshold)
    local info = timeout_call('.smld', 'lua', 'pd_diag_link_phy_error', {Priority = 'High'},
    ctrl_index, pd_device_id, encl_device_id, pd_error, serial_threshold, recent_threshold)
    return c_diagnose_link_phy_error_info.new(info)
end

---@return c_ctrl_exp_sas_phy_err
function sml_mt.get_ctrl_exp_sas_phy_err_info(ctrl_index)
    local info = timeout_call('.smld', 'lua', 'get_ctrl_exp_sas_phy_err_info', {Priority = 'High'}, ctrl_index)
    return c_ctrl_exp_sas_phy_err.new(info)
end

sml.callback:start_module('sml.sml_callbacks')

sml.OOB_TYPE_OVER_I2C = sml_core.OOB_TYPE_OVER_I2C
sml.OOB_TYPE_OVER_PCIE = sml_core.OOB_TYPE_OVER_PCIE
sml.SML_ADD_CTRL = sml_core.SML_ADD_CTRL
sml.SML_REGISTER_CTRL = sml_core.SML_REGISTER_CTRL
sml.SML_DEL_CTRL = sml_core.SML_DEL_CTRL
sml.SML_UNREGISTER_CTRL = sml_core.SML_UNREGISTER_CTRL

function sml.set_i2c_chip(ctrl_idx, chip)
    i2c_chip_map[ctrl_idx] = chip
    log:notice("sml: set i2c chip, ctrl_idx=%d, chip=%s", ctrl_idx, chip.path)
    return true
end

return sml
