-- 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 log = require 'mc.logging'
local skynet = require 'skynet'
local defs = require 'domain.defs'
local client = require 'lsw.client'
local _, lsw_drv = pcall(require, 'lsw_drv')
local monitor = require 'domain.debug.monitor'
local external_vlan = class()

local EXTERNAL_ETHID<const> = 2
local DEFAULT_VLAN_MEMBER<const> = 0
local DEFAULT_VLAN_UNTAGED<const> = 0
local mtr = monitor.get_instance()

function external_vlan:ctor(parent, db)
    self.parent = parent
    self.db = db
end

function external_vlan:set_smm_path(res)
    local row = self.db:select(self.db.SmmMsg):where({Unit = self.parent.obj.Unit}):first()
    if not row then
        self.db.SmmMsg({
            Unit = self.parent.obj.Unit,
            SMMLinkPath = res,
            ExternalVlanId = 0
        }):save()
    else
        row.SMMLinkPath = res
        row:save()
    end
end

function external_vlan:get_smm_path()
    local row = self.db:select(self.db.SmmMsg):where({Unit = self.parent.obj.Unit}):first()
    if not row then
        log:notice("[lsw] cached smm path not exist")
        return 0
    end
    return row.SMMLinkPath
end

function external_vlan:get_external_vlanid()
    local row = self.db:select(self.db.SmmMsg):where({Unit = self.parent.obj.Unit}):first()
    if not row then
        log:notice("[lsw] cached external vlanid not exist")
        return 0
    end
    return row.ExternalVlanId
end

function external_vlan:set_external_vlanid(value)
    local row = self.db:select(self.db.SmmMsg):where({Unit = self.parent.obj.Unit}):first()
    if not row then
        self.db.SmmMsg({
            Unit = self.parent.obj.Unit,
            SMMLinkPath = 0,
            ExternalVlanId = value
        }):save()
    else
        row.ExternalVlanId = value
        row:save()
    end
end

-- 外网vlan配置初始化
function external_vlan:start_init()
    skynet.fork(function()
        self:register_signal()
        local vlan_id = self:try_get_external_vlan()
        local smm_path = self:try_get_smm_path()
        if vlan_id and smm_path then
            log:notice("[lsw] get external vlanid: %s, smm path: %s", vlan_id, smm_path)
            self:set_external_vlanid(vlan_id)
            self:set_smm_path(smm_path)
            self:config_new_vlan(defs.ATLAS_VLAN[smm_path])
        end
    end)
end

function external_vlan:register_signal()
    -- 注册外网通路切换信号回调
    client:SubscribeEthernetInterfacesActivePortChangedSignal(function(_, id, link_lsw_port)
        log:notice('[lsw] received signal, switch to port: %s', link_lsw_port)
        self:switch_smm_vlan(link_lsw_port)
    end)

    -- 注册外网vlanid更改信号回调
    client:OnMgmtPortPropertiesChanged(function(values)
        if values.VLANId:value() then
            log:notice('[lsw] received signal, switch to external vlan: %s', values.VLANId:value())
            self:check_vlanid_and_init_vlan(values.VLANId:value())
        end
    end)
end

function external_vlan:switch_smm_vlan(value)
    local inner_vlan_id = defs.ATLAS_VLAN[value]
    if not inner_vlan_id or self:get_smm_path() == value then
        log:notice("[lsw] received same smm path, not need init again")
        return
    end

    self:set_smm_path(value)
    self:config_new_vlan(inner_vlan_id)
end

function external_vlan:config_new_vlan(inner_vlan_id)
    for _, v in pairs(self.parent.component.vlans) do
        if v.obj.VlanId == inner_vlan_id then
            self:init_external_vlan(v)
        end
    end
end

function external_vlan:erase_old_vlan()
    local external_vlan_id = self:get_external_vlanid()
    local member = DEFAULT_VLAN_MEMBER
    local untaged = DEFAULT_VLAN_UNTAGED
    log:notice('[lsw] init external vlan(%s), member(%s) untaged(%s)', external_vlan_id, member, untaged)
    local res = lsw_drv.l_lsw_single_vlan_init(self.parent:get_type(), self.parent.obj.Unit,
        external_vlan_id, member, untaged)
    if not res then
        log:error('[lsw]single vlan(%d) init fail', external_vlan_id)
    else
        log:info('[lsw]single vlan(%d) init success', external_vlan_id)
    end
end

function external_vlan:init_external_vlan(inner_vlan)
    if not inner_vlan or not inner_vlan.obj then
        mtr:update_state(defs.NetConfigIdx.ExtVlanInit, true)
        return
    end

    local external_vlan_id = self:get_external_vlanid()
    local member = inner_vlan.obj.Member
    local untaged = inner_vlan.obj.Untaged
    log:notice('[lsw] init external vlan(%s), member(%s) untaged(%s)', external_vlan_id, member, untaged)
    local res = lsw_drv.l_lsw_single_vlan_init(self.parent:get_type(), self.parent.obj.Unit,
        external_vlan_id, member, untaged)
    mtr:update_state(defs.NetConfigIdx.ExtVlanInit, res)
    if not res then
        log:error('[lsw]single vlan(%d) init fail', external_vlan_id)
    else
        log:info('[lsw]single vlan(%d) init success', external_vlan_id)
    end
end

function external_vlan:try_get_external_vlan()
    local mgmt_port_objects = client:GetMgmtPortObjects()
    for _, mgmt_port in pairs(mgmt_port_objects) do
        if mgmt_port and mgmt_port.EthId == EXTERNAL_ETHID and mgmt_port.VLANId then
            return mgmt_port.VLANId
        end
    end
    return false
end

function external_vlan:try_get_smm_path()
    local obj
    local objects = client:GetNetConfigObjects()
    for _, v in pairs(objects) do
        obj = v
        break -- 只有一个对象，找到就退出
    end

    if obj then
        return obj.MasterSmmLinkNetId
    end
end

function external_vlan:check_vlanid_and_init_vlan(external_vlan_id)
    if external_vlan_id == self:get_external_vlanid() then
        log:notice("[lsw] received same smm external vlan id, not need init again")
        return
    end

    -- 配置新vlan前将旧vlan配置清除
    self:erase_old_vlan()
    self:set_external_vlanid(external_vlan_id)
    self:config_new_vlan(defs.ATLAS_VLAN[self:get_smm_path()])
end

return external_vlan