--


if IsServer then
	function HandlerFireClient(handler, client, ...)
		if LoggingNetwork then
			table.insert(LoggingNetwork[client][handler.Remote].dataOut, GetParamString(...))
		end
		
		return handler.Remote:FireClient(client, ...)
	end
	
	--
	
	function Network:GetPlayers()
		return Players:GetPlayers()
	end

	function Network:GetPlayerPosition(player)
		return player and player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.Position or nil
	end
	
	--
	
	function Network:FireClient(client, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end
		
		HandlerFireClient(handler, client, ...)
	end
	
	function Network:FireAllClients(name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end
		
		for i,v in pairs(self:GetPlayers()) do
			HandlerFireClient(handler, v, ...)
		end
	end
	
	function Network:FireOtherClients(client, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end
		
		for i,v in pairs(self:GetPlayers()) do
			if v ~= client then
				HandlerFireClient(handler, v, ...)
			end
		end
	end
	
	function Network:FireOtherClientsWithinDistance(client, dist, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end
		
		local pos = self:GetPlayerPosition(client)
		if not pos then
			return
		end
		
		for _,player in pairs(self:GetPlayers()) do
			if player ~= client then
				local otherPos = self:GetPlayerPosition(player)
				
				if otherPos and (pos - otherPos).Magnitude <= dist then
					HandlerFireClient(handler, player, ...)
				end
			end
		end
	end
	
	function Network:FireAllClientsWithinDistance(pos, dist, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end
		
		for _,player in pairs(self:GetPlayers()) do
			local otherPos = self:GetPlayerPosition(player)
			
			if otherPos and (pos - otherPos).Magnitude <= dist then
				HandlerFireClient(handler, player, ...)
			end
		end
	end
	
	function Network:InvokeClientWithTimeout(timeout, client, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end
		
		return SafeInvoke(timeout, handler, client, ...)
	end
	
	function Network:InvokeClient(...)
		return self:InvokeClientWithTimeout(60, ...)
	end
	
	
	function Network:LogTraffic(...)
		FastSpawn(self.LogTrafficAsync, self, ...)
	end
	
	function Network:LogTrafficAsync(duration, output)
		output = output or warn
		
		if LoggingNetwork then return end
		output("Logging Network Traffic...")
		
		LoggingNetwork = setmetatable({}, { __index = function(t, i)
			t[i] = setmetatable({}, { __index = function(t, i) t[i] = { dataIn = {}, dataOut = {} } return t[i] end })
			return t[i]
		end})
		
		local start = os.clock()
		wait(duration)
		local effDur = os.clock() - start
			
		local clientTraffic = LoggingNetwork
		LoggingNetwork = nil
		
		for player,remotes in pairs(clientTraffic) do
			local totalReceived = 0
			local totalSent = 0
			
			for remote,data in pairs(remotes) do
				totalReceived += #data.dataIn
				totalSent += #data.dataOut
			end
			
			output(string.format("Player '%s', total received/sent: %d/%d", player.Name, totalReceived, totalSent))
			
			for remote,data in pairs(remotes) do
				-- Incoming
				
				local list = data.dataIn
				if #list > 0 then
					output(string.format("   %s %s: %d (%.2f/s)", "FireServer", remote.Name, #list, #list / effDur))

					local count = math.min(#list, 3)
					for i = 1, count do
						local index = math.floor(1 + (i - 1) / math.max(1, count - 1) * (#list - 1) + 0.5)
						output(string.format("      %d: %s", index, list[index]))
					end
				end

				-- Outgoing
				
				local list = data.dataOut
				if #list > 0 then
					output(string.format("   %s %s: %d (%.2f/s)", "FireClient", remote.Name, #list, #list / effDur))
					
					local count = math.min(#list, 3)
					for i = 1, count do
						local index = math.floor(1 + (i - 1) / math.max(1, count - 1) * (#list - 1) + 0.5)
						output(string.format("      %d: %s", index, list[index]))
					end
				end
			end
		end
	end
else
	EventsFolder.ChildAdded:Connect(function(child) GetEventHandler(child.Name) end)
	for _,child in pairs(EventsFolder:GetChildren()) do GetEventHandler(child.Name) end
	
	FunctionsFolder.ChildAdded:Connect(function(child) GetFunctionHandler(child.Name) end)
	for _,child in ipairs(FunctionsFolder:GetChildren()) do GetFunctionHandler(child.Name) end
	
	--
	
	function Network:FireServer(name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end
		
		if handler.Remote then
			handler.Remote:FireServer(...)
		else
			local params = table.pack(...)
			
			AddToQueue(handler, function()
				handler.Remote:FireServer(unpack(params))
			end, true)
		end
	end
	
	function Network:InvokeServerWithTimeout(timeout, name, ...)
		local handler = GetFunctionHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteFunction"):format(name))
		end
		
		if not handler.Remote then
			-- Code below will break if the callback passed to AddToQueue is called
			-- before the function returns. This should never happen unless somebody
			-- changed how AddToQueue works.
			local thread = coroutine.running()
			
			AddToQueue(handler, function()
				ResumeThread(thread)
			end, true)
			
			YieldThread()
		end

		local result = table.pack(SafeInvoke(timeout, handler, ...))
		assert(result[1] == true, "InvokeServer error")

		return unpack(result, 2)
	end
	
	function Network:InvokeServer(name, ...)
		return self:InvokeServerWithTimeout(nil, name, ...)
	end
end