--[[----------------------------------------------------------------------------

KSBucketBrigade.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2010 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

--[[

KSBucketBridge:

	The bucket brigade mechanism support provides a way to keep items alive
	for a certain number of GC cycles.
	
		-- FIXME [mhamburg 2010-11-09]: Improve documentation.
	
		-- [erwright 2010-11-04]: I was surprised in looking at the unit tests that the
		-- items added to a brigade don't get collected unless the brigade() function is called
		-- repeatedly to add more items.  That behavior is not apparent from the description of the
		-- class, and needs to be clarified.
		
	Bucket brigades look like functions that you call with single items.
	
	function brigade( item )
		-- Protects item in the indicated brigade.
		-- Returns item.
		-- Item should be nil, a table, a userdata, a function, or '< purge >'.
		
	We use the magic value '< purge >' (which as a string isn't particularly
	collectable anyway) to get a brigade to purge its current contents.

	function KSBucketBridge.makeBrigade( [ depth ] )
		-- Depth defaults to 1 if not supplied.
		-- If supplied, it needs to be an integer >= 1.
		
	function KSBucketBridge.purgeAllBrigades()
		-- Purge all brigades.
	
]]

--------------------------------------------------------------------------------

local KSTableUtils = require 'KSTableUtils'

local KSTableUtils_makeFullyWeak = KSTableUtils.makeFullyWeak

local type = type
local assert = assert
local pairs = pairs
local math_floor = math.floor

--------------------------------------------------------------------------------

local KSBucketBrigade = { }

--------------------------------------------------------------------------------

-- To support purging all brigades, we need a set of all of the brigages.

local brigades = KSTableUtils_makeFullyWeak()

--------------------------------------------------------------------------------

--[[

A brigade is stored as an array of tables together with a weak table with one
entry leading to the head entry. Requesting an entry from the head will advance
the brigade if the head has lost its entry.

	Brigade
		buckets --> array of buckets indexed circularly
		head --> weak table --> link object --> head bucket
		
The complicated chaining off of head is so that garbage collection can collect
the link object since it is only referenced from the weak head table.

]]

--------------------------------------------------------------------------------

function KSBucketBrigade.makeBrigade( depth )

	depth = depth or 1
	
	local index = 0
	
	assert( type( depth ) == 'number' and 1 <= depth )
	
	assert( math_floor( depth ) == depth ) -- integer

	local buckets = { }
	
	local head = KSTableUtils_makeFullyWeak()
	
	local result = function( item )
	
		-- Purging resets the data structures
	
		if item == '< purge >' then
			if buckets[ 1 ] then
				buckets = { }
				head[ 1 ] = nil
				index = 0
			end
			return
		end
		
		-- It's nice to support nil
		
		if item == nil then
			return item
		end
		
		-- Otherwise check for an appropriate item type
		
		local type_item = type( item )
		
		assert( type_item == 'table' or type_item == 'userdata' or type_item == 'function',
			"Brigades only work with collectable items." )
			
		-- Get the head bucket.
		
		local headLink = head[ 1 ]
		
		local headBucket
			
		if headLink ~= nil then
		
			headBucket = headLink[ 1 ]
		
		else
		
			-- Go construct it.
			
			headBucket = { }
			
			head[ 1 ] = { headBucket }
			
			-- Put it in the circular array.
			
			index = index + 1
			
			if depth < index then
				index = 1
			end
			
			buckets[ index ] = headBucket
			
		end
		
		-- Add the item to the bucket
		
		headBucket[ item ] = true
		
		-- Allow this to be used as a pass through function
		
		return item
	
	end
	
	-- Remember this brigade in case we want to purge all of the brigades
	
	brigades[ result ] = true
	
	return result

end

--------------------------------------------------------------------------------

function KSBucketBrigade.purgeAllBrigades()

	for brigade in pairs( brigades ) do
		brigade( '< purge >' )
	end

end

--------------------------------------------------------------------------------

return KSBucketBrigade
