-- Copyright (c) 2026 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 singleton = require 'mc.singleton'
local class = require 'mc.class'
local log = require 'mc.logging'
local file_sec = require 'utils.file'
local context = require 'mc.context'
local vos = require 'utils.vos'
local utils = require 'mc.utils'

local smc_dump_service = class()

local function smc_command_to_offset(cmd_obj)
    -- 参数说明
    -- func: function字段, 6位无符号整数, 范围0-63
    -- cmd: command字段, 16位无符号整数, 范围0-65535
    -- ms: MS字段, 1位无符号整数, 值0或1
    -- rw: RW字段, 1位无符号整数, 值0或1
    -- param: parameter字段, 8位无符号整数, 范围0-255
    -- 返回值: offset, 32位无符号整数

    -- 计算各字段的位移值(使用算术乘法模拟左移)
    local shift_func = cmd_obj.Function * 67108864 -- 2^26 = 67108864
    local shift_cmd = cmd_obj.Command * 1024       -- 2^10 = 1024
    local shift_ms = cmd_obj.MS * 512              -- 2^9 = 512
    local shift_rw = cmd_obj.RW * 256              -- 2^8 = 256
    local param = 0                                -- 计算offset时默认为0

    -- 组合各字段
    local offset = shift_func + shift_cmd + shift_ms + shift_rw + param
    return offset
end

function smc_dump_service:on_add_object(class_name, object, position)
    local switch = {
        ['SmcCmdList'] = function ()
            if object.Type then
                local board_key = object.Type .. position
                self.dfx_info_list[board_key] = object

                -- 将板对象config包含的命令按照offset从小到大排序
                if not self.cmd_key_list[board_key] then
                    self.cmd_key_list[board_key] = {}
                end
                for k, v in pairs(object.Config) do
                    table.insert(self.cmd_key_list[board_key], k)
                    v.Offset = smc_command_to_offset(v)
                end
                table.sort(self.cmd_key_list[board_key], function (a, b)
                    return object.Config[a].Offset < object.Config[b].Offset
                end)

                -- 将板对象按照板类型顺序排序
                if not self.dfx_info_list.board_key_list then
                    self.dfx_info_list.board_key_list = {}
                end
                table.insert(self.dfx_info_list.board_key_list, board_key)
                table.sort(self.dfx_info_list.board_key_list, function (a, b)
                    return a < b
                end)
            end
        end
    }

    if switch[class_name] then
        switch[class_name]()
        log:notice('[smc_dump] add smc cmd list object, class: %s, position: %s', class_name, position)
    end
end

function smc_dump_service:on_delete_object(class_name, object, position)
    local switch = {
        ['SmcCmdList'] = function ()
            if object.Type then
                local board_key = object.Type .. position
                self.dfx_info_list[board_key] = nil
            end
        end
    }

    if switch[class_name] then
        switch[class_name]()
        log:notice('[smc_dump] delete smc cmd list object, class: %s, position: %s', class_name, position)
    end
end

function smc_dump_service:ctor()
    self.dfx_info_list = {}
    self.cmd_key_list = {}
    self.is_collecting = false
end

local function byte_string_format(str)
    local numbers = {}
    local byte
    if not str then
        return ''
    end

    for i = 1, #str do
        byte = string.byte(str, i)
        numbers[#numbers + 1] = string.format("%02X", byte)
    end

    return table.concat(numbers, ' ')
end

local function get_value(chip, offset, len)
    local ok, msg = pcall(function ()
        return chip:Read(context.get_context_or_default(), offset, len)
    end)

    if not ok then
        return false, msg
    end

    return true, byte_string_format(msg)
end

local function pad_string_right(str, total_length)
    -- 使用string.format 进行右对齐并填充空格
    return string.format("%-" .. total_length .. "s", str)
end

function smc_dump_service:on_dump_cb_process(src_path)
    if self.is_collecting then
        log:notice('[smc_dump]smc_cmd_list dump is running')
        return
    end
    self.is_collecting = true
    log:notice('[smc_dump]smc_cmd_list dump start')
    local file_name = src_path .. '/cpld_reg_info'
    local file = file_sec.open_s(file_name, 'a+')
    if not file then
        log:error('[smc_dump]smc_cmd_list dump open file failed')
        self.is_collecting = false
        return
    end
    local ok, msg, board_obj
    local t_smc = {}
    table.insert(t_smc, 'Collect Start ' .. os.date("%Y-%m-%d %H:%M:%S", os.time()) .. '\n')
    for _, board_key in pairs(self.dfx_info_list.board_key_list) do
        board_obj = self.dfx_info_list[board_key]
        if board_obj then
            ok, msg = pcall(function ()
                table.insert(t_smc, self:dump_smc_cmd_list(board_obj, board_key))
            end)
            if not ok then
                log:error('[smc_dump]smc_cmd_list dump failed, err:%s', msg)
            end
        end
    end
    table.insert(t_smc, 'Collect End ' .. os.date("%Y-%m-%d %H:%M:%S", os.time()) .. '\n')
    file:write(table.concat(t_smc))
    utils.chmod(file_name, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP) -- 设置日志文件权限为640
    file:close()
    self.is_collecting = false
    log:notice('[smc_dump]smc_cmd_list dump success')
end

function smc_dump_service:on_dump_cb(ctx, dest_path)
    local src_path = '/data/var/log/general_hardware'
    self:on_dump_cb_process(src_path)
    if file_sec.check_real_path_s(src_path) == 0 then
        vos.system_s('/bin/cp', '-r', src_path .. '/.', dest_path)
    end
end

local board_type_table = {
    ['CLU'] = 'FANBOARD',
    ['EXU'] = 'EXPBOARD',
    ['BCU'] = 'CPUBOARD',
    ['SEU'] = 'HDDBackplane',
    ['IEU'] = 'RISERCARD'
}

function smc_dump_service:dump_smc_cmd_list(board_obj, board_key)
    local board_smc = {}
    local offset, ok, msg_table, msg_data, byte_data
    local board_type = board_type_table[board_obj.Type] or 'Unknown'
    log:notice('[smc_dump]smc_cmd_list dump start Type: %s, Slot: %s', board_obj.Type, board_obj.Slot)
    table.insert(board_smc, '--------------------------------------------------' .. '\n')
    table.insert(board_smc, board_type .. board_obj.Slot .. ' CPLD INFO' .. '\n')
    table.insert(board_smc, '--------------------------------------------------' .. '\n')
    table.insert(board_smc, 'offset      device num   data' .. '\n')
    table.insert(board_smc, '--------------------------------------------------' .. '\n')

    for _, value in pairs(self.cmd_key_list[board_key]) do
        byte_data = board_obj.Config[value]
        if byte_data.Collected ~= 1 or byte_data.Length * (byte_data.Parameter + 1) > 512 then
            goto continue
        end
        offset = byte_data.Offset
        ok, msg_table = get_value(board_obj.Chip, offset, byte_data.Length * (byte_data.Parameter + 1))
        if not ok then
            log:error('[smc_dump]Function = %s, Command = %s, Parameter = %s, Length = %s, get_value failed, err:%s',
                byte_data.Function, byte_data.Command, byte_data.Parameter, byte_data.Length, msg_table)
        end
        for i = 1, (byte_data.Parameter + 1), 1 do
            table.insert(board_smc,
                pad_string_right(i == 1 and string.format("%08X", offset) or '', 12))
            table.insert(board_smc, pad_string_right(string.format("%02d", i), 12))
            msg_data = ok and string.sub(
                msg_table, 3 * (i - 1) * byte_data.Length + 1, 3 * i * byte_data.Length)
            or 'NA'
            table.insert(board_smc, msg_data .. '\n')
        end
        ::continue::
    end
    return table.concat(board_smc)
end

return singleton(smc_dump_service)