require("utils")
require("timed_actions")

-- calls callback when all objects with supplied ids get disabled
-- one time use, all listeners are unregistered after callback is called
function onGroupDisabled(objectIds, callback)
	local callbackSentry = nil
	local listener = function(objectId, isEnabled) if not isEnabled then callbackSentry() end end
	
  	callbackSentry = function()
    	local isAllDisabled = true
		for k, id in pairs(objectIds) do
			isAllDisabled = isAllDisabled and (not Object.isEnabled(id))
		end
		if isAllDisabled then
			callback()
			for k, id in pairs(objectIds) do
				Object.unregister(id, listener)
			end
		end
    end

	for k, id in pairs(objectIds) do
		Object.onEnable(id, listener)
	end
end

-- enable/disable group of objects with specified ids
function groupSetEnabled(objectIds, isEnabled) 
	for _, id in pairs(objectIds) do
		Object.setEnabled(id, isEnabled)
	end
end

-- enables group of objects when player enters specified area
-- and disables them as player leaves the area 
function setupGroupTrigger(triggerAreaId, objectIds)
	Object.onEnterArea(triggerAreaId, 
		function(areaId, objectId)
			groupSetEnabled(objectIds, true)
		end)
	Object.onLeaveArea(triggerAreaId, 
		function(areaId, objectId)
			groupSetEnabled(objectIds, false)
		end)
end

-- links state of target object with state of source object,
-- so that target is disabled/enabled together with source
function linkObjectStates(sourceId, targetId)
	Object.onEnable(sourceId, function(id, isEnabled) Object.setEnabled(targetId, isEnabled) end)
end

-- access actor group by ActorGroup[groupId]
-- access individual actors by ActorGroup[groupId].actors[key],
-- but you'll need to know what type of key to use, and that depends on the way the group was created
-- 
ActorGroup = {
	DEBUG = true,
	id = 0,         -- id of this group
	groupCount = 0, -- total number of groups of this type
	actors = nil,   -- actors in this group
	count = 0,      -- number of actors in this group
	regex = "GROUP_([%d]+)_([%d]+)" -- default regex with numeric group ids and ordered actors
}

-- Determines wheather supplied actor belongs to any matching group
function ActorGroup:processActor(actor)
	if not actor.name then
		log("ActorGroup:processActor(): Sorry, can't process unnamed actor", self.DEBUG)
		return
	end

	local groupId = nil
	local order = nil
	groupId, order = string.match(actor.name, self.regex)
	groupId = tonumber(groupId) or groupId -- makes id a number, if it can be a number
	order = tonumber(order) -- we don't care if it can't be a number

	if groupId then
		local group = self[groupId]
		if not group then -- need to create new group
			group = self:new(groupId)
			self[groupId] = group
		end
		actor.order = order
		group:addActor(actor)
	end
end

function ActorGroup:addActor(actor)
	log("ActorGroup:addActor(): group/actor: " .. self.id .. " / " .. (actor.name or actor.id), self.DEBUG)
	table.insert(self.actors, actor)
	if actor.order then
		-- looks like actors are ordered
		table.sort(self.actors, self.compareActorOrder)
	end
	self.count = self.count + 1
end

-- compares two ordered actors
function ActorGroup.compareActorOrder(actor1, actor2)
	if not (actor1 == nil) and not (actor2 == nil) and actor1.order and actor2.order then
		return  actor1.order < actor2.order
	end
	return false
end

-- Creates new ActorGroup with specified id and actors (optional)
function ActorGroup:new(id, actors)
	log("ActorGroup:new(): " .. id, self.DEBUG)
	local group = { actors = {}, id = id, count = 0 }
	if actors then
		group.actors = actors
		group.count = count(actors)
	end
	setmetatable(group, self)
	self.__index = self
	self.groupCount = self.groupCount + 1
	return group
end

-- Creates completely new type of ActorGroup, with its own regex pattern 
-- use this if you need to have more than one grouping types,
-- for example, you can create ZoneGroups, NeuralChains, GravitonGroups, each grouping different types of objects
function ActorGroup:newInstance(regex)
	local group = {
		regex = regex,
		id = 0,         -- id of this group
		groupCount = 0, -- total number of groups of this type
		actors = nil,   -- actors in this group
		count = 0,      -- number of actors in this group
	}
	setmetatable(group, self)
	self.__index = self

	return group
end

-- callback is function(i, n, actor)
function ActorGroup:forEach(stepDelay, callback)
	local counter = 0
	local total = self.count
	local delay = 0
	for _,actor in pairs(self.actors) do
		counter = counter + 1
		local i = counter
		TimedActionManager.delayAction(delay, function() callback(i, total, actor) end)

		delay = delay + stepDelay
	end
end

