-- Copyright (c) 2025 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 class = require 'mc.class'
local log = require 'mc.logging'
local ipmi = require 'ipmi'
local system_info = require 'domain.system_info.system_info'
local prop_def = require 'macros.property_def'
local s_pack = string.pack
local cc = ipmi.types.Cc
local FIRSTBLOCK<const> = 0
local INFOLEN<const> = 4
local DATAINDEX<const> = 2
local SEGINDEX<const> = 1
local SEGLEN<const> = 1
local DATALEN<const> = 2
local LENGTHINDEX<const> = 3
local BLOCKLENGTH<const> = 16
local FIRSTBLOCKLENGTH<const> = 14
local table_cache = require 'mc.table_cache'
local tbl_cache = table_cache.new()

local BlockInfo = class(system_info, nil, true)

function BlockInfo:ctor(cfg)
    self.cfg = cfg or {}
    -- 总长度
    self.length = 0
    -- 最后一块长度
    self.last_seg_length = 0
    -- 总块数
    self.seg_num = 0
    -- 当前块，从第0块开始
    self.current_seg_num = 0
end

-- 功能:从数组下标from_index开始存储,解析数据库的数据:['/x/x/x/x/x']->[数组]
-- 默认:从0开始存储,非string类型，0：数据长度，1：当前seg；2~n：数据
function BlockInfo:decode_db_info(db_info, store_index)
    if not db_info then
        return nil
    end

    local data = tbl_cache:allocate()
    local format = '/(%w+)'
    local index = 0
    for word in string.gmatch(db_info, format) do
        data[index] = tonumber(word)
        index = index + 1
    end

    return data
end

-- 功能:从数组下标from_index开始编码,编码数据库的数据:[数组]->['/x/x/x/x/x']
-- 默认:从0开始解析
function BlockInfo:encode_db_info(arr_data, parse_index)
    local text = {}
    local len = #arr_data
    local index = 1
    local from_index = 0
    for i = from_index, len, 1 do
        local data = arr_data[i]
        if not data then
            return ''
        end

        local s = '/' .. tostring(data)
        -- table是从1开始
        text[index] = s
        index = index + 1
    end

    return table.concat(text)
end

function BlockInfo:init_param(length)
    self.length = length
    self.current_seg_num = 0
    if length == 0 then
        self.last_seg_length = 0
        self.seg_num = 0
        return
    end

    if length % 16 == 0 then
        self.last_seg_length = 16
        self.seg_num = length // 16
    else
        self.last_seg_length = length % 16
        self.seg_num = (length // 16) + 1
    end
end

function BlockInfo:init()
    BlockInfo.super.init(self)
    local data
    if self.db_obj and self.db_obj[self.prop] then
        local db_val = self.db_obj[self.prop]
        if #db_val ~= 0 then
            data = self:decode_db_info(db_val)
            self:init_param(data[0])
            self.current_seg_num = data[1]
        end
    end
    self.data = data or tbl_cache:allocate()
end

-- 持久化
function BlockInfo:save()
    if not self.db_obj or self.db_obj[self.prop] == nil then
        log:error('bios: save block prop %s invalid param', self.prop)
        return false
    end
    self.db_obj[self.prop] = self:encode_db_info(self.data)
    self.db_obj:save()
    return true
end

function BlockInfo:clear()
    self.length = 0
    self.last_seg_length = 0
    self.seg_num = 0
    self.current_seg_num = 0
    tbl_cache:deallocate(self.data)
end

function BlockInfo:reallocate(length)
    length = length or 0
    self:clear()
    self:init_param(length)
    self.data = tbl_cache:allocate()
    self.data[0] = length
end

-- total_length：设置的总长度，data_length：实际数据长度
function BlockInfo:validate_first_block(total_length, data_length)
    if not total_length or not data_length then
        log:error('[BlockInfo]validate fail, invalid param')
        return cc.InvalidFieldRequest
    end
    -- 第一帧，设置的数据长度必须大于3：编码 + 长度 + 数据 (块：16字节)
    if total_length < 3 then
        log:error('[BlockInfo]set data length less than 1, total length %s', total_length)
        return cc.InvalidFieldRequest
    end

    -- 只有一帧，第一帧数据必须大于或者等于设置的大小
    if total_length < BLOCKLENGTH then
        if data_length < total_length then
            log:error('[BlockInfo]first block data length %s less than total length %s', data_length, total_length)
            return cc.ReqDataLenInvalid
        end
        self.last_seg = true
    -- 有多帧，第一帧数据必须等于14，需要扣除前两个字节
    else
        if data_length < BLOCKLENGTH then
            log:error('[BlockInfo]first block data length %s less than 16')
            return cc.ReqDataLenInvalid
        end
        self.last_seg = false
    end
    return cc.Success
end

function BlockInfo:validate(seg_selector, data_length)
    local total_length = self.length
    if not seg_selector or not total_length or not data_length then
        log:error('[BlockInfo]validate fail, invalid param')
        return cc.InvalidFieldRequest
    end

    if seg_selector > self.current_seg_num or seg_selector > self.seg_num then
        log:error('[BlockInfo]seg %s invalid, current seg %s, seg num is %s',
            seg_selector, self.current_seg_num, self.seg_num)
        return cc.InvalidFieldRequest
    end

    self.last_seg = ((seg_selector + 1) == self.seg_num)
    if self.last_seg then
        if data_length < self.last_seg_length then
            log:error('[BlockInfo]seg %s is last seg, real data length %s less than %s',
                seg_selector, data_length, self.last_seg_length)
            return cc.ReqDataLenInvalid
        end
    else
        if data_length < BLOCKLENGTH then
            log:error('[BlockInfo]seg %s not last seg, real data length %s less than 16', seg_selector, data_length)
            return cc.ReqDataLenInvalid
        end
    end
    return cc.Success
end

local function get_byte(info_data, index)
    if not info_data or #info_data < index then
        return 0
    end
    return string.sub(info_data, index, index):byte()
end

-- 写入data：seg_num块，info_data数据
function BlockInfo:fill_data(seg_num, info_data)
    local index = seg_num * 16
    if self.last_seg then
        for i = 1, BLOCKLENGTH do
            if i <= self.last_seg_length then
                self.data[index + i + 1] = get_byte(info_data, i)
            else
                self.data[index + i + 1] = 0
            end
        end
    else
        for i = 1, BLOCKLENGTH do
            self.data[index + i + 1] = get_byte(info_data, i)
        end
    end
    if seg_num == self.current_seg_num and seg_num + 1 < self.seg_num then
        self.current_seg_num = seg_num + 1
    end
    self.data[1] = self.current_seg_num
end

--data1：seg_num，块序号
--data2~17：数据[block内容:（编码1字节）+ （长度1字节）+（数据14字节）]
function BlockInfo:set_firtst_block_info(config_data, ctx)
    if not config_data or #config_data < INFOLEN then
        log:error('[BlockInfo]set first block config data invalid length')
        return cc.ReqDataLenInvalid
    end
    local seg_selector = get_byte(config_data, SEGINDEX)
    local length = get_byte(config_data, LENGTHINDEX)
    local data = string.sub(config_data, DATAINDEX, -1)
    local res = self:validate_first_block(length + DATAINDEX, #data)
    if res ~= cc.Success then
        return res
    end

    -- 需要包含前两个字节
    self:reallocate(length + DATAINDEX)
    self:fill_data(seg_selector, data)
    if not self:save() then
        return cc.InvalidFieldRequest
    end
    ipmi.ipmi_operation_log(ctx, 'BIOS', 'Set system info %s block %s successfully', self.prop, 0)
    return cc.Success
end

--data1：seg_num，块序号
--data2~17：数据[16字节]
function BlockInfo:set_other_block_info(config_data, ctx)
    if not config_data or #config_data < DATALEN then
        log:error('[BlockInfo]set other block config data invalid length')
        return cc.ReqDataLenInvalid
    end
    local seg_selector = get_byte(config_data, SEGINDEX)
    local data = string.sub(config_data, DATAINDEX, -1)
    local res = self:validate(seg_selector, #data)
    if res ~= cc.Success then
        return res
    end
    self:fill_data(seg_selector, data)
    if not self:save() then
        return cc.InvalidFieldRequest
    end
    ipmi.ipmi_operation_log(ctx, 'BIOS', 'Set system info %s block %s successfully', self.prop, seg_selector)
    return cc.Success
end

function BlockInfo:set_info(config_data, ctx)
    if not config_data or #config_data < SEGLEN then
        log:error('[BlockInfo]config data invalid length')
        return cc.ReqDataLenInvalid
    end
    local seg_selector = get_byte(config_data, SEGINDEX)
    if seg_selector == FIRSTBLOCK then
        return self:set_firtst_block_info(config_data, ctx)
    else
        return self:set_other_block_info(config_data, ctx)
    end
end

local function unit_arr_to_bin(arr_data, seg_selector)
    if not arr_data then
        return ''
    end

    local text = {}
    text[1] = s_pack('I1', seg_selector)
    local len = #arr_data
    for i = 1, len do
        local bin = s_pack('I1', arr_data[i])
        text[i + 1] = bin
    end

    return table.concat(text)
end

function BlockInfo:get_info(seg_selector)
    if seg_selector + 1 > self.seg_num then
        log:error('[BlockInfo]get config data seg %s invalid, seg num is %s', seg_selector, self.seg_num)
        return cc.SensorInvalid, prop_def.ParameterRevision, ''
    end

    local option_data = {}
    local start_index = seg_selector * 16 + 1
    for i = 1, BLOCKLENGTH do
        option_data[i] = self.data[start_index + i] or 0
    end
    return cc.Success, prop_def.ParameterRevision, unit_arr_to_bin(option_data, seg_selector)
end

return BlockInfo