-- Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
-- 
-- this file licensed under the 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 std_smbus = require 'protocol_open.protocol.std_smbus'
local bs = require 'mc.bitstring'
local crc32 = require 'mc.crc32'
local utils = require 'mc.utils'
local log = require 'mc.logging'
local ctx = require 'mc.context'

local header_len<const> = 12
local crc32_len<const> = 4

local response_bs<const> = bs.new([[<<
    error_code:16,
    opcode:16,
    total_length:32,
    length:32/integer,
    data:length/string
>>]])

local request_bs<const> = bs.new([[<<
    reserved:16,
    opcode:16,
    offset:32,
    length:32
>>]])

local request_params_template<const> = {opcode = true, expect_data_len = true}

local smbus = class(std_smbus)

local error_code<const> = {
    [0x1] = 'opcode not support',
    [0x2] = 'parameter error',
    [0x3] = 'internal error',
    [0x4] = 'crc error',
    [0xF] = 'bus busy'
}

function smbus:send_and_receive(data, len)
    log:debug('sending data: %s, reading len: %s', utils.to_hex(data), len)
    return pcall(function()
        return self.ref_chip:WriteRead(ctx.new(), data, len)
    end)
end

function smbus:get_max_frame_len()
    return smbus.super.get_max_frame_len(self) - crc32_len
end

function smbus:construct_request_data(track_request)
    local request = {reserved = 0}
    request.opcode = track_request.opcode
    request.offset = track_request.data_written
    request.length = track_request.len
    local temp = self.request_bs:pack(request)
    return temp .. string.pack('I' .. crc32_len, crc32(temp, 0, true))
end

function smbus:unpack_response_data(rsp_bin, expect_data_len)
    -- crc校验
    local M = self.buffer_len
    if expect_data_len > 0 then
        M = expect_data_len + header_len + crc32_len
    end
    local bin_data = rsp_bin:sub(1, M - crc32_len)
    local bin_crc = rsp_bin:sub(M - crc32_len + 1, M)
    local crc_cal = crc32(bin_data, 0, true)
    local crc_expect = string.unpack('I' .. crc32_len, bin_crc)
    if crc_cal ~= crc_expect then
        return false, 'crc check error on smbus response'
    end

    local result = self.response_bs:unpack(bin_data, true)
    if not result then
        return false, 'unable to parse smbus response binary'
    end
    return true, result
end

function smbus:send_and_receive_request_in_frames(track_request)
    -- 最后一帧只发剩余长度，其他帧发最大长度
    if track_request.data_written > 0 and #track_request.data > 0 and
        (track_request.data_written + track_request.max_frame_len) >= #track_request.data then
        track_request.len = #track_request.data - track_request.data_written
    end

    local req_bin = self:construct_request_data(track_request)
    if req_bin == '' then
        return false, 'unable to construct request data'
    end
    return self:send_and_receive(req_bin, self.buffer_len)
end

function smbus:get_response_empty_obj()
    return self.response_bs:unpack(string.rep('\x00', self.buffer_len), true)
end

function smbus:send_request(request)
    return self:_send_request_internal(request)
end

function smbus:ctor()
    self.name = 'smbus'
    self.request_bs = request_bs
    self.response_bs = response_bs
    self.request_params_template = request_params_template
    self.error_code = error_code
end

return smbus

