-- 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 "log"
local cjson = require "cjson"

local Smc = {
    buffer = {},
    filename = "",

    -- 读写命令共有字段
    ADDR_IDX = 1,
    COMMAND_CODE_IDX = 2,
    DATA_IDX = 4,

    -- 写命令独有字段
    WRITE_ADDR_IDX = 1,
    WRITE_COMMAND_CODE_IDX = 2,
    WRITE_LENGTH_IDX = 3,
    WRITE_OPCODE_IDX = 4,
    WRITE_OPCODE_WIDTH = 4,
    WRITE_DATA_IDX = 8,

    -- 命令字
    SMC_WRITE_OPCODE = 0x20,
    SMC_READ_OPCODE = 0x21,
    SMC_WRITE_DATA = 0x22,

    -- OPCODE标志位
    RW_MASK = 0x100,
    MS_MASK = 0x200,
    PARA_MASK = 0xff,
    OPCODE_MASK = 0xfffffc00,
    RW_OFFSET = 8,
    MS_OFFSET = 9,
}

local OpInfo = {
    rw_flag = nil,
    ms_flag = nil,
    para = nil,
    opcode = nil,
}

function OpInfo:new(o, rw_flag, ms_flag, para, opcode)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.rw_flag = rw_flag
    self.ms_flag = ms_flag
    self.para = para
    self.opcode = opcode
    return o
end

local BaseWriteInfo = {
    op_info = nil,
    length = nil,
    crc = nil,
    verify_data = nil,
}

function BaseWriteInfo:new(o, op_info, length, crc, verify_data)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.op_info = op_info or OpInfo:new(nil, nil, nil, nil, nil)
    self.length = length
    self.crc = crc
    self.verify_data = verify_data
    return o
end

local function copy_data(data, idx, length)
    if idx + length > #data + 1 then
        return {}
    end
    local result = {}
    for i = idx, idx + length - 1 do
        table.insert(result, data[i])
    end
    return result
end

function Smc:new(o, buffer)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.buffer = buffer or {}
    return o
end

function Smc:parse_addr(addr_idx)
    return self.buffer[addr_idx]
end

function Smc:parse_command_code(command_code_idx)
    return self.buffer[command_code_idx]
end

function Smc:parse_length(length_idx)
    return self.buffer[length_idx]
end

function Smc:parse_opcode(opcode_idx, opcode_width)
    local opcode = 0
    for i = opcode_idx, opcode_idx + opcode_width - 1 do
        opcode = opcode * (2 ^ 8)
        opcode = opcode + self.buffer[i]
    end

    local rw_flag = (opcode & self.RW_MASK) >> self.RW_OFFSET
    local ms_flag = (opcode & self.MS_MASK) >> self.MS_OFFSET
    local para = opcode & self.PARA_MASK
    local opcode = opcode & self.OPCODE_MASK
    return OpInfo:new(nil, rw_flag, ms_flag, para, opcode)
end

function Smc:parse_data(data_idx, length)
    local result = {}
    for i = data_idx, data_idx + length - 1 do
        table.insert(result, self.buffer[i])
    end
    return result
end

function Smc:parse_crc()
    return self.buffer[#self.buffer]
end

function Smc:parse_base_write_command()
    if #self.buffer < 8 then
        log:print(LOG_ERROR, "parse_base_write_command, length error")
        return nil
    end
    local length = self:parse_length(self.WRITE_LENGTH_IDX)
    local op_info = self:parse_opcode(self.WRITE_OPCODE_IDX, self.WRITE_OPCODE_WIDTH)
    local crc = self:parse_crc()
    local verify_data = copy_data(self.buffer, 1, #self.buffer - 1)
    return BaseWriteInfo:new(nil, op_info, length, crc, verify_data)
end

function Smc:parse_write_opcode()
    return self:parse_base_write_command()
end

function Smc:parse_write_data()
    local base_write_info = self:parse_base_write_command()
    if base_write_info == nil then
        return
    end
    local write_data = self:parse_data(self.WRITE_DATA_IDX, base_write_info.length - self.WRITE_OPCODE_IDX)
    return base_write_info, write_data
end

function Smc:parse()
    local addr = self:parse_addr(self.ADDR_IDX)
    local command_code = self:parse_command_code(self.COMMAND_CODE_IDX)
    local base_write_info = BaseWriteInfo:new(nil, nil, nil, nil, nil)
    local write_data = nil
    if command_code == self.SMC_WRITE_OPCODE then
        base_write_info = self:parse_write_opcode()
    elseif command_code == self.SMC_WRITE_DATA then
        base_write_info, write_data = self:parse_write_data()
    end

    return {
        addr = addr,
        command_code = command_code,
        opcode = base_write_info.op_info.opcode,
        ms_flag = base_write_info.op_info.ms_flag,
        rw_flag = base_write_info.op_info.rw_flag,
        para = base_write_info.op_info.para,
        length = base_write_info.length,
        crc = base_write_info.crc,
        verify_data = base_write_info.verify_data,
        write_data = write_data,
    }
end

return Smc
