-- 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 log = require 'mc.logging'
local utils = require 'mc.utils'
local class = require 'mc.class'
local cmds = require 'hwproxy.plugins.sml.cmds'
local object_pool = require 'object_pool'
local _, skynet = pcall(require, 'skynet')
local worker = require 'worker.core'

local RET_OK<const> = 0
local RET_ERR<const> = -1
local MASK<const> = 0xFFFFFFFF
local BLOCK_TYPE<const> = 1

local running_chip_map = {}
local function read_write_with_retry_times(func, retry_times, ...)
    local ok, ret_msg
    for i = 0, retry_times do
        ok, ret_msg = pcall(func, ...)
        if ok then
            utils.msleep(3)
            return ok, ret_msg
        end

        -- 失败后也睡一会
        utils.msleep(10)
        log:debug('[Storage]read or write failed, retry times: %d', i)
    end

    return ok, ret_msg
end

-- 提供给lib库的i2c写函数
local function i2c_write(_, data, running_chip)
    local input = object_pool.new('input_args', 0, MASK, BLOCK_TYPE, #data)
    local ok, err = read_write_with_retry_times(running_chip.write, 3, running_chip, input, data)
    object_pool.recycle('input_args', input)
    if not ok then
        log:debug('[Storage]Failed to write through i2c, err:%s', err)
        return RET_ERR
    end
    return RET_OK
end

-- 提供给lib库的i2c读函数
local function i2c_read(_, read_length, running_chip)
    local input = object_pool.new('input_args', 0, MASK, BLOCK_TYPE, read_length, nil)
    local ok, rsp = read_write_with_retry_times(running_chip.read, 3, running_chip, input)
    object_pool.recycle('input_args', input)

    if not ok then
        log:debug('[Storage]Failed to read through i2c, err:%s', rsp)
        return RET_ERR, nil
    end

    return RET_OK, rsp
end

-- 提供给lib库的i2c读写函数
local function i2c_writeread(_, data, read_length, running_chip)
    local input = object_pool.new('input_args', 0, MASK, BLOCK_TYPE, #data)
    local ok, rsp = read_write_with_retry_times(running_chip.write, 3, running_chip, input, data)
    if not ok then
        object_pool.recycle('input_args', input)
        log:debug('[Storage]Failed to writeread write through i2c, err:%s', rsp)
        return RET_ERR, nil
    end

    input.length = read_length
    ok, rsp = read_write_with_retry_times(running_chip.read, 3, running_chip, input)
    object_pool.recycle('input_args', input)
    if not ok then
        log:debug('[Storage]Failed to writeread read through i2c, err:%s', rsp)
        return RET_ERR, nil
    end

    return RET_OK, rsp
end

local function wait_and_timeout(fetch)
    local co = coroutine.running()

    skynet.fork(function()
        for _ = 1, 120 do -- 超时时间120s
            skynet.sleep(100)
            if fetch.result ~= nil then
                return
            end
        end

        if co then
            skynet.wakeup(co)
        end
    end)

    fetch.cb = function()
        if co then
            skynet.wakeup(co)
        end
    end

    skynet.wait()
end

local function wait_hw_access(fetch)
    if fetch.result == nil then
        wait_and_timeout(fetch)
    end

    local result = fetch.result
    object_pool.recycle('fetch', fetch)
    if not fetch.result then
        return RET_ERR, nil
    end
    return RET_OK, result
end

local function empty_cb()
end

local function queue_write_read(write_data, read_len, running_chip)
    local need_write = (write_data and #write_data > 0)
    local need_read = (read_len > 0)

    local fetch = object_pool.new('fetch', empty_cb, nil, false)
    if need_write and need_read then
        running_chip:block_write_read(fetch, write_data, read_len)
    elseif need_write then
        running_chip:block_write(fetch, 0, write_data)
    elseif need_read then
        running_chip:block_read_with_low_priority(fetch, 0, read_len)
    else
        return RET_ERR, nil
    end

    local result, rsp_data = wait_hw_access(fetch)
    if not need_read then
        return result
    end
    return result, rsp_data
end

local function local_write_read(write_data, read_len, running_chip)
    local need_write = (write_data and #write_data > 0)
    local need_read = (read_len > 0)

    local result = RET_ERR
    local rsp_data = nil
    if need_write and need_read then
        result, rsp_data = i2c_writeread(nil, write_data, read_len, running_chip)
    elseif need_write then
        result = i2c_write(nil, write_data, running_chip)
    elseif need_read then
        result, rsp_data = i2c_read(nil, read_len, running_chip)
    end

    return result, rsp_data
end

local sml_plugin = class()

function sml_plugin:ctor(app)
    self.app = app
    self.init_handle_flag_map = {}
    self.init_register_map = {}
end

function sml_plugin:init()
    local lib_sml = require 'sml.core'
    local cbs = lib_sml.callbacks.new()
    self.cbs = cbs
end

function sml_plugin:init_register(controller_id)
    -- 当前raid所在bus若已经注册过则退出
    if self.init_register_map[self.app.bus_object_name] == true then
        return
    end

    self.wr_id = 154 + controller_id
    self.wr_name = string.format('sml_writeread_%s', controller_id)
    self.wk_id = 172 + controller_id
    self.wk_name = string.format('sml_work_%s', controller_id)
    local work = worker.new(0)
    work:start_module('hwproxy.plugins.sml.cmd_work')
    work:send(self.wk_id, true)
    work:send(self.wk_name, true)
    work:send(skynet.self(), true)
    self.work = work
    local ok, err = pcall(function()
        skynet.register_protocol({name = self.wr_name, id = self.wr_id, pack = nil, unpack = skynet.tostring})
        skynet.register_protocol({name = self.wk_name, id = self.wk_id, pack = nil, unpack = skynet.tostring})
    end)
    if not ok then
        log:error('[Storage]register plugin failed, err: %s', err)
    end

    skynet.dispatch(self.wr_name, function(_, read_len, write_data)
        -- read_len 对应source，反向解析source可以拿到ctrl_id
        local ctrl_id = (read_len >> 8) & 0xFF
        local running_chip = running_chip_map[ctrl_id]
        if not running_chip then
            self.cbs:reply_write_read(ctrl_id, RET_ERR)
            return
        end

        read_len = read_len & 0xFF
        local ok, result, rsp_data
        if self.wait_co then
            ok, result, rsp_data = pcall(local_write_read, write_data, read_len, running_chip)
        else
            ok, result, rsp_data = pcall(queue_write_read, write_data, read_len, running_chip)
        end

        if not ok then
            self.cbs:reply_write_read(ctrl_id, RET_ERR)
        else
            self.cbs:reply_write_read(ctrl_id, result, rsp_data)
        end
    end)

    skynet.dispatch(self.wk_name, function(_, _, data)
        if self.wait_co then
            self.wait_result = data
            skynet.wakeup(self.wait_co)
        end
    end)
    self.init_register_map[self.app.bus_object_name] = true
end

local function process_result(ok, result, ...)
    if not ok then
        error(string.format('[Storage]run cmd failed: %s', result))
    end

    return result, ...
end

function sml_plugin:run_cmd(chip, cmd, ctrl_id, ...)
    self:init_register(ctrl_id)
    running_chip_map[ctrl_id] = chip

    if not self.init_handle_flag_map[ctrl_id] then
        self.cbs:init_handle(ctrl_id, skynet.self(), self.wr_id)
        self.init_handle_flag_map[ctrl_id] = true
    end

    self.wait_co = coroutine.running()
    self.wait_result = nil
    local args = skynet.packstring(cmd, ctrl_id, ...)
    self.work:send(args, true)

    while not self.wait_result do
        skynet.wait()
    end

    self.wait_co = nil
    return process_result(skynet.unpack(self.wait_result))
end

function sml_plugin:has_cmd(cmd_name)
    return cmds[cmd_name] ~= nil
end

return sml_plugin
