package ClosureForGroups

public interface ForGroupCallback
	function callback(unit u)

/** This is used as dummy in the GroupEnum calls and should not be used */
constant DUMMY_GROUP = CreateGroup()

// ====Callback stack for nested usage====
ForGroupCallback array tempCallbacks
var tempCallbacksCount = 0
var maxCount = INT_MAX
var iterCount = 0
constant filter = Filter(() -> filterCallback(GetFilterUnit()))

function filterCallback(unit filter)
	if iterCount < maxCount
		currentCallback().callback(filter)
	iterCount++

function pushCallback(ForGroupCallback c)
	tempCallbacks[tempCallbacksCount] = c
	tempCallbacksCount++
	iterCount = 0
	maxCount = INT_MAX

function popCallback()
	DUMMY_GROUP.clear()
	tempCallbacksCount--
	destroy tempCallbacks[tempCallbacksCount]

function currentCallback() returns ForGroupCallback
	return tempCallbacks[tempCallbacksCount - 1]
// ====Stack end====

/** Executes the given closure for every unit in this group,
	removing the units from the group as processed */
public function group.forEachFrom(ForGroupCallback cb)
	for u from this
		cb.callback(u)
	destroy cb

/** Executes the given closure for every unit in this group,
	keeping all units in the group */
public function group.forEachIn(ForGroupCallback cb)
	for u in this
		cb.callback(u)
	destroy cb

/** Executes the given closure for every unit of the given type.
	Remember that names of custom units are "custom_[typeId]" */
public function forUnitsOfType(string unitname, ForGroupCallback c)
	pushCallback(c)
	GroupEnumUnitsOfType(DUMMY_GROUP, unitname, filter)
	popCallback()

/** Executes the given closure for every unit of the given player.
	Cancels itself after *count* iterations */
public function forUnitsOfTypeCounted(string unitname, int count, ForGroupCallback c)
	pushCallback(c)
	maxCount = count
	GroupEnumUnitsOfType(DUMMY_GROUP, unitname, filter)
	popCallback()

/** Executes the given closure for every unit of the given player */
public function forUnitsOfPlayer(player p, ForGroupCallback c)
	pushCallback(c)
	GroupEnumUnitsOfPlayer(DUMMY_GROUP, p, filter)
	popCallback()

/** Executes the given closure for every existing unit */
public function forUnitsAll(ForGroupCallback c)
	for i = 0 to bj_MAX_PLAYER_SLOTS - 1
		pushCallback(c)
		GroupEnumUnitsOfPlayer(DUMMY_GROUP, players[i], filter)
		DUMMY_GROUP.clear()
		tempCallbacksCount--
	destroy c

/** Executes the given closure for every unit in the given rect */
public function forUnitsInRect(rect r, ForGroupCallback c)
	pushCallback(c)
	GroupEnumUnitsInRect(DUMMY_GROUP, r, filter)
	popCallback()

/** Executes the given closure for every unit in the given rect.
	Cancels itself after *count* iterations */
public function forUnitsInRectCounted(rect r, int count, ForGroupCallback c)
	pushCallback(c)
	maxCount = count
	GroupEnumUnitsInRect(DUMMY_GROUP, r, filter)
	popCallback()

/** Executes the given closure for every unit in range of the given position */
public function forUnitsInRange(vec2 pos, real radius, ForGroupCallback c)
	forUnitsInRange(pos, radius, false, c)

/** Executes the given closure for every unit in range of the given position
	With collisionSizeFiltering true it will take the units' collision into account. */
public function forUnitsInRange(vec2 pos, real radius, bool collisionSizeFiltering, ForGroupCallback c)
	if collisionSizeFiltering
		pushCallback(c)
		GroupEnumUnitsInRange(ENUM_GROUP, pos.x, pos.y, radius + MAX_COLLISION_SIZE, null)
		for u from ENUM_GROUP
			if IsUnitInRangeXY(u, pos.x, pos.y, radius)
				c.callback(u)
		popCallback()
	else
		pushCallback(c)
		GroupEnumUnitsInRange(DUMMY_GROUP, pos.x, pos.y, radius, filter)
		popCallback()

/** Executes the given closure for every unit in range of the given position
	Cancels itself after *count* iterations */
public function forUnitsInRangeCounted(vec2 pos, real radius, int count, ForGroupCallback c)
	pushCallback(c)
	maxCount = count
	GroupEnumUnitsInRange(DUMMY_GROUP, pos.x, pos.y, radius, filter)
	popCallback()

/** Executes the given closure for every unit selected by the given player */
public function forUnitsSelected(player p, ForGroupCallback c)
	pushCallback(c)
	GroupEnumUnitsSelected(DUMMY_GROUP, p, filter)
	popCallback()

/** Executes the given closure for the closest unit inside the given range of the given position,
	matching the provided filter. If there is no unit in range, the closure will be run with "null" */
public function forNearestUnit(vec2 pos, real range, filterfunc filter, ForGroupCallback c)
	pushCallback(c)
	GroupEnumUnitsInRange(ENUM_GROUP, pos.x, pos.y, range, filter)
	unit nearest = null
	var bestDist = REAL_MAX
	for u from ENUM_GROUP
		let distSq = pos.distanceToSq(u.getPos())
		if distSq < bestDist
			nearest = u
			bestDist = distSq
	c.callback(nearest)
	popCallback()

// Destructables
// ====Callback stack for nested usage====
public interface ForGroupCallbackD
	function callback(destructable d)

function filterD() returns boolean
	currentCallbackD().callback(GetEnumDestructable())
	return false

ForGroupCallbackD array tempCallbacksD
int tempCallbacksDCount = 0

function pushCallbackD(ForGroupCallbackD c)
	tempCallbacksD[tempCallbacksDCount] = c
	tempCallbacksDCount++

function popCallbackD()
	tempCallbacksDCount--
	destroy tempCallbacksD[tempCallbacksDCount]

function currentCallbackD() returns ForGroupCallbackD
	return tempCallbacksD[tempCallbacksDCount - 1]
// ====Stack end====

let enumRect = Rect(0,0,0,0)

/** Executes the given closure for all destructables in a rect
	that incompasses the circle with the range radius at the given position. */
public function forDestructablesInRange(vec2 pos, real range, ForGroupCallbackD cb)
	forDestructablesInRange(pos, range, null, cb)

/** Executes the given closure for all destructables in a rect
	that incompasses the circle with the range radius at the given position. */
public function forDestructablesInRange(vec2 pos, real range, boolexpr filter, ForGroupCallbackD cb)
	enumRect.resize(vec2(pos.x - range, pos.y - range), vec2(pos.x + range, pos.y + range))
	forDestructablesInRect(enumRect, filter, cb)

/** Executes the given closure for all destructables in the given rect */
public function forDestructablesInRect(rect r, ForGroupCallbackD cb)
	forDestructablesInRect(r, null, cb)

/** Executes the given closure for all destructables in the given rect */
public function forDestructablesInRect(rect r, boolexpr filter, ForGroupCallbackD cb)
	pushCallbackD(cb)
	EnumDestructablesInRect(r, filter, () -> filterD())
	popCallbackD()

// Nearest Destructable helpers
destructable nearestD = null
var nearestDDist = REAL_MAX
var gpos = ZERO2

/** Executes the given closure for the closes destructable in the given rect.
	If there is no destructable in range, the closure will be run with "null" */
public function forNearestDestructable(vec2 pos, real range, ForGroupCallbackD c)
	nearestD = null
	nearestDDist = range
	gpos = pos
	enumRect.resize(vec2(pos.x - range, pos.y - range), vec2(pos.x + range, pos.y + range))
	EnumDestructablesInRect(enumRect, null) ->
		let dist = gpos.distanceTo(GetEnumDestructable().getPos())
		if dist < nearestDDist
			nearestD = GetEnumDestructable()
			nearestDDist = dist

	c.callback(nearestD)
	destroy c