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

local PORT_INFO_STATUS_SPEED_OFFSET <const> = 64
local PORT_TEMP_OFFSET <const> = 0x33
local LINK_WIDTH_ABILITY_OFFSET <const> = 17
local USAGE_TYPE_OFFSET <const> = 26
local MEDIUM_TYPE_OFFSET <const> = 26
local AUTO_SPEED_NEGOTIATION_OFFSET <const> = 72
local PORT_LLDP_OFFSET <const> = 129
local CARD_LLDP_OFFSET <const> = 129
local OPTICAL_PRESENCE_OFFSET <const> = 57

local HyperCard_chip = {}

HyperCard_chip.__index = HyperCard_chip

function HyperCard_chip.new(bmc_chip, cpld_chip, position)
    return setmetatable({ bmc_chip = bmc_chip, cpld_chip = cpld_chip, position = position}, HyperCard_chip)
end

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

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

    return numbers
end

local function chip_read(chip, read_offset, read_len)
    local retry_count = 3
    for _ = 1, retry_count do
        local ok, recv_data = pcall(function ()
            return chip:Read(context.get_context_or_default(), read_offset, read_len)
        end)
        if ok then
            return string_to_number(recv_data)
        end
    end
    return false
end

local function chip_write(chip, write_offset, data)
    local retry_count = 3
    local ok, err
    for _ = 1, retry_count do
        ok, err = pcall(function ()
            return chip:Write(context.get_context_or_default(), write_offset, data)
        end)
        if ok then
            return true
        else
            log:error('chip write error: %s', err)
        end
    end
    return false
end

function HyperCard_chip:GetHyperMacAddress(position)
    log:debug("start to update mac address")
    local mac_address
    local fru_objs = client:GetProductObjects()
    for _, fru_obj in pairs(fru_objs) do
        if fru_obj.extra_params.Position == self.position then
            mac_address = string.sub(fru_obj.ProductCustomInfo, 1, 17)
            break
        end
    end
    if not mac_address then
        log:debug('Get hyper card mac address failed')
        return mac_address
    end
    return mac_address
end

function HyperCard_chip:SetPortLLDPEnable(port, value)
    local set_value = 1 << port.PortID
    log:debug('port%s lldp turn %s, set_value is %s', port.PortID, value, set_value)
    if port.PortID < 0 or port.PortID > 5 then
        return false, false
    end

    local chip_data = chip_read(self.cpld_chip, PORT_LLDP_OFFSET, 1)
    if not chip_data then
        return false, false
    end

    if value then
        chip_data[1] = chip_data[1] | set_value
    else
        chip_data[1] = chip_data[1] & (~set_value)
    end

    local ret = chip_write(self.cpld_chip, PORT_LLDP_OFFSET, chip_data)
    if not ret then
        log:notice("SetPortLLDPEnable PortID= %s data = %s, ret = %s", port.PortID, chip_data, ret)
    end
    return ret, ret
end

local function parse_temp(temp)
    local pars_temp
    -- 0xfe 表示温度不在位，NA状态；0xfd表示温度在位，但获取失败
    if temp == 0xfe or temp == 0xfd then
        return temp
    end

    --最高位为符号位，0代表正数，1代表负数
    if temp & 0x80 == 0 then
        pars_temp = temp
    else
        pars_temp = 0 - (temp & 0x7f)
    end
    --温度高于127则保持127不变，低于-127则保持-127不变
    if pars_temp > 127 then
        pars_temp = 127
    elseif pars_temp < -127 then
        pars_temp = -127
    end

    return pars_temp
end

function HyperCard_chip:GetPortTemp()
    local port_map_temp = {}
    local port_temps = chip_read(self.bmc_chip, PORT_TEMP_OFFSET, 6)
    if not port_temps then
        return nil
    end
    for i = 1, #port_temps do
        table.insert(port_map_temp, parse_temp(port_temps[i]))
        log:debug('port%s GetPortTemp is %s', i, parse_temp(port_temps[i]))
    end
    return port_map_temp
end

function HyperCard_chip:GetLinkWidthAbility()
    local input = chip_read(self.bmc_chip, LINK_WIDTH_ABILITY_OFFSET, 9)
    if not input then
        return nil
    end
    local max_index = 0
    local speeds = {"NA port", "10M", "100M", "1000M", "10G", "25G", "40G", "50G", "100G", "200G", "400G"}
    for i, _ in ipairs(input) do
        if input[i] + 1 <= #speeds then
            max_index = (input[i] + 1) > max_index and (input[i] + 1) or max_index
        end
        log:debug('port%s GetLinkWidthAbility is %s', i, speeds[input[i] + 1])
    end
    log:debug('GetLinkWidthAbility is %s', speeds[max_index])
    return speeds[max_index] or "Unknown"
end

function HyperCard_chip:GetPortSpeed()
    local input = chip_read(self.bmc_chip, PORT_INFO_STATUS_SPEED_OFFSET, 8)
    if not input then
        return nil
    end

    local port_speed = {0, 0, 0, 0, 0, 0, 0, 0}
    local index
    local speeds = {10000, 25000, 40000, 50000, 100000, 200000, 400000, 0}
    for i, _ in ipairs(input) do
        if input[i] ~= 0xff then
            index = (input[i] >> 5) + 1
            if (input[i] & 0x0f) <= #speeds then
                port_speed[index] = speeds[input[i] & 0x0f]
            end
            log:debug('port%s GetPortSpeed is %s', index, port_speed[index])
        end
    end
    return port_speed
end

function HyperCard_chip:GetUsageType()
    local input = chip_read(self.bmc_chip, USAGE_TYPE_OFFSET, 9)
    if not input then
        return nil
    end

    local port_type = {}
    local index
    local types = {"unuse", "management", "service", "bmc"}
    for i, _ in ipairs(input) do
        index = (input[i] >> 2) & 0x03
        port_type[i] = types[index + 1]
        log:debug('port%s GetUsageType is %s', i, port_type[i])
    end
    return port_type
end

function HyperCard_chip:GetMediumType()
    local input = chip_read(self.bmc_chip, MEDIUM_TYPE_OFFSET, 9)
    if not input then
        return nil
    end

    local port_type = {}
    local index
    local types = {"Unknown", "Optical", "Electronic", "Optical-Electronic"}
    for i, _ in ipairs(input) do
        index = input[i] & 0x03
        port_type[i] = types[index + 1]
        log:debug('port%s GetMediumType is %s', i, port_type[i])
    end
    return port_type
end

function HyperCard_chip:GetLinkStatus()
    local input = chip_read(self.bmc_chip, PORT_INFO_STATUS_SPEED_OFFSET, 8)
    if not input then
        return nil
    end

    -- 非无效值，对应port赋值为连接和断开，无效值场景赋值为N/A
    local port_status = {255, 255, 255, 255, 255, 255, 255, 255}
    local index
    local status = {0, 1}
    for i, _ in ipairs(input) do
        if input[i] ~= 0xff then
            index = (input[i] >> 5) + 1
            port_status[index] = status[((input[i] >> 4) & 0x01) + 1]
            log:debug('port%s GetLinkStatus is %s', index, port_status[index])
        end
    end
    return port_status
end

function HyperCard_chip:GetNegotiationInfo()
    local input = chip_read(self.bmc_chip, AUTO_SPEED_NEGOTIATION_OFFSET, 8)
    if not input then
        return nil
    end

    local ports_map = {}
    local index
    local auto_speed_negotiation_status = {false, true, false, false, false, false, false, false}
    local fullduplex_status = {true, false, false, false}
    for i, _ in ipairs(input) do
        if input[i] ~= 0xff then
            index = (input[i] >> 5) & 0x07
            ports_map[index] = {
                full_duplex = fullduplex_status[(input[i] & 0x03) + 1] or false,
                auto_negatiation = auto_speed_negotiation_status[((input[i] >> 2) & 0x07) + 1] or false
            }
            log:debug('port%s auto_negatiation is %s, full_duplex is %s',
                index, ports_map[index].auto_negatiation, ports_map[index].full_duplex)
        end
    end

    return ports_map
end

local function parse_LLDPinfo(lldp_info)
    local ports_map = {}
    for i = 1, 6 do
        local byte = lldp_info[1]
        -- lldp_status 值为1代表报文开启，值为2代表报文关闭，与函数update_oem_smbus_lldpinfo定义一致
        ports_map[i - 1] = {
            lldp_status = 2 - ((byte >> (i - 1)) & 1)
        }
        log:debug('port%s lldp_status is %s', i, 2 - ((byte >> (i - 1))& 1))
    end
    return ports_map
end

function HyperCard_chip:GetLLDPInfo()
    local lldp_info_origin = chip_read(self.cpld_chip, PORT_LLDP_OFFSET, 1)
    if not lldp_info_origin then
        return nil
    end
    local lldp_info = parse_LLDPinfo(lldp_info_origin)
    return lldp_info
end

function HyperCard_chip:GetOpticalPresence()
    local info = chip_read(self.bmc_chip, OPTICAL_PRESENCE_OFFSET, 1)
    if not info then
        return nil
    end

    local port_ops_presence_map = {}
    local byte
    for i = 1, 6 do
        byte = info[1]
        port_ops_presence_map[i] = (byte >> (i - 1)) &1
        log:debug('port%s presence is %s', i, (byte >> (i - 1)) & 1)
    end
    return port_ops_presence_map
end

local MCU = {oem_smbus = HyperCard_chip}

return MCU
