-- 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 log                      = require 'mc.logging'
local context                  = require 'mc.context'
local class                    = require 'mc.class'

local jbog_utils               = class()

-- BMC 数据长度（字节）
local SN_LENGTH                = 48 -- 序列号（Serial Number）
local MAC_LENGTH               = 6  -- MAC 地址（6 字节）
local IPV4_LENGTH              = 4  -- IPv4 地址（4 字节）
local IPV6_LENGTH              = 16 -- IPv6 地址（16 字节）

local BOX_BMC_MSG_TYPE <const> = {
    Ipv4 = 0x02,
    Ipv6 = 0x08,
    Mac = 0x20,
    SN = 0x80
}

local config                   = {
    SetBmcIpv4 = {
        func = 0x21,
        command = 4,
        ms = 1,
        rw = 0,
        parameter = 1
    },
    SetBmcIpv6 = {
        func = 0x21,
        command = 4,
        ms = 1,
        rw = 0,
        parameter = 1
    },
    SetBmcMac = {
        func = 0x21,
        command = 4,
        ms = 1,
        rw = 0,
        parameter = 1
    },
    SetBmcSN = {
        func = 0x21,
        command = 4,
        ms = 1,
        rw = 0,
        parameter = 1
    },
    SetBmcId = {
        func = 0x21,
        command = 6,
        ms = 1,
        rw = 0,
        parameter = 1
    }
}

--- 根据协议规范生成 32 位 SMC 命令码，命令格式（高位 → 低位）：
--- [6 位 func][16 位 command][1 位 ms][1 位 rw][8 位 parameter]
local function generate_cmd(cmd)
    -- 提取字段并设置默认值
    local func      = cmd.func or 0
    local command   = cmd.command or 0
    local ms        = cmd.ms or 0
    local rw        = cmd.rw or 0
    local parameter = cmd.parameter or 0

    return (func << 26)
        | (command << 10)
        | ((ms & 1) << 9)
        | ((rw & 1) << 8)
        | (parameter & 0xFF)
end

--- 将 IPv6 的 8 个 16-bit word 压缩为标准文本表示
--- 找到最长连续零块，用 "::" 替代（仅一次）
--- 例如：{0,0,0,0,0,0xffff,0xc00a,0x8001} → "::ffff:c00a:8001"
local function compress_ipv6(words)
    local n = #words
    if n ~= 8 then return nil end -- 安全检查

    -- Step 1: 找最长连续零段（长度 >= 2）
    local best_start, best_len = 0, 0
    local cur_start = nil

    for i = 1, n do
        if words[i] == 0 then
            if not cur_start then cur_start = i end
        else
            if cur_start then
                local len = i - cur_start
                if len > best_len then
                    best_start, best_len = cur_start, len
                end
                cur_start = nil
            end
        end
    end
    if cur_start then
        local len = n + 1 - cur_start
        if len > best_len then
            best_start, best_len = cur_start, len
        end
    end

    -- 如果没有长度 >=2 的零段，不压缩
    if best_len < 2 then
        local hexes = {}
        for _, w in ipairs(words) do
            table.insert(hexes, string.format('%x', w))
        end
        return table.concat(hexes, ':')
    end

    -- 构建压缩后的字符串
    local left = {}
    local right = {}

    -- 左侧非零段 [1, best_start - 1]
    for i = 1, best_start - 1 do
        table.insert(left, string.format('%x', words[i]))
    end

    -- 右侧非零段 [best_start + best_len, n]
    for i = best_start + best_len, n do
        table.insert(right, string.format('%x', words[i]))
    end

    -- 拼接：left + "::" + right
    if #left == 0 and #right == 0 then
        return "::"
    elseif #left == 0 then
        return "::" .. table.concat(right, ':')
    elseif #right == 0 then
        return table.concat(left, ':') .. "::"
    else
        return table.concat(left, ':') .. "::" .. table.concat(right, ':')
    end
end

-- 辅助函数：安全地将十进制字符串转为 0～255 的字节
local function safe_byte_dec(str)
    if not str then return nil end
    local num = tonumber(str)
    return (num and num >= 0 and num <= 255) and string.char(num) or nil
end

-- 辅助函数：安全地将十六进制字符串（1～2位）转为字节
local function safe_byte_hex(str)
    if not str then return nil end
    -- 补前导零成偶数长度（如 "a" -> "0a"）
    if #str % 2 == 1 then str = "0" .. str end
    local num = tonumber(str, 16)
    return (num and num >= 0 and num <= 255) and string.char(num) or nil
end

local function expand_ipv6(ipv6_addr)
    if type(ipv6_addr) ~= "string" then
        return nil
    end
    -- Check for multiple "::"
    if string.match(ipv6_addr, "::.*::") then
        return nil
    end
    -- 去除可能的 zone ID（如 %eth0），因打包不需要
    ipv6_addr = string.gsub(ipv6_addr, "%%.*", "")
    -- 全零地址简写
    if ipv6_addr == "::" then
        return "0000:0000:0000:0000:0000:0000:0000:0000"
    end
    -- 检查是否包含 ::（压缩形式）
    local has_double_colon = string.find(ipv6_addr, "::", 1, true)
    -- 统计现有段数（非空的十六进制段）
    local segments = {}
    for seg in string.gmatch(ipv6_addr, "[^:]+") do
        if seg ~= "" then
            -- 验证每段是否为合法十六进制（1～4位）
            if not string.match(seg, "^[A-Fa-f0-9]+$") or #seg > 4 then
                return nil
            end
            table.insert(segments, seg)
        end
    end

    local existing_count = #segments
    if existing_count > 8 then
        return nil -- 超过8段，非法
    end

    if not has_double_colon then
        -- 无压缩，必须正好8段
        if existing_count ~= 8 then
            return nil
        end
    else
        -- 有 ::，计算需要插入的 0000 段数
        local zero_segments_needed = 8 - existing_count
        if zero_segments_needed < 0 then
            return nil -- 如 "1:2:3:4:5:6:7:8:9::" 非法
        end

        -- 分割 :: 左右部分
        local left_part, right_part = string.match(ipv6_addr, "^([A-Fa-f0-9:]*)::([A-Fa-f0-9:]*)$")
        if not left_part or not right_part then
            return nil
        end

        -- 重建段列表
        local new_segments = {}

        -- 左侧段
        if left_part ~= "" then
            for seg in string.gmatch(left_part, "[^:]+") do
                table.insert(new_segments, seg)
            end
        end

        -- 插入 zero_segments_needed 个 "0000"
        for _ = 1, zero_segments_needed do
            table.insert(new_segments, "0000")
        end

        -- 右侧段
        if right_part ~= "" then
            for seg in string.gmatch(right_part, "[^:]+") do
                table.insert(new_segments, seg)
            end
        end

        if #new_segments ~= 8 then
            return nil
        end
        segments = new_segments
    end

    -- 补前导零到4位，并拼接
    for i = 1, #segments do
        segments[i] = string.lower(segments[i])
        segments[i] = string.rep("0", 4 - #segments[i]) .. segments[i]
    end

    return table.concat(segments, ":")
end

-- IPv4 打包：输入 "192.168.1.1" → 4字节二进制
local function dal_ipv4_addr_pack(data)
    if not data or data == '' then
        log:notice('The data is empty, converted to the default value 0.0.0.0')
        data = '0.0.0.0'
    end

    local parts = {}
    for part in string.gmatch(data, '[^%.]+') do
        local byte = safe_byte_dec(part)
        if not byte then
            log:info('Invalid IPv4 octet: %s', part)
            return string.rep('\0', 4) -- 返回 0.0.0.0
        end
        table.insert(parts, byte)
    end

    if #parts ~= 4 then
        log:notice('Invalid IPv4 format: expected 4 octets, got %d', #parts)
        return string.rep('\0', 4)
    end

    return table.concat(parts)
end

-- IPv6 打包：输入 "2001:db8::1" → 16字节二进制
local function dal_ipv6_addr_pack(data)
    if not data or data == '' then
        log:notice('The data is empty, converted to the default value ::')
        data = '::'
    end

    -- 展开 IPv6
    local expanded = expand_ipv6(data)
    -- 如果 expand 失败，尝试直接处理
    if not expanded or #expanded == 0 then
        expanded = data
    end

    local parts = {}
    for segment in string.gmatch(expanded, '[^:]+') do
        -- 每段必须是 1～4 个十六进制字符
        if #segment > 4 then
            log:notice('Invalid IPv6 segment: %s (too long)', segment)
            return string.rep('\0', 16)
        end
        -- 补前导零到 4 位
        segment = string.rep('0', 4 - #segment) .. segment
        -- 拆成两个字节
        local byte1 = safe_byte_hex(string.sub(segment, 1, 2))
        local byte2 = safe_byte_hex(string.sub(segment, 3, 4))
        if not byte1 or not byte2 then
            log:notice('Invalid hex in IPv6 segment: %s', segment)
            return string.rep('\0', 16)
        end
        table.insert(parts, byte1)
        table.insert(parts, byte2)
    end

    if #parts ~= 16 then
        log:notice('Invalid IPv6: expected 16 bytes, got %d', #parts)
        return string.rep('\0', 16)
    end

    return table.concat(parts)
end

-- MAC 打包：输入 "aa:bb:cc:dd:ee:ff" → 6字节二进制
local function dal_mac_pack(data)
    if not data or data == '' then
        log:notice('The data is empty, converted to the default value 00:00:00:00:00:00')
        data = '00:00:00:00:00:00'
    end

    local parts = {}
    for part in string.gmatch(data, '[^:]+') do
        -- MAC 段应为 1～2 位十六进制
        if #part > 2 then
            log:notice('Invalid MAC octet: %s', part)
            return string.rep('\0', 6)
        end
        local byte = safe_byte_hex(part)
        if not byte then
            log:notice('Invalid hex in MAC: %s', part)
            return string.rep('\0', 6)
        end
        table.insert(parts, byte)
    end

    if #parts ~= 6 then
        log:notice('Invalid MAC format: expected 6 octets, got %d', #parts)
        return string.rep('\0', 6)
    end

    return table.concat(parts)
end

--- 向 SMC 芯片写入命令和数据（内部封装）
function jbog_utils:_block_write(cmd, data)
    if not self.chip then
        log:notice(' _block_write: chip not initialized')
        return false
    end
    self.chip:Write(context.new(), cmd, data)
    return true
end

--- 从 SMC 芯片读取指定长度的数据（内部封装）
function jbog_utils:_block_read(cmd, len)
    if not self.chip then
        log:notice('smc._block_read: chip not initialized')
        return nil
    end
    return self.chip:Read(context.get_context_or_default(), cmd, len)
end

-- === 公共写接口 ===

function jbog_utils:set_box_bmc_ipv4(data)
    log:info('set ipv4 addr %s to box', data)
    local data = dal_ipv4_addr_pack(data)
    -- 协议要求：首字节为消息类型，后跟原始 IPv4 字节（4 字节）
    return self:_block_write(self.cmds.SetBmcIpv4, string.char(BOX_BMC_MSG_TYPE.Ipv4) .. data:sub(1, IPV4_LENGTH))
end

function jbog_utils:set_box_bmc_ipv6(data)
    log:info('set ipv6 addr %s to box', data)
    local data = dal_ipv6_addr_pack(data)
    -- 协议要求：首字节为消息类型，后跟原始 IPv6 字节（16 字节）
    return self:_block_write(self.cmds.SetBmcIpv6, string.char(BOX_BMC_MSG_TYPE.Ipv6) .. data:sub(1, IPV6_LENGTH))
end

function jbog_utils:set_box_bmc_mac(data)
    log:info('set mac %s to box', data)
    local data = dal_mac_pack(data)
    -- 协议要求：首字节为消息类型，后跟 6 字节 MAC
    return self:_block_write(self.cmds.SetBmcMac, string.char(BOX_BMC_MSG_TYPE.Mac) .. data:sub(1, MAC_LENGTH))
end

function jbog_utils:set_box_bmc_sn(data)
    -- 序列号字段固定 48 字节：超长截断，不足补 '\0'
    data = data:sub(1, SN_LENGTH)
    data = data .. string.rep('\0', SN_LENGTH - #data)
    return self:_block_write(self.cmds.SetBmcSN, string.char(BOX_BMC_MSG_TYPE.SN) .. data)
end

function jbog_utils:set_box_bmc_id(data)
    log:info('set bmc id %s to box', data)
    return self:_block_write(self.cmds.SetBmcId, string.char(data))
end

function jbog_utils:ctor(ref_chip)
    self.chip = ref_chip
    self.cmds = {}
    for k, spec in pairs(config) do
        self.cmds[k] = generate_cmd(spec)
    end
end

jbog_utils.generate_cmd = generate_cmd
jbog_utils.compress_ipv6 = compress_ipv6
jbog_utils.safe_byte_dec = safe_byte_dec
jbog_utils.safe_byte_hex = safe_byte_hex
jbog_utils.expand_ipv6 = expand_ipv6
jbog_utils.dal_ipv4_addr_pack = dal_ipv4_addr_pack
jbog_utils.dal_ipv6_addr_pack = dal_ipv6_addr_pack
jbog_utils.dal_mac_pack = dal_mac_pack


return jbog_utils
