-- 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.
--         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 bs = require 'mc.bitstring'
local log = require 'mc.logging'
local context = require 'mc.context'
local class = require 'mc.class'
local utils = require 'mc.utils'
local _, skynet = pcall(require, 'skynet')

local READ_SEND_CODE <const> = 0x0a
local READ_RECEIVE_CODE <const> = 0x09
local READ_BYTE_CNT <const> = 5

local WRITE_SEND_CODE <const> = 0x0F

local RESPONSE_BODY<const> = [[<<
    count:8,
    data:32/little
>>]]

local REQUEST_CONTEXT<const> = {
    ['SEND_ADDR'] = {
        pattern = bs.new([[<<
            byte_cnt:8,
            addr:32/big
        >>]]),
        length = 5
    },
    ['SEND_ADDR_ADTA'] = {
        pattern = bs.new([[<<
            byte_cnt:8,
            addr:32/big,
            data:32/little
        >>]]),
        length = 9
    }
}

local smbus_81532 = class(nil, nil, true)
function smbus_81532:ctor(ref_chip)
    self.chip = ref_chip
end

function smbus_81532:construct_request(addr, data, pattern)
    local request = pattern.pattern:unpack(string.rep('\x00', pattern.length), true)
    request.byte_cnt = pattern.length - 1
    request.addr = addr
    request.data = data
    local payload = pattern.pattern:pack(request)
    if log:getLevel() >= log.DEBUG then
        log:debug("smbus construct_request, %s", utils.to_hex(payload))
    end
    return payload
end

function smbus_81532:chip_plugin_write(use_pec, send_data, write_cmd)
    local ok, msg = skynet.unpack(self.chip:PluginRequest(context.get_context_or_default(),
        'general_hardware', 'retimer_smbus_write_block',
        skynet.packstring(use_pec, send_data, write_cmd)))
    if not ok then 
        log:error("PluginRequest:retimer_smbus_write_block failed, chip: %s, write_cmd: %s, send_data: %s",
            self.chip.object_name, write_cmd, utils.to_hex(send_data)
        )
    end
    return ok, msg
end

function smbus_81532:chip_plugin_read(use_pec, send_data, write_cmd, read_cmd, read_length)
    local ok, msg = skynet.unpack(self.chip:PluginRequest(context.get_context_or_default(),
        'general_hardware', 'retimer_smbus_read_block',
        skynet.packstring(use_pec, send_data, write_cmd, read_cmd, read_length)))
    if not ok then
        log:error([[PluginRequest:retimer_smbus_read_block failed, chip: %s, write_cmd: %s, send_data: %s, ]]..
            [[read_cmd: %s, read_length: %s]],
            self.chip.object_name, write_cmd, utils.to_hex(send_data), read_cmd, read_length
        )
    end
    return ok, msg
end

function smbus_81532:smbus_write(addr, data, use_pec)
    local write_cmd = use_pec and WRITE_SEND_CODE| (1 << 7) or WRITE_SEND_CODE
    local send_data = self:construct_request(addr, data, REQUEST_CONTEXT['SEND_ADDR_ADTA'])

    return self:chip_plugin_write(use_pec, send_data, write_cmd)
end

function smbus_81532:smbus_read_quick(addr, use_pec)
    local write_cmd = use_pec and READ_SEND_CODE | (1 << 7) or READ_SEND_CODE
    local send_data = self:construct_request(addr, nil, REQUEST_CONTEXT['SEND_ADDR'])
    local read_length = READ_BYTE_CNT + (use_pec and 1 or 0)
    local read_cmd = use_pec and READ_RECEIVE_CODE | (1 << 7) or READ_RECEIVE_CODE

    return self:chip_plugin_read(use_pec, send_data, write_cmd, read_cmd, read_length)
end

function smbus_81532:retimer_write(args)
    local ok = self:smbus_write(args.addr, args.data, args.use_pec)
    if not ok then
        return false, 'fail to write retimer'
    end
    return true, nil
end

function smbus_81532:retimer_read(args)
    local ok, msg = self:smbus_read_quick(args.addr, args.use_pec)
    if not ok then
        return false, 'smbus_read fail'
    end
    return self:parse_response(msg, args.addr)
end

function smbus_81532:retimer_loop_read(args, fun)
    local ok, msg = false, nil
    for _ = 1, 5 do
        ok, msg = self:retimer_read(args)
        if ok and fun(msg) then
            return ok, msg
        end
    end
    return false, "fail to get loop stop flag!"
end

function smbus_81532:parse_response(rawdata, addr)
    local result = bs.new(RESPONSE_BODY):unpack(rawdata, true)
    if not result then 
        log:error("parse response error, addr is %s",addr)
        return false, "parse response error"
    end
    return true, result.data
end

return smbus_81532
