-- Ensures that SenseManager does nothing if accidentally required on client

if not RunService:IsServer() then
	return SenseManager
end

local octree = Octree.new()
local budgetRunner = BudgetRunner.new(FRAME_BUDGET)
local agentQueue = CircularQueue.new()
local agentIdToRootPart = {}
local enabled = true

local queryCount = 0
local queryDuration = 0
local lastMetricReportTime = tick()

function SenseManager.registerEvent(eventData)
	if enabled then
		return octree:registerEvent(eventData)
	else
		return nil
	end
end

function SenseManager.registerAgent(agentId, rootPart)
	if enabled then
		agentIdToRootPart[agentId] = rootPart
		agentQueue:push(agentId)
	end
end

function SenseManager.unregisterAgent(agentId)
	if enabled then
		agentIdToRootPart[agentId] = nil
		agentQueue:remove(agentId)
	end
end

function SenseManager.removeEventById(eventId)
	if enabled then
		octree:removeEventById(eventId)
	end
end

function SenseManager.queryRegion(center, volume)
	if enabled then
		local startTime = tick()
		local result = octree:queryRegion(center, volume)
		local endTime = tick()
		
		queryDuration = queryDuration + endTime - startTime
		queryCount = queryCount + 1
		
		return result
	else
		return {}
	end
end

function SenseManager.querySphere(center, radius)
	if enabled then
		local startTime = tick()
		local diameter = radius * 2
		local result = {}
		
		for _, event in ipairs(octree:queryRegion(center, Vector3.new(diameter, diameter, diameter))) do
			if (event.position - center).Magnitude < radius then
				table.insert(result, event)
			end
		end
		local endTime = tick()
		
		queryDuration = queryDuration + endTime - startTime
		queryCount = queryCount + 1
	
		return result
	else
		return {}
	end
end

function SenseManager.setEnabled(state)
	if state ~= enabled then
		if state then
			octree = Octree.new()
			agentQueue = CircularQueue.new()
			agentIdToRootPart = {}
		else
			octree:destroy()
			agentQueue = nil
			agentIdToRootPart = nil
		end
		
		enabled = state
	end
end

RunService.Heartbeat:Connect(function()
	local frameBudgetFull = false
	
	local startAgentId = agentQueue:next()
	local currAgentId = startAgentId
	if currAgentId then
		budgetRunner:tear()
		repeat
			local rootPart = agentIdToRootPart[currAgentId]
			if rootPart then
				frameBudgetFull = not budgetRunner:run(function()
					Blackboards.getAgentBlackboard(currAgentId).senseEvents = SenseManager.querySphere(rootPart.Position, SENSE_RADIUS)
				end)
			end
			currAgentId = agentQueue:next()
		until(frameBudgetFull or currAgentId == startAgentId)
	end
	
	local currTime = tick()
	if currTime >= lastMetricReportTime + METRIC_REPORT_RATE then
		SenseManagerStatEvent:FireAllClients(currTime - lastMetricReportTime, queryCount, queryDuration)
		queryDuration = 0
		queryCount = 0
		lastMetricReportTime = currTime
	end
end)

return SenseManager
