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

local function get_wwn_rsp(data)
    local r = bs.new([[<<
        _:8,
        wwn_str:8/string
    >>]]):unpack(data, true)
    if r and r.wwn_str then
        return r.wwn_str:gsub('.', function (c)
            return string.format('%02X:', c:byte())
        end):sub(1, -2)
    end
    return nil
end

local pldm_link_speed_mdbs_switch = {
    [0x00] = 10000,
    [0x01] = 25000,
    [0x02] = 40000,
    [0x03] = 50000,
    [0x04] = 100000,
    [0x05] = 200000,
    [0x06] = 400000
}

local pldm_link_speed_gbps_switch = {
    [0x10] = 8,
    [0x11] = 16,
    [0x12] = 32,
    [0x13] = 64
}

local pldm_link_status_switch = {
    [0x00] = 'Disconnected',
    [0x01] = 'Connected'
}

local function get_link_status_rsp(data)
    local r = bs.new([[<<
        _:8,
        link_speed:8,
        link_status:8
    >>]]):unpack(data, true)
    local speed_mbps, speed_gbps, link_status
    if r and r.link_speed then
        speed_mbps = pldm_link_speed_mdbs_switch[r.link_speed]
        speed_gbps = pldm_link_speed_gbps_switch[r.link_speed]
    end
    if r and r.link_status then
        link_status = pldm_link_status_switch[r.link_status]
    end
    return {
        LinkStatus = link_status or 'N/A',
        SpeedMbps = speed_mbps or 0,
        SpeedGbps = speed_gbps or 0
    }
end

-- SDI BlackBox
local Hi1822_pldm = {
    protocol_dependencies = { pldm_huawei = { endpoint = nil } },
    properties = {
        BlackBox = {
            protocol = 'pldm_huawei',
            action = 'on_demand',
            request = {
                -- 运行时需传入data(包括offset length)
                command_code = 0x03,
                huawei_cmd_id = 0x03,
                sub_cmd_id = 0x07,
                extra_cmd = 24
            },
            response = function(data)
                local r = bs.new([[<<
                    _:8,
                    total_length:32/big,
                    length:32/big,
                    data:1024/string
                >>]]):unpack(data, true)
                return {
                    total_length = r.total_length,
                    length = r.length,
                    data = r.data
                }
            end
        },
        Port0DefaultWWPN = {
            protocol = 'pldm_huawei',
            action = 'on_schedule',
            period_in_sec = 120,
            request = {
                command_code = 0x02,
                huawei_cmd_id = 0x02,
                sub_cmd_id = 0x00,
                extra_cmd = 0x00 -- 这里传的是port_index
            },
            response = function(data)
                return get_wwn_rsp(data)
            end
        },
        Port0WorkWWPN = {
            protocol = 'pldm_huawei',
            action = 'on_schedule',
            period_in_sec = 120,
            request = {
                command_code = 0x02,
                huawei_cmd_id = 0x02,
                sub_cmd_id = 0x01,
                extra_cmd = 0x00 -- 这里传的是port_index
            },
            response = function(data)
                return get_wwn_rsp(data)
            end
        },
        Port1DefaultWWPN = {
            protocol = 'pldm_huawei',
            action = 'on_schedule',
            period_in_sec = 120,
            request = {
                command_code = 0x02,
                huawei_cmd_id = 0x02,
                sub_cmd_id = 0x00,
                extra_cmd = 0x01 -- 这里传的是port_index
            },
            response = function(data)
                return get_wwn_rsp(data)
            end
        },
        Port1WorkWWPN = {
            protocol = 'pldm_huawei',
            action = 'on_schedule',
            period_in_sec = 120,
            request = {
                command_code = 0x02,
                huawei_cmd_id = 0x02,
                sub_cmd_id = 0x01,
                extra_cmd = 0x01 -- 这里传的是port_index
            },
            response = function(data)
                return get_wwn_rsp(data)
            end
        },
        DefaultWWNN = {
            protocol = 'pldm_huawei',
            action = 'on_schedule',
            period_in_sec = 120,
            request = {
                command_code = 0x02,
                huawei_cmd_id = 0x02,
                sub_cmd_id = 0x02
            },
            response = function(data)
                return get_wwn_rsp(data)
            end
        },
        WorkWWNN = {
            protocol = 'pldm_huawei',
            action = 'on_schedule',
            period_in_sec = 120,
            request = {
                command_code = 0x02,
                huawei_cmd_id = 0x02,
                sub_cmd_id = 0x03
            },
            response = function(data)
                return get_wwn_rsp(data)
            end
        },
        Port0LinkStatus = {
            protocol = 'pldm_huawei',
            action = 'on_schedule',
            period_in_sec = 5,
            request = {
                command_code = 0x02,
                huawei_cmd_id = 0x00,
                sub_cmd_id = 0x08,
                extra_cmd = 0x00
            },
            response = function(data)
                return get_link_status_rsp(data)
            end
        },
        Port1LinkStatus = {
            protocol = 'pldm_huawei',
            action = 'on_schedule',
            period_in_sec = 5,
            request = {
                command_code = 0x02,
                huawei_cmd_id = 0x00,
                sub_cmd_id = 0x08,
                extra_cmd = 0x01
            },
            response = function(data)
                return get_link_status_rsp(data)
            end
        }
    }
}

-- SP580
local Hi1822 = {
    protocol_dependencies = { smbus = { ref_chip = nil, buffer_len = 64 } },
    properties = {
        Health = {
            protocol = 'smbus',
            action = 'on_schedule',
            period_in_sec = 5,
            request = { opcode = 0x1, expect_data_len = 1 },
            response = function(data)
                return bs.new([[<<health:8>>]]):unpack(data, true).health
            end
        },
        FaultCode = {
            protocol = 'smbus',
            action = 'on_schedule',
            period_in_sec = 2,
            request = { opcode = 0x2 },
            response = libmgmt_protocol.create_array_parser([[<<code:16>>]], 2)
        },
        ChipTemp = {
            protocol = 'smbus',
            action = 'on_schedule',
            period_in_sec = 2,
            request = { opcode = 0x3, expect_data_len = 2 },
            response = function(data)
                return bs.new([[<<temp:16/signed>>]]):unpack(data, true).temp
            end
        },
        ErrorLog = {
            protocol = 'smbus',
            action = 'on_demand',
            request = { opcode = 0xC },
            response = function(data)
                return data
            end
        },
        LastWord = {
            protocol = 'smbus',
            action = 'on_demand',
            request = { opcode = 0xD },
            response = function(data)
                return data
            end
        },
        RunningLog = {
            protocol = 'smbus',
            action = 'on_demand',
            request = { opcode = 0xE },
            response = function(data)
                return data
            end
        },
        FirmwareVersion = {
            protocol = 'smbus',
            action = 'on_schedule',
            period_in_sec = 60,
            request = { opcode = 0x5, expect_data_len = 3 },
            response = function(data)
                local r = bs.new([[<<
                    buf1:8,
                    buf2:8,
                    buf3:8
                >>]]):unpack(data)
                return string.format('%s.%s.%s.%s', r.buf1, r.buf2 // 10, r.buf2 % 10, r.buf3)
            end
        },
        OpticalTemp = {
            protocol = 'smbus',
            action = 'on_schedule',
            period_in_sec = 2,
            request = { opcode = 0x400 },
            response = function(data)
                local parser = libmgmt_protocol.create_array_parser([[<<temp:16>>]], 2)
                local r = parser(data)
                if not r then
                    return r
                end
                local result = {}
                local temp
                for i, t in ipairs(r) do
                    temp = t.temp
                    if temp == 0x7ffd then
                        log:debug('optical temp for port %d is invalid', i)
                        result[i] = 0xFFFF
                    elseif temp == 0x7ffe then
                        log:debug('optical module is not available for port %d', i)
                        result[i] = 0xFFFF
                    elseif temp == 0x7fff then
                        log:debug('Failed to read optical temp from port %d', i)
                        result[i] = 0xFFFF
                    else
                        result[i] = temp
                    end
                end
                return result
            end
        },
        LinkStatus = {
            protocol = 'smbus',
            action = 'on_schedule',
            period_in_sec = 2,
            request = { opcode = 0x403 },
            response = function(data)
                local parser = libmgmt_protocol.create_array_parser([[<<link:8>>]], 1)
                local r = parser(data)
                if not r then
                    return r
                end
                local link_switch <const> = { [0x0] = 'Disconnected', [0x1] = 'Connected', [0xFF] = 'N/A' }
                local result = {}
                for _, d in ipairs(r) do
                    result[#result + 1] = link_switch[d.link] or 'N/A'
                end
                return result
            end
        },
        MacAddress = {
            protocol = 'smbus',
            action = 'on_schedule',
            period_in_sec = 2,
            request = { opcode = 0x404 },
            response = function(data)
                local parser = libmgmt_protocol.create_array_parser(
                    [[<<port:8, PF_VF:16, addr:1/MAC_ADDRESS>>]], 9)
                local r = parser(data)
                if not r then
                    return r
                end
                local result = {}
                for _, d in ipairs(r) do
                    result[#result + 1] = d.addr.mac_address
                end
                return result
            end
        },
        PHYTemp = {
            protocol = 'smbus',
            action = 'on_demand',
            request = { opcode = 0x405 },
            response = function(data)
                local temp = bs.new([[<<temp:16>>]]):unpack(data).temp
                if temp == 0x7ffd then
                    log:error('PHY temp is invalid')
                elseif temp == 0x7ffe then
                    log:warn('PHY chip is not initialized')
                elseif temp == 0x7fff then
                    log:error('Failed to read PHY temp')
                else
                    return temp
                end
            end
        }
    }
}

return {
    smbus = function(ref_chip)
        local obj = utils.table_copy(Hi1822)
        obj.protocol_dependencies.smbus.ref_chip = ref_chip
        return obj
    end,
    mctp_pldm = function(endpoint)
        local obj = utils.table_copy(Hi1822_pldm)
        obj.protocol_dependencies.pldm_huawei.endpoint = endpoint
        return obj
    end,
    lldp = function()
    end
}
