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

local cmd_handler = {}

local function create_link_status_pattern()
    return bs.new([[<<
        response_code:16/big,
        reason_code:16/big,
        extended_speed_duplex:8,
        tx_flow_control:1,
        rx_flow_control:1,
        link_partner8:2,
        serdes_link:1,
        oem_link_speed:1,
        rsvd1:2,
        rsvd2:1,
        link_partner1:1,
        link_partner2:1,
        link_partner3:1,
        link_partner4:1,
        link_partner5:1,
        link_partner6:1,
        link_partner7:1,
        link_flag:1,
        speed_duplex:4,
        negotiate_flag:1,
        negotiate_complete:1,
        parallel_detection:1,
        rsvd3:24,
        host_nc_driver_status_indication:1,
        rsvd4:7,
        oem_link_status:32/big
    >>]])
end

local function parse_link_status_data(r)
    local speed = {10, 10, 100, 100, 100, 1000, 1000, 10000, 20000, 25000, 40000, 50000,
                   100000, 2500, 0, 1000, 200000, 400000, 800000}
    local full_duplex_flag = {false, true, false, false, true, false, true, true, true, true,
                              true, true, true, true, false, false}
    local link_switch = {[0x0] = 'Disconnected', [0x1] = 'Connected', [0xFF] = 'N/A'}
    local speed_mps = speed[r.speed_duplex] or 0
    if r.speed_duplex == 0xF then
        speed_mps = speed[r.extended_speed_duplex] or 0
    end
    local link_status = link_switch[r.link_flag] or 'N/A'
    local full_duplex = full_duplex_flag[r.speed_duplex] or false
    local auto_speed_negotiation = r.negotiate_flag == 1
    return link_status, speed_mps, auto_speed_negotiation, full_duplex
end

local function handle_link_status(port)
    local resp_data = port.ncsi_config_obj:Test({
        channel_id = port:get_port_id(),
        package_id = port.package_id,
        packet_type = 0x0A,
        expect_rsp_packet_type = 0x8A
    }):value()
    if resp_data == nil then
        log:error('get link status failed')
        return 'get link status failed'
    end
    local pattern = create_link_status_pattern()
    local r = pattern:unpack(resp_data, true)
    local link_status, speed_mps, auto_speed_negotiation, full_duplex = parse_link_status_data(r)
    local result = string.format('LinkStatus:%s, SpeedMbps:%d, AutoSpeedNegotiation:%s, FullDuplex:%s',
        link_status,
        speed_mps,
        auto_speed_negotiation and 'true' or 'false',
        full_duplex and 'true' or 'false')

    log:notice('%s get %s', port.NodeId, result)
    return result
end

local function create_vendor_info_pattern()
    return bs.new([[<<
        response_code:16/big,
        reason_code:16/big,
        ncsi_version_major:8,
        ncsi_version_minor:8,
        ncsi_version_update:8,
        ncsi_version_alpha1:8,
        rsvd0:24,
        ncsi_version_alpha2:8,
        firmware_name:12/string,
        firmware_version_1:8,
        firmware_version_2:8,
        firmware_version_3:16/big,
        pci_did:16/big,
        pci_vid:16/big,
        pci_ssid:16/big,
        pci_svid:16/big,
        manufacturer_id:32/big
    >>]])
end

local function bsd_to_string(v)
    if v & 0xF0 == 0xF0 then
        return tostring(v)
    elseif v < 0x10 then
        return '0' .. tostring(v)
    end
    return tostring((v // 16) * 10)
end

local function build_version_strings(r)
    local v1 = bsd_to_string(r.firmware_version_1 or 0)
    local v2 = bsd_to_string(r.firmware_version_2 or 0)
    return string.format('%s.%s.%04d', v1, v2, r.firmware_version_3 or 0)
end

local function handle_vendor_info(card)
    local resp_data = card.ncsi_config_obj:Test({
        package_id = card.package_id,
        packet_type = 0x15,
        expect_rsp_packet_type = 0x95,
        channel_id = 0x0
    }):value()

    if resp_data == nil then
        log:error('get vendor info failed')
        return 'get vendor info failed'
    end
    local pattern = create_vendor_info_pattern()
    local r = pattern:unpack(resp_data, true)
    local firmware_version = build_version_strings(r)
    local result = string.format(
        'FirmwareVersion:%s, VendorID:%s, DeviceID:%s, SubsystemVendorID:%s, SubsystemDeviceID:%s',
        firmware_version,
        string.format('0x%04x', r.pci_vid or 0),
        string.format('0x%04x', r.pci_did or 0),
        string.format('0x%04x', r.pci_svid or 0),
        string.format('0x%04x', r.pci_ssid or 0))
    log:notice('%s get %s', card.NodeId, result)
    return result
end

cmd_handler.handlers = {
    [0x0A] = function(port)
        return handle_link_status(port)
    end,
    [0x15] = function(card)
        return handle_vendor_info(card)
    end
}

return cmd_handler