-- 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 'mc.logging'
local s_byte = string.byte
local s_sub = string.sub

local M = {}
local processor_status = {}
processor_status.__index = processor_status

local processor_type_detail = {}
processor_type_detail.__index = processor_type_detail

function processor_type_detail.new(data)
    return setmetatable({data}, processor_type_detail)
end

function processor_type_detail:value()
    return self[1]
end

local characteristic = {
    "Reserved", "Unknown", "64-bit Capable", "Multi-Core", "Hardware Thread",
    "Execute Protection", "Enhanced Virtualization", "Power/Performance Control"
}

function processor_type_detail:tostring()
    local data = self[1]
    local result = {}
    for _, v in ipairs(characteristic) do
      if data & 0x01 ~= 0 then
        result[#result + 1] = v
      end
      data = data >> 1
    end

    return table.concat(result, '| ')
end

function processor_status.new(data)
    return setmetatable({data}, processor_status)
end

function processor_status:socket_populated()
    return self[1] & 0x40 == 0x40
end

function processor_status:cpu_status()
    return self[1] & 0x07
end

local function decode_voltage(v)
    if (v & 0x80) == 0x80 then
        return {(v & 0x7F) / 10.0}
    end

    local result = {}
    if v & 0x04 == 0x04 then -- 2 – 2.9V
        result[#result + 1] = 2.9
    end

    if v & 0x02 == 0x02 then -- 1 – 3.3V
        result[#result + 1] = 3.3
    end

    if v & 0x01 == 0x01 then -- 5V
        result[#result + 1] = 5.0
    end
    return result
end

local function get_core_enabled(length, read_fields, t)
    -- 3.0+SMBios协议
    if length >= 0x30 then
        local fields = read_fields('HHH')
        t.core_count_2 = fields[1]
        t.cores_enabled_2 = fields[2]
        t.thread_count_2 = fields[3]
    end
end

local function get_thread_enabled(length, read_fields, t)
    -- 3.6+SMBios协议
    if length >= 0x32 then
      local fields = read_fields('H')
      t.thread_enabled = fields[1]
      log:debug('thread_enaled:%s', t.thread_enabled)
  end
end

function M.decode(type, length, handle, read_fields, strings)
    if length < 0x1A then
        return
    end

    local t = {}
    -- 根据2.0+版本SMBios协议解析
    if length >= 0x1A then
        local fields = read_fields('BBBBJBBHHHBB')
        t.socket_designation = strings[fields[1]]
        t.processor_type = fields[2]
        t.processor_family = fields[3]
        t.processor_manufacturer = strings[fields[4]]
        t.processor_id = fields[5]
        t.processor_version = strings[fields[6]]
        t.voltage = decode_voltage(fields[7])
        t.external_clock = fields[8]
        t.max_speed = fields[9]
        t.current_speed = fields[10]
        t.status = processor_status.new(fields[11])
        t.processor_upgrade = fields[12]
    end

    -- 2.1+版本SMBios协议
    if length > 0x1A then
        local fields = read_fields('HHH')
        t.l1cache_handle = fields[1]
        t.l2cache_handle = fields[2]
        t.l3cache_handle = fields[3]
    end

    -- 2.3+SMBios协议
    if length >= 0x23 then
        local fields = read_fields('BBB')
        t.serial_number = strings[fields[1]]
        t.asset_tag = strings[fields[2]]
        t.part_number = fields[3] > 0 and strings[fields[3]] or ""
    end

    -- 2.5+SMBios协议
    if length >= 0x28 then
        local fields = read_fields('BBBH')
        t.core_count = fields[1]
        t.cores_enabled = fields[2]
        t.thread_count = fields[3]
        t.processor_characteristics = processor_type_detail.new(fields[4]):tostring()
    end

    -- 2.6+SMBios协议
    if length >= 0x2A then
        local fields = read_fields('H')
        t.processor_family_2 = fields[1]
    end

    get_core_enabled(length, read_fields, t)
    get_thread_enabled(length, read_fields, t)

    return {Handle = handle, Length = length, Type = type, Strings = strings, Fields = t}
end

-- 按照CPU@@的格式解析socket_designation字段，获得物理ID，再推算出逻辑ID作为alias_name
function M.get_alias_name_in_smbios(entry)
    local silk_template = 'CPU@@'
    local cpu_physics_id = 0
    local length = math.min(#silk_template, #entry.Fields.socket_designation)
    for i = 1, length do
        local c = s_sub(silk_template, i, i)
        local d = s_byte(entry.Fields.socket_designation, i)
        if c == '@' and d >= 0x30 and d <= 0x39 then
            cpu_physics_id = cpu_physics_id * 10 + (d - 0x30)
        elseif s_byte(c, 1) ~= d then
            log:error("Invalid socket_designation format:%s", entry.Fields.socket_designation)
            return
        end
    end
    -- 当前统一用逻辑ID=物理ID-1方式计算
    return cpu_physics_id - 1
end

return M
