-- 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.

-- Smc的i2c模型
-- 支持offsetwidth: 1
-- 数据大小：任意，由命令字决定
local Smc = {}

local log = require "log"
local cjson = require "cjson"
local Smc = require "protocol.Smc"
local JsonParser = require "data.JsonParser"

-- 需要修改
local default_json_file = "../chip/template/default_smc.json"

function calc_crc(buffer)
    local crc8_table = {
        0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
        0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
        0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
        0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
        0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
        0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
        0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
        0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
        0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
        0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
        0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
        0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
        0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
        0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
        0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
        0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3}
        local crc8 = 0x00
        for k,v in ipairs(buffer) do
            local index = v ~ crc8  -- 这个就是异或运算
            crc8 = crc8_table[index + 1];-- lua的table下标寻址是从1 开始的。
        end
    return crc8
end

local BufferManager = {
    filename = ""
}

function BufferManager:new(o, filename)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.filename = filename..".bin"
    return o
end

function BufferManager:read()
    local file = io.open(self.filename, "rb")
    if file == nil then
        log:print(LOG_ERROR, "open file fail")
        return
    end
    local data = file:read("*a")
    file:close()
    local result = {}
    for i = 1, #data do
        local num = tonumber(string.byte(data, i, i))
        result[i] = num
    end
    return result
end

function BufferManager:write(data)
    local file = io.open(self.filename, "wb")
    if file == nil then
        log:print(LOG_ERROR, "open file fail")
        return
    end
    for i = 1, #data do
        file:write(string.char(data[i]))
    end
    file:close()
end

local MockData = {
    desc = "",
    length = 0,
    size = 0,
    data = {},
}

function MockData:new(o, desc, length, size, data)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.desc = desc
    self.length = length
    self.size = size
    self.data = data
    return o
end

local SmcParser = {
    prot_parser = {},
    data_parser = {},
    -- 命令字
    SMC_WRITE_OPCODE = 0x20,
    SMC_READ_OPCODE = 0x21,
    SMC_WRITE_DATA = 0x22,

    DESC_IDX = 1,
    LEN_IDX = 2,
    SIZE_IDX = 3,
    DATA_IDX = 4,
}

function SmcParser:new(o, prot_parser, data_parser, buffer_manager)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.prot_parser = prot_parser
    self.data_parser = data_parser
    self.buffer_manager = buffer_manager
    return o
end

function SmcParser:process_write_opcode(key, smc_prot_data)
    local data = {}
    local value = self.data_parser:get_value(key)
    if value == nil then
        log:print(LOG_ERROR, "get value fail, key = %s", key)
        data = {0x01, 0x01} -- 命令字不支持
        self.buffer_manager:write(data)
        return
    end
    log:print(LOG_DEBUG, "key: %s, value: %s", key, cjson.encode(value))

    local crc = calc_crc(smc_prot_data.verify_data)
    if crc ~= smc_prot_data.crc then
        log:print(LOG_ERROR, string.format("cs error. calc: 0x%02x, expect: 0x%02x", crc, smc_prot_data.crc))
        data = {0x01, 0x05} -- crc错误
        self.buffer_manager:write(data)
        return
    end
    local ret, mock_data = self:get_mock_data(value)
    if ret == true then
        data = mock_data.data
        table.insert(data, 1, 0x00)
        table.insert(data, 1, #data)
    else
        data = {0x01, 0x01} -- 命令字不支持
        log:print(LOG_ERROR, "get mock data fail, key = 0x%08x, value = %s", key, cjson.encode(value))
    end

    log:print(LOG_DEBUG, "prepare buffer data: %s", cjson.encode(data))
    self.buffer_manager:write(data)
end

function SmcParser:process_write_data(key, smc_prot_data)
    local value = self.data_parser:get_value(key)
    if value == nil then
        log:print(LOG_ERROR, "get value fail, key = %s", key)
        return
    end
    log:print(LOG_DEBUG, "key: %s, value: %s", key, cjson.encode(value))

    local crc = calc_crc(smc_prot_data.verify_data)
    if crc ~= smc_prot_data.crc then
        log:print(LOG_ERROR, string.format("cs error. calc: 0x%02x, expect: 0x%02x", crc, smc_prot_data.crc))
        return
    end
    local ret, mock_data = self:get_mock_data(value)
    if ret ~= true then
        log:print(LOG_ERROR, "get mock data fail, key = 0x%08x, value = %s", key, cjson.encode(value))
        return
    end

    local data = mock_data.data
    local size = mock_data.size

    log:print(LOG_DEBUG, "write_data: %s", cjson.encode(smc_prot_data.write_data))
    log:print(LOG_DEBUG, "data: %s, size: %d", cjson.encode(data), size)
    if size == #smc_prot_data.write_data and smc_prot_data.ms_flag == 1 then
        if smc_prot_data.para ~= 0 and smc_prot_data.para + size <= #data + 1 then
            local i = (smc_prot_data.para - 1) * size + 1
            local j = 1
            while i <= smc_prot_data.para * size do
                data[i] = smc_prot_data.write_data[j]
                i = i + 1
                j = j + 1
            end
        elseif #smc_prot_data.write_data < #data then
            return
        else
            data = smc_prot_data.write_data
        end
    elseif #data == #smc_prot_data.write_data and smc_prot_data.ms_flag == 0 then
        data = smc_prot_data.write_data
    else
        return
    end

    log:print(LOG_DEBUG, "write data: %s", cjson.encode(data))
    log:print(LOG_DEBUG, "before value: %s", cjson.encode(value))
    local new_value = self:modify_value(value, data)
    log:print(LOG_DEBUG, "after value: %s", cjson.encode(value))
    self.data_parser:set_value(key, new_value)
    self.data_parser:write_file()
end

function SmcParser:modify_value(value, write_data)
    local new_value = value
    local i = self.DATA_IDX
    local j = 1
    while i <= #new_value and j <= #write_data do
        new_value[i] = string.format("%02x", write_data[j])
        i = i + 1
        j = j + 1
    end
    return new_value
end

function SmcParser:get_mock_data(value)
    if #value < 4 then
        log:print(LOG_ERROR, "mock data length(%d) is small", #value)
        return false
    end
    local desc = value[self.DESC_IDX]
    local size = value[self.SIZE_IDX]
    local length = value[self.LEN_IDX]
    local data = {}
    for i = self.DATA_IDX, #value do
        table.insert(data, tonumber(value[i], 16))
    end
    if #data ~= length then
        return false
    end

    return true, MockData:new(nil, desc, length, size, data)
end

function SmcParser:check_smc_prot_data(smc_prot_data)
    if smc_prot_data == nil then
        log:print(LOG_ERROR, "smc prot data is nil")
        return false
    end

    if smc_prot_data.addr == nil then
        log:print(LOG_ERROR, "addr is nil")
        return false
    end

    if smc_prot_data.command_code == nil then
        log:print(LOG_ERROR, "command_code is nil")
        return false
    end

    if smc_prot_data.command_code ~= self.SMC_READ_OPCODE then
        if smc_prot_data.length == nil then
            log:print(LOG_ERROR, "length is nil")
            return false
        end

        if smc_prot_data.verify_data == nil then
            log:print(LOG_ERROR, "verify_data is nil")
            return false
        end

        if smc_prot_data.crc == nil then
            log:print(LOG_ERROR, "crc is nil")
            return false
        end

        if smc_prot_data.opcode == nil or smc_prot_data.ms_flag == nil or
           smc_prot_data.rw_flag == nil or smc_prot_data.para == nil then
            log:print(LOG_ERROR, "opcode is nil")
            return false
        end

        if smc_prot_data.command_code == self.SMC_WRITE_DATA then
            if smc_prot_data.write_data == nil then
                log:print(LOG_ERROR, "write data is nil")
                return false
            end
        end
    end

    return true
end

function SmcParser:write()
    local smc_prot_data = self.prot_parser:parse()
    if self:check_smc_prot_data(smc_prot_data) ~= true then
        return false
    end
    log:print(LOG_DEBUG, "smc_prot_data: %s", cjson.encode(smc_prot_data))

    local key = string.format("0x%08x", smc_prot_data.opcode)

    if smc_prot_data.command_code == self.SMC_WRITE_OPCODE then
        self:process_write_opcode(key, smc_prot_data)
    elseif smc_prot_data.command_code == self.SMC_WRITE_DATA then
        self:process_write_data(key, smc_prot_data)
    end
end

function SmcParser:get_result(smc_prot_data, data, result_idx)
    local temp = {}
    table.insert(temp, (smc_prot_data.addr & 0xFE))
    table.insert(temp, smc_prot_data.command_code)
    table.insert(temp, (smc_prot_data.addr | 0x01))
    for i = 1, #data do
        table.insert(temp, data[i])
    end
    local crc = calc_crc(temp)
    table.insert(temp, crc)
    log:print(LOG_DEBUG, "SmcParser:get_result read result: %s", cjson.encode(temp))

    local result = {}
    for i = result_idx, #temp do
        table.insert(result, temp[i])
    end
    return result
end


function SmcParser:read()
    local buffer_data = {0x01, 0x03}
    local smc_prot_data = self.prot_parser:parse()
    if self:check_smc_prot_data(smc_prot_data) == true then
        buffer_data = self.buffer_manager:read()
    end

    log:print(LOG_DEBUG, "SmcParser:read buffer_data: %s", cjson.encode(buffer_data))

    return self:get_result(smc_prot_data, buffer_data, self.DATA_IDX)
end


function Smc:write(tx_buffer, filename)
    if type(tx_buffer) ~= "table" or #tx_buffer < 8 then
        return
    end
    log:print(LOG_DEBUG, "Smc:write tx_buffer: %s", cjson.encode(tx_buffer))
    local smc = Smc:new(nil, tx_buffer)
    local json_parser = JsonParser:new(nil, filename, default_json_file)
    local buffer_manager = BufferManager:new(nil, filename)
    local Smc_parse = SmcParser:new(nil, smc, json_parser, buffer_manager)
    Smc_parse:write()
end

function Smc:read(tx_buffer, length, filename)
    if type(tx_buffer) ~= "table" or #tx_buffer < 2 then
        return
    end
    log:print(LOG_DEBUG, "Smc:read tx_buffer: %s, filename is %s", cjson.encode(tx_buffer), filename)
    local smc = Smc:new(nil, tx_buffer)
    local json_parser = JsonParser:new(nil, filename, default_json_file)
    local buffer_manager = BufferManager:new(nil, filename)
    local Smc_parse = SmcParser:new(nil, smc, json_parser, buffer_manager)
    return Smc_parse:read(length)
end

return Smc