-- 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_find = string.find
local s_byte = string.byte
local s_format = string.format
local s_sub = string.sub

local memory_type_detail = {}
memory_type_detail.__index = memory_type_detail

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

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

local characteristic = {
  'Reserved', 'Other', 'Unknown', 'Fast-paged', 'Static column', 'Pseudo-static', 'RAMBUS', 'Synchronous', 'CMOS',
  'EDO', 'Window DRAM', 'Cache DRAM', 'Non-volatile', 'Registered (Buffered)', 'Unbuffered (Unregistered)',
  'LRDIMM'
}

function memory_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

local memory_operating_mode_capabilities = {}
memory_operating_mode_capabilities.__index = memory_operating_mode_capabilities

function memory_operating_mode_capabilities.new(v)
  return setmetatable({v}, memory_operating_mode_capabilities)
end

function memory_operating_mode_capabilities:other()
  return self[1] & 0x0002 == 0x0002
end

function memory_operating_mode_capabilities:unknown()
  return self[1] & 0x0004 == 0x0004
end

function memory_operating_mode_capabilities:volatile_memory()
  return self[1] & 0x0008 == 0x0008
end

function memory_operating_mode_capabilities:byte_accessible_persistent_memory()
  return self[1] & 0x0010 == 0x0010
end

function memory_operating_mode_capabilities:block_accessible_persistent_memory()
  return self[1] & 0x0020 == 0x0020
end

local memory_attributes = {}
memory_attributes.__index = memory_attributes

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

function memory_attributes:rank()
  return self[1] & 0x0F
end

local M = {}

local function fill_t_version_21(t, fields, strings)
    t.physical_memory_array_handle = fields[1]
    t.memory_error_information_handle = fields[2]
    t.total_width = fields[3]
    t.data_width = fields[4]
    t.size = fields[5]
    t.form_factor = fields[6]
    t.device_set = fields[7]
    t.device_locator = strings[fields[8]]
    t.bank_locator = strings[fields[9]]
    t.memory_type = fields[10]
    t.type_detail = memory_type_detail.new(fields[11])
end

local function fill_t_version_23(t, fields, strings)
    t.speed = fields[1]
    t.manufacturer = strings[fields[2]]
    t.serial_number = strings[fields[3]]
    t.asset_tag = strings[fields[4]]
    t.part_number = strings[fields[5]]
end

local function fill_t_version_32(t, fields, strings)
    t.memory_technology = fields[1]
    t.memory_operating_mode_capability = memory_operating_mode_capabilities.new(fields[2])
    t.firmware_version = strings[fields[3]]
    t.module_manufacturer_id = fields[4]
    t.module_product_id = fields[5]
    t.memory_subsystem_controller_manufacturer_id = fields[6]
    t.memory_subsystem_controller_product_id = fields[7]
    t.non_volatile_size = fields[8]
    t.volatile_size = fields[9]
    t.cache_size = fields[10]
    t.logical_size = fields[11]
end

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

  local t = {}

  -- 2.1+
  if length >= 0x15 then
    local fields = read_fields('HHHHHBBBBBH')
    fill_t_version_21(t, fields, strings)
  end

  -- 2.3+
  if length >= 0x1B then
    local fields = read_fields('HBBBB')
    fill_t_version_23(t, fields, strings)
  end

  -- 2.6+
  if length >= 0x1C then
    local fields = read_fields('B')
    t.attributes = memory_attributes.new(fields[1])
  end

  -- 2.7+
  if length >= 0x22 then
    local fields = read_fields('I4H')
    t.extended_size = fields[1]
    t.configured_memory_speed = fields[2]
    log:info('configured_memory_speed:%s', t.configured_memory_speed)
  end

  -- 2.8+
  if length >= 0x28 then
    local fields = read_fields('HHH')
    t.minimum_voltage = fields[1]
    t.maximum_voltage = fields[2]
    t.configured_voltage = fields[3]
  end

  -- 3.2+
  if length >= 0x54 then
    local fields = read_fields('BHBHHHHJJJJ')
    fill_t_version_32(t, fields, strings)
  end

  -- 3.3+
  if length >= 0x5C then
    local fields = read_fields('I4I4')
    t.extended_speed = fields[1]
    t.extended_configured_memory_speed = fields[2]
  end
  return {Handle = handle, Length = length, Type = type, Strings = strings, Fields = t}
end

-- 根据smbios信息中的device_locator计算出CpuID，ChannelID和DimmID拼接成alias_name
function M.get_alias_name_in_smbios(entry)
    local CPU_ID = '@'
    local CHANNEL_ID = '#'
    local DIMM_ID = '$'
    local NODE_ID = '*'
    local FRAME_ID = '^'
    local IDS = CPU_ID .. CHANNEL_ID .. DIMM_ID .. NODE_ID .. FRAME_ID
    local silk_template = 'DIMM@#$'
    local vals = {}
    for i = 1, #silk_template do
        local c = s_sub(silk_template, i, i)
        local d = s_byte(entry.Fields.device_locator, i)
        if s_find(IDS, c, 1, true) and d >= 0x30 and d <= 0x39 then
            vals[c] = (vals[c] or 0) * 10 + (d - 0x30)
        elseif s_find(IDS, c, 1, true) and d >= 0x41 and d <= 0x46 then
            vals[c] = (vals[c] or 0) * 10 + (d - 0x37)
        elseif c ~= '%' and s_byte(c, 1) ~= d then
            break
        end
    end
    if vals[CPU_ID] and vals[CHANNEL_ID] and vals[DIMM_ID] then
      return s_format('%d%d%d%d', vals[CPU_ID], 0, vals[CHANNEL_ID], vals[DIMM_ID])
    end
end

return M
