-- 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 class = require 'mc.class'
local c_handler_base = require 'bma.handles.handler_base'
local c_handler_eth = require 'bma.handles.handler_eth'
local c_handler_fc = require 'bma.handles.handler_fc'
local c_optical_module = require 'device.class.optical_module'
local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local optical_module_mgmt = require 'device.class.nic_mgmt.om_mgmt'
local json = require 'cjson'
local json_null = json.null
local om_mgmt = require 'device.class.nic_mgmt.om_mgmt'

local get_odata_id = c_handler_base.get_odata_id
local get_value_to_update = c_handler_base.get_value_to_update
local is_null = c_handler_base.is_null

local function speed_str_to_mbps(speed_str)
    local bps_str, t = speed_str:match('(%d+)([GM])')
    if not bps_str then
        log:error('handle bma optical speed failed: invalid speed_str=%s', speed_str)
        return 0
    end

    local bps = tonumber(bps_str)
    if t == 'M' then
        return bps
    elseif t == 'G' then
        return bps * 1000
    else
        log:error('handle bma optical speed failed: unknown unit %s, speed_str=%s', t, speed_str)
        return bps
    end
end

local function parser_speed_array(speed)
    local ret = {}
    if is_null(speed) then
        return ret
    end
    for i, speed_str in ipairs(speed) do
        ret[i] = speed_str_to_mbps(speed_str)
    end
    return ret
end

local c_handler_optical = class(c_handler_base)

function c_handler_optical:ctor()
end

function c_handler_optical:init()
    self:regist_odata_type('OemEthernetInterfaceSff')
    self:regist_odata_type('OemInfiniBandSff')
    self:regist_odata_type('OemFCSff')

    self:regist_class_type(c_optical_module)

    c_handler_eth.get_instance().on_need_update:on(function(object, path)
        local optical_module = object and object:get_optical_module() or nil
        self:match_resource(optical_module, path)
    end)

    c_handler_fc.get_instance().on_need_update:on(function(object, path)
        local optical_module = object and object:get_optical_module() or nil
        self:match_resource(optical_module, path)
    end)

    c_optical_module.on_add_object:on(function(object)
        self:match_resource(object)
    end)
end

function c_handler_optical:find_object(_, data)
    local port_bma_id = self.get_parent_path(get_odata_id(data))
    if not port_bma_id then
        return nil
    end

    local eth_obj = c_handler_eth.get_instance():get_object(port_bma_id) or
        c_handler_fc.get_instance():get_object(port_bma_id)
    if not eth_obj then
        return nil
    end

    return eth_obj:get_optical_module()
end

local function parser_manufacture_date(date_str, default)
    if is_null(date_str) then
        return default
    end
    local ok, formatted_date = pcall(function ()
        return string.format('20%s-%s-%s',
            string.sub(date_str, 1, 2),
            string.sub(date_str, 3, 4),
            string.sub(date_str, 5, 6))
    end)
    if not ok then
        return date_str
    end
    return formatted_date
end

local function update_presence(presence, data, default)
    if data == nil then
        return presence
    end
    if is_null(data) or data == false then
        return default
    end
    -- 其余情况则光模块在位，返回1
    return 1
end

local function set_presence_to_device_obj(op, presence)
    if presence == nil then
        return
    end
    local presence_value = 1
    if presence == json_null or presence == false then
        presence_value = 0
    end
    pcall(function ()
        optical_module_mgmt.get_instance():set_presence_to_device_obj(op, presence_value)
    end)
end

local function set_speed_match_to_device_obj(op, speed_match)
    if speed_match == nil then
        return
    end
    if speed_match == json_null then
        speed_match = true
    end
    pcall(function ()
        optical_module_mgmt.get_instance():set_speed_match_to_device_obj(op, speed_match)
    end)
end

local function set_type_match_to_device_obj(op, type_match)
    if type_match == nil then
        return
    end
    if type_match == json_null then
        type_match = true
    end
    pcall(function ()
        optical_module_mgmt.get_instance():set_type_match_to_device_obj(op, type_match)
    end)
end

local function set_fault_state_to_device_obj(op, fault_state)
    if fault_state == nil then
        return
    end
    pcall(function ()
        optical_module_mgmt.get_instance():set_fault_state_to_device_obj(op, fault_state)
    end)
end

local function set_power_state_to_device_obj(op, power_state)
    pcall(function ()
        optical_module_mgmt.get_instance():set_power_state_to_device_obj(op, power_state)
    end)
end

local function update_manufacturer_from_bma(op, data, default_str)
    if data.VendorName == nil then
        return
    end
    op.Manufacturer = get_value_to_update(op.Manufacturer, data.VendorName, default_str)
    pcall(function()
        om_mgmt.get_instance():update_manufacturer_to_device_obj(op, op.Manufacturer)
    end)
end

local function update_part_number_from_bma(op, data, default_str)
    if data.PartNumber == nil then
        return
    end
    op.PartNumber = get_value_to_update(op.PartNumber, data.PartNumber, default_str)
    pcall(function()
        om_mgmt.get_instance():update_part_number_to_device_obj(op, op.PartNumber)
    end)
end

local function update_serial_number_from_bma(op, data, default_str)
    if data.SerialNumber == nil then
        return
    end
    op.SerialNumber = get_value_to_update(op.SerialNumber, data.SerialNumber, default_str)
    pcall(function()
        om_mgmt.get_instance():update_serial_number_to_device_obj(op, op.SerialNumber)
    end)
end

local function update_production_date_from_bma(op, data, default_str)
    if data.ManufactureDate == nil then
        return
    end
    op.ProductionDate = parser_manufacture_date(data.ManufactureDate, default_str)
    pcall(function()
        om_mgmt.get_instance():update_production_date_to_device_obj(op, op.ProductionDate)
    end)
end

local function update_wave_length_nanometer_from_bma(op, data, default_str)
    if data.Wavelength == nil then
        return
    end
    op.WaveLengthNanometer = get_value_to_update(op.WaveLengthNanometer, data.Wavelength, default_str)
    pcall(function()
        om_mgmt.get_instance():update_wave_length_nanometer_to_device_obj(op, op.WaveLengthNanometer)
    end)
end

c_handler_optical.update_presence = update_presence
c_handler_optical.set_presence_to_device_obj = set_presence_to_device_obj

function c_handler_optical:update_optical(op, data)
    if op and op:check_port_workload_type() then
        return
    end
    local default_str = ""
    local default_num = 0
    update_manufacturer_from_bma(op, data, default_str)
    update_part_number_from_bma(op, data, default_str)
    update_serial_number_from_bma(op, data, default_str)
    update_production_date_from_bma(op, data, default_str)
    op.TransceiverType = get_value_to_update(op.TransceiverType, data.TransceiverType, default_str)
    update_wave_length_nanometer_from_bma(op, data, default_str)
    op.FiberConnectionType = get_value_to_update(op.FiberConnectionType, data.MediumMode, default_str)
    if data.speed ~= nil then
        op.SupportedSpeedsMbps = parser_speed_array(data.speed)
    end
    local cur_diagnostic = data.DiagnosticStatus
    if cur_diagnostic == json_null then
        op.FaultState = default_num
    elseif cur_diagnostic ~= nil then
        local diagnostic_merge = default_num
        local count = 0
        for _, v in pairs(cur_diagnostic) do
            local n = tonumber(v, 16)
            pcall(function ()
                diagnostic_merge = diagnostic_merge | n
            end)
            count = count + 1
        end
        if count == 1 and diagnostic_merge == 0xFFFFFFFF then
            diagnostic_merge = default_num
        end
        op.FaultState = diagnostic_merge
    end
    set_fault_state_to_device_obj(op, op.FaultState)
    op:update_fault_state(nil, op.FaultState)
    set_power_state_to_device_obj(op, op.PowerState)
    op.Type = get_value_to_update(op.Type, data.type, default_str)
    op.TypeMatch = get_value_to_update(op.TypeMatch, data.TypeMatch, true)
    set_type_match_to_device_obj(op, data.TypeMatch)
    op.SpeedMatch = get_value_to_update(op.SpeedMatch, data.SpeedMatch, true)
    set_speed_match_to_device_obj(op, data.SpeedMatch)
    op.ChannelNum = get_value_to_update(op.ChannelNum, data.ChannelNum, default_num)
    op.Presence = update_presence(op.Presence, data.IsSffExist, default_num)
    set_presence_to_device_obj(op, data.IsSffExist)
    if data.IsSffExist == true then
        op:update_asset_data_info()
    end
end

function c_handler_optical:reset()
    for object, _ in pairs(self.objects) do
        object:reset_bma_info()
    end
end

function c_handler_optical:add(_, data, object)
    self:update_optical(object, data)
end

function c_handler_optical:delete(_, _, object)
    object:reset_bma_info()
    object:reset_bma_stats()
end

function c_handler_optical:update(path, data, object)
    self:update_optical(object, data)
end

return singleton(c_handler_optical)
