local NetManager =
{
    receiveCallbacks = {},
    msgCallbacks = {},
    isClosedMap = {},
    chatReconnectWaitTime = 1,
    connectIPMap = {},
    receiveCallbackPool = {},
    alreadyConnected = {},
    mainReconnectWaitTime = 0,
	sendQueue = {},
    tillBeforeOverCount = 0,
    msgId = 0
}

local CSApplication = CS.UnityEngine.Application
local CSNotReachable = CS.UnityEngine.NetworkReachability.NotReachable

local MAX_CHAT_WAIT_TIME = 16
local MAX_MAIN_WAIT_TIME = 10
local JOIN_CHAT_INTERVAL = 15
local TILL_BEFORE_INTERVAL = 0.5

local NetErrorCode = CS.BF.NetErrorCode

local pb = require "pb"
local ProtoMsgDispatch = require "app/proto/proto_msg_dispatch"

local protoPaths =
{
    "assets/proto/protocol.bytes",
}

NetManager.MAIN_SOCKET_NAME = 0
NetManager.CHAT_SOCKET_NAME = 1
NetManager.LOGIN_TYPE = {
	TOKEN = "token",
	ANONYMOUS = "anonymous",
	GOOGLE = "google",
	APPLE = "apple",
    FACEBOOK = "facebook",
}

function NetManager:getGate()
	if EDITOR_MODE then
		return "https://entrance.wdd817.link"
	else
		return "https://d3ksek7t8d0wbt.cloudfront.net/"
	end
end

function NetManager:init(callback)
    self.chatReconnectWaitTime = 1

    CS.BF.BFMain.Instance.NetMgr:InitNetClientCount(1) -- 只有主链接，没有聊天，但是聊天逻辑先不删
    if self.initSucc then
        if callback then
            return callback()
        end
    end
    self.initSucc = true
    -- pb.option "int64_as_string" --如果需要uint64再加上

    local loadCount = #protoPaths
    for i, path in ipairs(protoPaths) do
        ResourceManager:loadOriginAssetAsync(path, GConst.TYPEOF_UNITY_CLASS.TEXT_ASSET, function(_, textAsset)
            pb.load(textAsset.bytes)
            ResourceManager:unload(path)
            loadCount = loadCount - 1
            if loadCount == 0 then
                if callback then
                    return callback()
                end
            end
        end)
    end
end

function NetManager:setChatUrl(url)
    local arr = string.split(url, ":")
    self.chatDomain = arr[1]
	self.chatPort = arr[2]
end

function NetManager:getChatDomain()
    return self.chatDomain
end

function NetManager:getChatPort()
    return self.chatPort
end

function NetManager:getIsBusy()
    return self.isSending
end

function NetManager:isNotReachable()
	return CSApplication.internetReachability == CSNotReachable
end

function NetManager:connect(domain, port, callback, socketName)
    socketName = socketName or NetManager.MAIN_SOCKET_NAME
    if self:isAvailable(socketName) then
        if callback then
            callback()
        end
        return
    end

    self.alreadyConnected[socketName] = false
    self.isClosedMap[socketName] = false

    if socketName == NetManager.MAIN_SOCKET_NAME then
        UIManager:showWaitNet(true)
    end

    self.connectIPMap[socketName] = ""

    CS.BF.BFMain.Instance.NetMgr:AddLuaOnConnected(function(name)
        self.alreadyConnected[name] = true
        Logger.log("[NetManager]connect success:%s", name)
        local connectIP = self:getConnectIP(name)
        self.connectIPMap[socketName] = connectIP
        if name == NetManager.MAIN_SOCKET_NAME then
            if self.reconnectMainId then
                SchedulerManager:unscheduleGlobal(self.reconnectMainId)
                self.reconnectMainId = nil
            end
            self.mainReconnectWaitTime = 0
            UIManager:hideWaitNet()
        else
            self.chatReconnectWaitTime = 1
            if self.reconnectChatId then
                SchedulerManager:unscheduleGlobal(self.reconnectChatId)
                self.reconnectChatId = nil
            end
            if DataManager.ChatData then
                DataManager.ChatData:onConnect()
            end
            self:joinChatChannel()
        end
        if callback then
            callback()
        end
    end)

    CS.BF.BFMain.Instance.NetMgr:AddLuaOnDisconnected(function(name)
        -- self:onDisconnect(name)
        self:disconnectAndReconnect()
    end)

    CS.BF.BFMain.Instance.NetMgr:AddLuaOnReceiveMessage(function(name, group, recvId, bytes)
        if name == NetManager.MAIN_SOCKET_NAME then
            self.recvId = recvId
            if self.reconnectMainFlag then -- 如果是在重连状态下收到消息就设置下重连所需要的数据
                self:setReconnectData()
            end
        end
        self:onReceive(group, bytes)
    end)

    CS.BF.BFMain.Instance.NetMgr:AddLuaOnError(function(name, errorCode, errorMsg)
        self:onError(name, errorCode, errorMsg)
    end)

    CS.BF.BFMain.Instance.NetMgr:AddLuaOnReconnectSuccess(function(name)
        Logger.log("[NetManager]reconnect succes:%s", name)
        if self.receiveCallbacks["ChapterSettlementRsp"] then -- 处理一下重发问题
            if #self.receiveCallbacks["ChapterSettlementRsp"] > 0 then
                self.receiveCallbacks["ChapterSettlementRsp"] = nil
                self.isSending = false
                self:trySend()
            end
        end
        if name == NetManager.MAIN_SOCKET_NAME then
            if self.reconnectMainId then
                SchedulerManager:unscheduleGlobal(self.reconnectMainId)
                self.reconnectMainId = nil
            end
            self.mainReconnectWaitTime = 0
            if self.reconnectMainFlag then
                self.reconnectMainFlag = false
            end
            if self.activeReconnectMainFlag then
                self.activeReconnectMainFlag = false
                UIManager:hideWaitNet()
                if UIManager:getWaitNetCount() == 0 then
                    UIManager:hideMessageBoxByTag(GConst.GAME_OBJECT_TYPE.MESSAGEBOX_RECONNECT)
                end
            end
            local durationTime = 0
            if self.disconnectTime then
                durationTime = Time:getServerTime() - self.disconnectTime
                self.disconnectTime = nil
            end
            BIReport:postNetWorkOpt(BIReport.NETWORK_OPT_TYPE.RECONNECT, UIManager:getTopUIIndex(), nil, durationTime)
        else
            self.chatReconnectWaitTime = 1
            if self.reconnectChatId then
                SchedulerManager:unscheduleGlobal(self.reconnectChatId)
                self.reconnectChatId = nil
            end
            if self.joinChatId then
                SchedulerManager:unscheduleGlobal(self.joinChatId)
                self.joinChatId = nil
            end
            if DataManager.ChatData then
                DataManager.ChatData:onConnect()
            end
            self:joinChatChannel()
        end
    end)

    CS.BF.BFMain.Instance.NetMgr:SetLuaOnDecodePbCallback(function(name, group, recvId, data)
        local msgName = ProtoMsgDispatch:getReqMsgNameByMsgId(group)
        if msgName == nil then
            return
        end
        Logger.logHighlight("[NetManager]===onReceive===name:%s, msgId:%s", msgName, group)

        -- 这里保证即使协议有问题解不出来也不要卡住
        local ok, pbData = pcall(function()
            local fullMsgName = ProtoMsgDispatch:getMsgFullNameByMsgName(msgName)
            return pb.decode(fullMsgName, data)
        end)

        if ok and pbData then
            pbData.status = not pbData.err_code and 0 or ProtoMsgDispatch:getErrCodeEnum(pbData.err_code)
            if pbData.status ~= 0 then
                local lastLoginInfo = LocalData:getLastLoginInfo()
                BIReport:postPbAuthfailed(ProtoMsgType.FromMsgEnum.AuthReq, lastLoginInfo, self.authFailToLogin)
                if self.autoReAuthSid then
                    SchedulerManager:unscheduleGlobal(self.autoReAuthSid)
                    self.autoReAuthSid = nil
                end

                if self.authFailToLogin then
                    self:closeAll()
                    self:clearTagAuthFailToLogin()
                    return
                end

                if msgName == "ReconnectRsp" then -- Reconnect失败就直接回登录界面
                    self:closeAll()
                else
                    self:closeAndClear()
                    local accountInfo = LocalData:getAccountInfo()
                    local loginType
                    local lastLoginType
                    if accountInfo.google_id and accountInfo.google_id ~= "" then
                        loginType = SDKManager.BF_LOGIN_TYPE.GOOGLE
                        lastLoginType = NetManager.LOGIN_TYPE.GOOGLE
                    elseif accountInfo.apple_id and accountInfo.apple_id ~= "" then
                        loginType = SDKManager.BF_LOGIN_TYPE.APPLE
                        lastLoginType = NetManager.LOGIN_TYPE.APPLE
                    end
                    if loginType then
                        SDKManager:login(function(params)
                            if not params.token then
                                return
                            end
                            LocalData:setLastLoginInfo(lastLoginType, params.id, params.token)
                            ModuleManager.LoginManager:saveAuthArgs(false)
                            ModuleManager.LoginManager:initSocket()
                        end, loginType)
                    else
                        if not self.autoReAuth then
                            self.autoReAuth = true
                            self.autoReAuthSid = self:performWithDelayGlobal(function()
                                ModuleManager.LoginManager:initSocket()
                            end, 0.5)
                        else
                            LocalData:setLastLoginInfo()
                            ModuleManager.LoginManager:saveAuthArgs(false)
                            local params = {
                                content = I18N:getGlobalText(I18N.GlobalConst.DISCONNECT_RELOGIN),
                                okText = I18N:getGlobalText(I18N.GlobalConst.BTN_TEXT_OK),
                                noShowClose = true,
                                okFunc = function()
                                    self.autoReAuth = false
                                    ModuleManager.LoginManager:initSocket()
                                end,
                                boxType = GConst.MESSAGE_BOX_TYPE.MB_OK,
                                top = true,
                            }
                            GFunc.showMessageBox(params)
                        end
                    end
                end
            else
                if self.autoReAuthSid then
                    SchedulerManager:unscheduleGlobal(self.autoReAuthSid)
                    self.autoReAuthSid = nil
                end
                self.autoReAuth = false
                self:clearTagAuthFailToLogin()
                ModuleManager.LoginManager:saveAuthArgs(true)
                UIManager:hideWaitNet(true)

                if pbData.id then -- 重连也会走到这儿，但是没有token等信息
                    LocalData:setLastLoginInfo(NetManager.LOGIN_TYPE.TOKEN, pbData.id, pbData.token)
                    LocalData:setAccountInfo(pbData)
                    BIReport:updateAccountId(pbData.id)
                end

                pbData.send_id = pbData.send_id or 0
                pbData.err_code = nil
                CS.BF.BFMain.Instance.NetMgr.decodePbStr = json.encode(pbData)
                CS.BF.BFMain.Instance.NetMgr.rspGroup = group
                CS.BF.BFMain.Instance.NetMgr.decodeFinish = true
            end
        end
    end)

    CS.BF.BFMain.Instance.NetMgr:SetLuaAuthCallback(function(isSuccess)
        if not isSuccess then
            local params = {
                content = I18N:getGlobalText(I18N.GlobalConst.DISCONNECT_RELOGIN),
                okText = I18N:getGlobalText(I18N.GlobalConst.BTN_TEXT_OK),
                noShowClose = true,
                okFunc = function()
                    ModuleManager.LoginManager:goToLoginScene()
                end,
                boxType = GConst.MESSAGE_BOX_TYPE.MB_OK,
                top = true,
            }
            GFunc.showMessageBox(params)
        end
    end)

    local configuration = CS.BF.NetConnectConfiguration(CS.BF.NetServiceType.TCPService, tostring(socketName))
    configuration:EnableMessageType(CS.BF.NetIncomingMessageType.DebugMessage)
    configuration:EnableMessageType(CS.BF.NetIncomingMessageType.WarningMessage)
    configuration:EnableMessageType(CS.BF.NetIncomingMessageType.ErrorMessage)
    if socketName == NetManager.MAIN_SOCKET_NAME then
        configuration.EnableSilenceReconnect = false                            -- game连接不用自动重连
        configuration.ReconnectTimeoutTime = GConst.WAIT_NET_RSP_TIME           -- 每次重连超时时间
        configuration.AlreadySendMessageCacheCount = 3
    else
    --     configuration.ReconnectTimeoutTime = 8                                  -- 每次重连超时时间
    --     configuration.ReconnectBaseInterval = 1                                 -- 每次重连间隔时间
    --     configuration.AutoReconnectCount = 5                                    -- 重连次数
        -- 自动重连可能有bug，先手动重连
        configuration.EnableSilenceReconnect = false                            -- chat连接不用自动重连
        configuration.ReconnectTimeoutTime = MAX_CHAT_WAIT_TIME                 -- 每次重连超时时间
        configuration.AlreadySendMessageCacheCount = 3
    end
    CS.BF.BFMain.Instance.NetMgr:ConnectWithConfiguration(socketName, configuration, domain, tonumber(port))
end

function NetManager:isConnected(socketName)
    if not self.initSucc then
        return false
    end
    return CS.BF.BFMain.Instance.NetMgr:IsConnected(socketName)
end

function NetManager:isAvailable(socketName)
    if not self.initSucc then
        return false
    end
    return CS.BF.BFMain.Instance.NetMgr:IsAvailable(socketName)
end

function NetManager:isDisconnected(socketName)
    if not self.initSucc then
        return true
    end
    return CS.BF.BFMain.Instance.NetMgr:IsDisconnected(socketName)
end

function NetManager:getConnectIP(socketName)
    if not self.initSucc then
        return ""
    end
    return CS.BF.BFMain.Instance.NetMgr:GetConnectIP(socketName)
end

function NetManager:_sendBytes(clientName, bytes, group, cmd)
	self.isSending = true
    CS.BF.BFMain.Instance.NetMgr:Send(clientName, group, cmd, bytes)
end

---- 后台发送协议，不锁定界面
function NetManager:sendOnBackground(msgName, params, callback, socketName)
    self:_send(msgName, params, callback, socketName, false)
end

---- 后台发送协议，不锁定界面,且不会有回复
function NetManager:sendOnBackgroundWithoutRsp(msgName, params, socketName)
    self:_send(msgName, params, nil, socketName, false, true)
end

function NetManager:saveAuthArgs(authArgs)
    local msgFullName = ProtoMsgDispatch:getMsgFullNameByMsgName(ProtoMsgType.FromMsgEnum.AuthReq)
    local bytes = pb.encode(msgFullName, authArgs)
    CS.BF.BFMain.Instance.NetMgr.authReqData = bytes
end

function NetManager:saveChatAuthArgs(token)
    local msgFullName = ProtoMsgDispatch:getMsgFullNameByMsgName(ProtoMsgType.FromMsgEnum.ChatAuthReq)
    local args = {
        id = DataManager.PlayerData:getUid(),
        token = token
    }
    CS.BF.BFMain.Instance.NetMgr.ChatAuthReqData = pb.encode(msgFullName, args)
end

function NetManager:send(binder, msgName, params, responseData, callback, lockGame, noRsp, getType)
    if responseData.rewards then
        responseData.rewards =  GFunc.formatRewardsToServerStruct(responseData.rewards)
    end
    if responseData.costs then
        responseData.costs =  GFunc.formatRewardsToServerStruct(responseData.costs)
    end
    responseData.err_code = GConst.ERROR_STR.SUCCESS
    self:_send(binder, msgName, params, responseData, callback, lockGame, noRsp, getType)
end

function NetManager:sendTillBeforeOver(binder, msgName, params, responseData, callback, lockGame, noRsp, getType)
    if responseData.rewards then
        responseData.rewards =  GFunc.formatRewardsToServerStruct(responseData.rewards)
    end
    if responseData.costs then
        responseData.costs =  GFunc.formatRewardsToServerStruct(responseData.costs)
    end
    responseData.err_code = GConst.ERROR_STR.SUCCESS
    self:_send(binder, msgName, params, responseData, callback, lockGame, noRsp, getType, true)
end

function NetManager:_send(binder, msgName, params, responseData, callback, lockGame, noRsp, getType, beforeOver)
    local socketName = NetManager.MAIN_SOCKET_NAME
	if self:isDisconnected(socketName) then
        if socketName == NetManager.MAIN_SOCKET_NAME then
            if UIManager:getWaitNetCount() > 0 then
                self:disconnect(socketName)
            else
                if self.reconnectMainId then
                    SchedulerManager:unscheduleGlobal(self.reconnectMainId)
                    self.reconnectMainId = nil
                end
                self:reconnectMain()
            end
        else
            self:disconnect(socketName)
        end
	else
        if beforeOver then
            UIManager:showWaitNet()
            self.tillBeforeOverCount = self.tillBeforeOverCount + 1
        end
        local pipedMsgName, pipedParams = self:pipedMessage(msgName, params)
        table.insert(self.sendQueue, {
            binder = binder,
            originMsgName = msgName,
            msgName = pipedMsgName,
            params = pipedParams,
            responseData = responseData,
            socketName = socketName,
            callback = callback,
            lockGame = lockGame,
            noRsp = noRsp,
            getType = getType,
            beforeOver = beforeOver,
        })
        if not self:isNotSave(msgName) then
            self:saveSendQueue()
        end

		self:trySend()
	end
end

function NetManager:trySend()
    local binder
    local originMsgName
	local curMsgName
	local curParams
    local responseData
    local getType
	local curSocketName
	local curCallback
	local curLockGame
	local noRsp
    local beforeOver
	if not self.isSending and self.sendQueue[1] then -- 没有正在发送的消息，并且有发送队列, 则继续发送
		local cache = self.sendQueue[1]
        binder = cache.binder
        originMsgName = cache.originMsgName
		curMsgName = cache.msgName
		curParams = GFunc.getTable(cache.params)
        responseData = cache.responseData
        getType = cache.getType
		curSocketName = cache.socketName
		curCallback = cache.callback
		curLockGame = cache.lockGame
		noRsp = cache.noRsp
        beforeOver = cache.beforeOver
	end

	if curMsgName then
        if EDITOR_MODE then
            local subName = curMsgName
            local reqData
            if curMsgName == ProtoMsgType.FromMsgEnum.PipedReq then
                reqData = curParams.data
                Logger.logHighlight("[NetManager]===onSend===name:%s params:%s", ProtoMsgDispatch:getReqMsgNameByMsgId(curParams.msg_id), json.encode(reqData))
            else
                reqData = curParams
                Logger.logHighlight("[NetManager]===onSend===name:%s params:%s", subName, json.encode(reqData))
            end
        end

		local msgFullName = ProtoMsgDispatch:getMsgFullNameByMsgName(curMsgName)
        if curMsgName == ProtoMsgType.FromMsgEnum.PipedReq then
            local msgName = ProtoMsgDispatch:getReqMsgNameByMsgId(curParams.msg_id)
            local fullMsgName = ProtoMsgDispatch:getMsgFullNameByMsgName(msgName)
            if curParams.data and type(curParams.data) == "table" then
                curParams.data = pb.encode(fullMsgName, curParams.data)
            end
        end

		local bytes = pb.encode(msgFullName, curParams)
		if bytes then
			if curLockGame then
				UIManager:showWaitNet()
			end
			local msgId = ProtoMsgDispatch:getMsgIdByMsgName(curMsgName)

			self:_sendBytes(curSocketName, bytes, msgId, 0)

			if noRsp then
				return
			end
			local msg = ProtoMsgDispatch:getRspMsgByMsgName(originMsgName)
			if not self.receiveCallbacks[msg] then
				self.receiveCallbacks[msg] = {}
			end
			local receiveCallback = self:getReceiveCallback()
            receiveCallback.binder = binder
			receiveCallback.lockGame = curLockGame
            receiveCallback.callback = curCallback
            receiveCallback.responseData = responseData
            receiveCallback.getType = getType
            receiveCallback.beforeOver = beforeOver
			table.insert(self.receiveCallbacks[msg], receiveCallback)
		else
			Logger.logError("[NetManager]send data error %s", curMsgName)
		end
	end
end

-- 静默重连
function NetManager:silentReconnectMain()
    self.reconnectMainFlag = true
    self:setReconnectData()
    CS.BF.BFMain.Instance.NetMgr:Reconnect(NetManager.MAIN_SOCKET_NAME)
end

-- 主动重连
function NetManager:reconnectMain()
    UIManager:showWaitNet(true) -- 强制重新计时
    if self.activeReconnectMainFlag then -- 说明之前已经在主动重连中,为了保证引用计数正确,这里减一
        UIManager:hideWaitNet()
    else
        self.activeReconnectMainFlag = true
    end
    self.reconnectMainFlag = true
    self:setReconnectData()
    CS.BF.BFMain.Instance.NetMgr:Reconnect(NetManager.MAIN_SOCKET_NAME)
end

function NetManager:getReceiveCallback()
    if #self.receiveCallbackPool > 0 then
        return table.remove(self.receiveCallbackPool)
    end
    return {}
end

function NetManager:recycleReceiveCallback(receiveCallback)
    receiveCallback.callback = nil
    receiveCallback.lockGame = true
    table.insert(self.receiveCallbackPool, receiveCallback)
end

function NetManager:disconnectAll()
    if self.alreadyConnected[NetManager.MAIN_SOCKET_NAME] then
        self:showReconnectMain()
    else
        self:closeAll()
    end
end

function NetManager:disconnectAndReconnect()
    if self.alreadyConnected[NetManager.MAIN_SOCKET_NAME] then
        if not DataManager:getLoginSuccess() then -- 没登陆成功的，直接重新走auth->login
            ModuleManager.LoginManager:reloginOnReconnectRefuse()
            return
        end

        if self.reconnectMainId then
            return
        end
        if self.mainReconnectWaitTime > MAX_MAIN_WAIT_TIME then
            self.mainReconnectWaitTime =  MAX_MAIN_WAIT_TIME
        end

        self.reconnectMainId = self:performWithDelayGlobal(function ()
            self.reconnectMainId = nil
            self:silentReconnectMain()
        end, self.mainReconnectWaitTime)
        self.mainReconnectWaitTime = self.mainReconnectWaitTime + 1
    else
        self:closeAll()
    end
end

function NetManager:showReconnectMain()
    if self.reconnectMainId then
        SchedulerManager:unscheduleGlobal(self.reconnectMainId)
        self.reconnectMainId = nil
    end
    if UIManager:isDisconnectMsgBoxVisible() then
        return
    end
    local params = {
        content = I18N:getGlobalText(I18N.GlobalConst.RECONNECT),
        okText = I18N:getGlobalText(I18N.GlobalConst.BTN_TEXT_OK),
        cancelText = I18N:getGlobalText(I18N.GlobalConst.RELOGIN),
        noShowClose = true,
        okFunc = function()
            self:reconnectMain()
        end,
        cancelFunc = function()
            ModuleManager.LoginManager:goToLoginScene()
        end,
        boxType = GConst.MESSAGE_BOX_TYPE.MB_OK_CANCEL,
        tag = GConst.GAME_OBJECT_TYPE.MESSAGEBOX_RECONNECT,
        top = true,
    }
    GFunc.showMessageBox(params)
end

function NetManager:disconnect(socketName)
    if self.isClosedMap[socketName] then
        return
    end
    if socketName == NetManager.CHAT_SOCKET_NAME then --暂时没有维护
        self.isClosedMap[socketName] = true
        CS.BF.BFMain.Instance.NetMgr:Close(socketName)

        if self.reconnectChatId then
            return
        end

        if DataManager.ChatData then
            DataManager.ChatData:onDisconnect()
        end

        self.reconnectChatId = self:performWithDelayGlobal(function ()
            self.reconnectChatId = nil
            if not self:isConnected(NetManager.CHAT_SOCKET_NAME) then
                ModuleManager.LoginManager:connectByChannel(nil, NetManager.CHAT_SOCKET_NAME)
            end
        end, self.chatReconnectWaitTime)

        self.chatReconnectWaitTime = self.chatReconnectWaitTime*2
        if self.chatReconnectWaitTime > MAX_CHAT_WAIT_TIME then
            self.chatReconnectWaitTime = MAX_CHAT_WAIT_TIME
        end
    else
        if self.alreadyConnected[socketName] then -- 如果是成功连接过
            if not DataManager:getLoginSuccess() then -- 没登陆成功的，直接重新走auth->login
                ModuleManager.LoginManager:reloginOnReconnectRefuse()
                return
            end

            if UIManager:getWaitNetCount() > 0 then -- 当前在等待服务器回消息
                if not self.activeReconnectMainFlag then
                    self:showReconnectMain()
                end
            else -- 否则就悄咪咪的重连
                if self.mainReconnectWaitTime > MAX_MAIN_WAIT_TIME then
                    self.mainReconnectWaitTime =  MAX_MAIN_WAIT_TIME
                end
                if self.reconnectMainId then
                    return
                end
                self.reconnectMainId = self:performWithDelayGlobal(function ()
                    self.reconnectMainId = nil
                    self:silentReconnectMain()
                end, self.mainReconnectWaitTime)
                self.mainReconnectWaitTime = self.mainReconnectWaitTime + 1
            end
        else -- 从来没有连上过
            self:closeAll()
        end
    end
end

function NetManager:onDisconnect(socketName)
    self:disconnect(socketName)
end

function NetManager:performWithDelayGlobal(func, delay)
	local sid = SchedulerManager:performWithDelayGlobal(func, delay)
	return sid
end

function NetManager:scheduleGlobal(func, delay)
	local sid = SchedulerManager:scheduleGlobal(func, delay)
	return sid
end

function NetManager:closeAll(errorType)
    if self.isClosedMap[NetManager.MAIN_SOCKET_NAME] then
        return
    end
    self:closeAndClear()
    UIManager:showDisconnect(errorType)
end

function NetManager:onReceive(msgId, data)
	self.isSending = false
    --Logger.logHighlight("MSGID:"..msgId)
    local msgName = ProtoMsgDispatch:getReqMsgNameByMsgId(msgId)
    if msgName == nil then
        return
    end
    -- 这里保证即使协议有问题解不出来也不要卡住

    local ok, pbData = pcall(function()
        local fullMsgName = ProtoMsgDispatch:getMsgFullNameByMsgName(msgName)
        return pb.decode(fullMsgName, data)
    end)

	local msg = ProtoMsgDispatch:getRspMsgByMsgName(msgName)
    if ok and pbData then
        if not pbData.err_code then
            pbData.status = 0
        else
            pbData.status = ProtoMsgDispatch:getErrCodeEnum(pbData.err_code)
        end
        if EDITOR_MODE then
            Logger.logHighlight("[NetManager]===onReceive===name:%s, msgId:%s, data:%s", msgName, msgId, json.encode(pbData))
        end
        self:dispatch(msgName, pbData)
    else
        if EDITOR_MODE then
            Logger.logHighlight("[NetManager]===onReceive failed===name:%s, msgId:%s", msgName, msgId)
        end
    end

    local sendInfo
    local sendMsgName
	if self.sendQueue[1]then
        sendMsgName = self.sendQueue[1].msgName
        if sendMsgName == ProtoMsgType.FromMsgEnum.PipedReq then
            sendMsgName = ProtoMsgDispatch:getReqMsgNameByMsgId(self.sendQueue[1].params.msg_id)
        end
        if ProtoMsgDispatch:getRspMsgByMsgName(sendMsgName) == msgName then
            sendInfo = table.remove(self.sendQueue, 1)
            self:saveSendQueue()
        end
	end

    if not ok or not pbData then
        local reqDataStr = GConst.EMPTY_STRING
        if sendInfo then
            if sendInfo.msgName == ProtoMsgType.FromMsgEnum.PipedReq then
                reqDataStr = json.encode(sendInfo.params.data)
            else
                reqDataStr = json.encode(sendInfo.params)
            end
        end
        BIReport:postPbDecodefailed(sendMsgName, reqDataStr)
    -- elseif pbData.err_code ~= GConst.ERROR_STR.SUCCESS then
    elseif pbData.status ~= 0 then -- 有一些协议没有err_code所以改为判定自定义的status
        local reqDataStr = GConst.EMPTY_STRING
        if sendInfo then
            if sendInfo.msgName == ProtoMsgType.FromMsgEnum.PipedReq then
                reqDataStr = json.encode(sendInfo.params.data)
            else
                reqDataStr = json.encode(sendInfo.params)
            end
        end
        BIReport:postNetError(sendMsgName, pbData.err_code, reqDataStr, pbData)
    end

	local callbacks = self.receiveCallbacks[msg]
	if callbacks and #callbacks > 0 then
		local receiveCallback = table.remove(callbacks, 1)
		local lockGame = true
		local receiveFunc = nil
        local responseData = nil
		if receiveCallback then
			lockGame = receiveCallback.lockGame
			receiveFunc = receiveCallback.callback
            responseData = receiveCallback.responseData
            if pbData.err_code == GConst.ERROR_STR.SUCCESS and pbData.rewards then
                local getType = receiveCallback.getType
                if sendMsgName == "ActPayReq" then
                    getType = PayManager:getItemGetType(pbData.act_type, pbData.id)
                elseif sendMsgName == "ActPaidResultReq" then -- 支付上报统一处理
                    if pbData.gift then
                        for _, gift in ipairs(pbData.gift) do
                            getType = PayManager:getItemGetType(gift.act_type, gift.id)
                            break
                        end
                    end
                elseif sendMsgName == "AppStorePaidReq" then -- ios支付上报统一处理
                    if pbData.gift then
                        for _, gift in ipairs(pbData.gift) do
                            getType = PayManager:getItemGetType(gift.act_type, gift.id)
                            break
                        end
                    end
                end

                if EDITOR_MODE and not getType then
                    Logger.logFatal("sendMessage server have rewards but not getType, check it! manager = ", receiveCallback.binder.__cname)
                end

                GFunc.addRewards(pbData.rewards, getType)
            end
            if pbData.err_code == GConst.ERROR_STR.SUCCESS and pbData.costs and not self:getNotAddCostsRsp(msgName) then
                local getType = receiveCallback.getType
                if sendMsgName == "ActPayReq" then
                    getType = PayManager:getItemGetType(pbData.mall_type, pbData.id)
                elseif sendMsgName == "ActPaidResultReq" then -- 支付上报统一处理
                    if pbData.gift then
                        for _, gift in ipairs(pbData.gift) do
                            getType = PayManager:getItemGetType(gift.mall_type, gift.id)
                            break
                        end
                    end
                end
                if EDITOR_MODE and not getType then
                    Logger.logFatal("sendMessage server have costs but not getType, check it! manager = ", receiveCallback.binder.__cname)
                end
                GFunc.addCosts(pbData.costs, getType)
            end
            if EDITOR_MODE and not self:getNotCheckResponse(ProtoMsgDispatch:getReqMsgByMsgName(msgName)) then -- 检查responseData
                local fullMsgName = ProtoMsgDispatch:getMsgFullNameByMsgName(msgName)
                local serverResult = pb.decode(fullMsgName, data)
                self:checkCoin(responseData, serverResult)
                if not GFunc.checkTableValueSame(responseData, serverResult, true) then
                    Logger.logWarningBox(msgName .. " : responseData not equate, please check it!")
                    Logger.logHighlight("------responseData not equate-------------")
                    Logger.printTable(responseData)
                    Logger.printTable(serverResult)
                end
            end
            if receiveCallback.beforeOver then
                UIManager:hideWaitNet()
                self.tillBeforeOverCount = self.tillBeforeOverCount - 1
                if self.tillBeforeOverCount < 0 then
                    self.tillBeforeOverCount = 0
                end
            end
			self:recycleReceiveCallback(receiveCallback)
            if lockGame then
                UIManager:hideWaitNet()
            end
            if ok and pbData and receiveFunc then
                if sendInfo then
                    if sendInfo.msgName == ProtoMsgType.FromMsgEnum.PipedReq then
                        pbData.reqData = sendInfo.params.data
                    else
                        pbData.reqData = sendInfo.params
                    end
                end
                receiveFunc(receiveCallback.binder, pbData)
            end
		end
	end

    if sendInfo then
        self:trySend()
    end
end

function NetManager:onError(socketName, errorType, errorMsg)
    self.isSending = false
    if socketName == NetManager.MAIN_SOCKET_NAME then
        Logger.logHighlight("game net error:errorType = %d, errorMsg = %s", errorType, errorMsg)
        if errorType == NetErrorCode.ConnectFailed or
            errorType == NetErrorCode.ExceptionError or
            errorType == NetErrorCode.ReceiveBufferError or
            errorType == NetErrorCode.BeginSendError or
            errorType == NetErrorCode.PeerDisconnect or
            errorType == NetErrorCode.DataParseError then
            self:disconnect(socketName)
        elseif errorType == NetErrorCode.ServerRefuseReconnect then -- 服务器拒绝客户端的重连请求，需要关闭连接，重新登录
            ModuleManager.LoginManager:reloginOnReconnectRefuse()
        elseif errorType == NetErrorCode.ServerKickNtfClient or
            errorType == NetErrorCode.OtherDeviceLogin then
            self:closeAll(errorType)
        elseif errorType == NetErrorCode.DNSParseDomainNameError then
            self:closeAll()
        else -- 未定义的error
            self:disconnectAndReconnect()
        end
        if not self.disconnectTime then
            self.disconnectTime = Time:getServerTime()
            BIReport:postNetWorkOpt(BIReport.NETWORK_OPT_TYPE.DISCONNECT, UIManager:getTopUIIndex(), errorType)
        end
    elseif socketName == NetManager.CHAT_SOCKET_NAME then
        Logger.logHighlight("chat net error:errorType = %d, errorMsg = %s", errorType, errorMsg)
        self:disconnect(socketName)
    end
end

function NetManager:setReconnectData()
    local msgFullName = ProtoMsgDispatch:getMsgFullNameByMsgName(ProtoMsgType.FromMsgEnum.ReconnectReq)
    CS.BF.BFMain.Instance.NetMgr.loginReqData = pb.encode(msgFullName, { recv_id = self.recvId })
end

function NetManager:dispatch(msgName, data)
	if self.msgCallbacks[msgName] then
		for module, callback in pairs(self.msgCallbacks[msgName]) do
			callback(module, data)
		end
	end
end

function NetManager:getKickOutReasonEnum(enum)
    return ProtoMsgDispatch:getKickOutReasonEnum(enum)
end

function NetManager:joinChatChannel()
    -- if self.joinChatId then
    --     SchedulerManager:unscheduleGlobal(self.joinChatId)
    --     self.joinChatId = nil
    -- end
    -- if DataManager.ChatData:getIsJoinWorldRoom() then
    --     return
    -- end
    -- local wordChannelId = DataManager.ChatData:getChatWorld():getLocalChatWorldChannleId()
	-- local args = {
	-- 	id = wordChannelId,
	-- 	adjust = true	-- 登录时第一次进入房间必须能接受调整,手动切房间的时候为false
	-- }
	-- self:send(ProtoMsgType.FromMsgEnum.JoinWorldRoomReq, args, function(data)
	-- 	if data.status == 0 then
	-- 		DataManager.ChatData:initChat(data)
	-- 	else
	-- 		self.joinChatId = self:performWithDelayGlobal(function ()
    --             self.joinChatId = nil
    --             self:joinChatChannel()
    --         end, JOIN_CHAT_INTERVAL)
	-- 	end
	-- end, NetManager.CHAT_SOCKET_NAME, true)
end

function NetManager:registerMsgCallback(msgName, module, callback)
    if not self.msgCallbacks[msgName] then
        self.msgCallbacks[msgName] = {}
    end
    self.msgCallbacks[msgName][module] = callback
end

if NOT_PUBLISH then
    NetManager._registerMsgCallback = NetManager.registerMsgCallback
    function NetManager:registerMsgCallback(msgName, module, callback)
        if debug.getinfo(2, "f").func ~= ServerPushManager.addServerPushListener then
            Logger.logFatal("this method only called by ServerPushManager.addServerPushListener")
        end
        self:_registerMsgCallback(msgName, module, callback)
    end
end

function NetManager:unRegisterMsgCallback(msgName, module)
    if self.msgCallbacks[msgName] then
        self.msgCallbacks[msgName][module] = nil
    end
end

function NetManager:clearMsgCallbacks()
    self.msgCallbacks = {}
    self.receiveCallbacks = {}
end

function NetManager:closeAndClear()
    if not self.initSucc then
        return
    end
    if self.tillBeforeOverSid then
        SchedulerManager:unscheduleGlobal(self.tillBeforeOverSid)
        self.tillBeforeOverSid = nil
    end
    if self.reconnectMainId then
        SchedulerManager:unscheduleGlobal(self.reconnectMainId)
        self.reconnectMainId = nil
    end
    if self.reconnectChatId then
        SchedulerManager:unscheduleGlobal(self.reconnectChatId)
        self.reconnectChatId = nil
    end
    if self.joinChatId then
        SchedulerManager:unscheduleGlobal(self.joinChatId)
        self.joinChatId = nil
    end

    if self.autoReAuthSid then
        SchedulerManager:unscheduleGlobal(self.autoReAuthSid)
        self.autoReAuthSid = nil
    end

    self.isSending = false
    self.sendQueue = {}
    self.tillBeforeOverCount = 0
    self.msgId = 0
    self.activeReconnectMainFlag = false
    self.reconnectMainFlag = false
    CS.BF.BFMain.Instance.NetMgr:RemoveAllLuaCallback()
    local mainSocketName = NetManager.MAIN_SOCKET_NAME
    local chatSocketName = NetManager.CHAT_SOCKET_NAME

    self.isClosedMap[mainSocketName] = true
    self.isClosedMap[chatSocketName] = true
    self.alreadyConnected[mainSocketName] = false
    self.alreadyConnected[chatSocketName] = false

    CS.BF.BFMain.Instance.NetMgr:Close(mainSocketName)
    -- CS.BF.BFMain.Instance.NetMgr:Close(chatSocketName)

    self:clearMsgCallbacks()
    UIManager:hideWaitNet(true)
end

function NetManager:saveSendQueue()
    local list = {}
	for _, info in ipairs(self.sendQueue) do
        if not self:isNotSave(info.msgName) then
            table.insert(list, {
                msgName = info.msgName,
                params = info.params,
                socketName = info.socketName,
            })
        end
	end
    if EDITOR_MODE and #list > 0 then
        local printList = {}
        for k, v in ipairs(list) do
            local msgName = v.msgName
            local params = v.params
            if msgName == ProtoMsgType.FromMsgEnum.PipedReq then
                msgName = ProtoMsgDispatch:getReqMsgNameByMsgId(params.msg_id)
                params = params.data
            end
            table.insert(printList, {
                msgName = msgName,
                params = params,
                socketName = v.socketName,
            })
        end
        Logger.logHighlight("[NetManager]剩余消息队列缓存：%s", json.encode(printList))
    end
	LocalData:saveSendQueue(list)
end

function NetManager:isNotSave(msgName)
    if msgName == ProtoMsgType.FromMsgEnum.LoginReq or
        msgName == ProtoMsgType.FromMsgEnum.SyncReq or
        msgName == ProtoMsgType.FromMsgEnum.IdleRewardReq or
        msgName == ProtoMsgType.FromMsgEnum.IdleExtraRewardReq or
        msgName == ProtoMsgType.FromMsgEnum.MailCycleReq or
        msgName == ProtoMsgType.FromMsgEnum.GMReq then
        return true
    end
    return false
end

function NetManager:getNotCheckResponse(msgName)
    return true
end

function NetManager:getNotAddCostsRsp(msgName)
    if msgName == ProtoMsgType.FromMsgEnum.ChapterTrainRsp then
        return true
    end
    return false
end

function NetManager:pipedMessage(msgName, params)
    return msgName, params -- 屏蔽PipedReq
    -- if msgName == ProtoMsgType.FromMsgEnum.LoginReq or
    --     msgName == ProtoMsgType.FromMsgEnum.SyncReq or
    --     msgName == ProtoMsgType.FromMsgEnum.IdleRewardReq or
    --     msgName == ProtoMsgType.FromMsgEnum.IdleExtraRewardReq or
    --     msgName == ProtoMsgType.FromMsgEnum.MailCycleReq or
    --     msgName == ProtoMsgType.FromMsgEnum.GMReq then
    --     return msgName, params
    -- end
    -- local msgId = ProtoMsgDispatch:getMsgIdByMsgName(msgName)
    -- self.msgId = self.msgId + 1
    -- local pipedStruct = {
    --     id = self.msgId,
    --     ts = Time:getServerTime() * 1000,
    --     msg_id = msgId,
    --     data = params,
    -- }
    -- return ProtoMsgType.FromMsgEnum.PipedReq, pipedStruct
end

if EDITOR_MODE then
	function NetManager:_checkDebugFuncMap()
        if self._debugFuncMap == nil then
            self._debugFuncMap = {
                [BaseModule.sendMessage] = true,
                [BaseModule.sendMessageTillBeforeOver] = true,
            }
        end
    end

    NetManager._releaseSend = NetManager.send
    function NetManager:send(obj, msgName, params, responseData, callback, lockGame, noRsp, getType)
        -- 参数检查
        if (responseData.rewards or responseData.costs) and not getType and msgName ~= ProtoMsgType.FromMsgEnum.GMReq then
            Logger.logFatal("sendMessage have rewards or have costs but not getType, check it! manager = ", self.__cname)
            return
        end

        if not params then
            Logger.logFatal("NetManager send params is nil %s", msgName)
        end

        if not responseData then
            Logger.logFatal("NetManager send responseData is nil %s", msgName)
        end

        local msgFullName = ProtoMsgDispatch:getMsgFullNameByMsgName(msgName)
        local bytes = pb.encode(msgFullName, params)
        if not bytes then
            Logger.logFatal("NetManager send params error %s", msgName)
            return
        end

        if not self:getNotCheckResponse(msgName) then
            local fullMsgName = ProtoMsgDispatch:getMsgFullNameByMsgName(ProtoMsgDispatch:getRspMsgByMsgName(msgName))
            for name, number, type in pb.fields(fullMsgName) do
                if name ~= "err_code" and responseData[name] == nil then
                    Logger.logFatal("NetManager send responseData error %s not have %s", msgName, name)
                    return
                end
            end
        end
        -- end 参数检查

        self:_checkDebugFuncMap()
        local currFunc = debug.getinfo(2, "f").func
        if obj ~= NetManager and self._debugFuncMap[currFunc] == nil then
            Logger.logFatal("you can not call NetManager:send directly")
        end
		local findCallback = false
		if obj ~= NetManager and obj.__cname ~= "PayManager" then
			for _, fieldValue in pairs(obj.class) do
				if fieldValue == callback then
					findCallback = true
				end
			end
		else
			findCallback = true
		end

		if not findCallback then
			Logger.logFatal("[NetManager]callback of NetManager:send is not %s function", obj.__cname)
		end
        self:_releaseSend(obj, msgName, params, responseData, callback, lockGame, noRsp, getType)
    end

    NetManager._releaseSendTillBeforeOver = NetManager.sendTillBeforeOver
    function NetManager:sendTillBeforeOver(obj, msgName, params, responseData, callback, ...)
        self:_checkDebugFuncMap()
        local currFunc = debug.getinfo(2, "f").func
        if obj ~= NetManager and self._debugFuncMap[currFunc] == nil then
            Logger.logFatal("[NetManager]you can not call NetManager:send directly")
        end
        self:_releaseSendTillBeforeOver(obj, msgName, params, responseData, callback, ...)
    end
end

function NetManager:checkCoin(responseData, serverResponseData)
end

function NetManager:tagAuthFailToLogin()
    self.authFailToLogin = true
end

function NetManager:clearTagAuthFailToLogin()
    self.authFailToLogin = false
end

return NetManager