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

KSPCallLoops.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2011 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.

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

--[[

	KSPCallLoops:
	
	Support for looping over a series of values invoking an action function
	within a pcall context while limiting the frequency with which we invoke
	pcall (which can be an expensive operation).
	
	function KSPCallLoops.pdoWithIterator( actionFn, errorFn, iterFn, s, k )
		For each tuple returned from the iterator, invoke actionFn. If
		this function fails, then we call errorFn if non-nil with the error
		message and the parameters passed to actionFn. If errorFn throws,
		then stop. Returns true if either actionFn or errorFn succeeds for
		each tuple. Returns false and the message if errorFn throws.
		Errors in the iterator are reported immediately as false, msg.
		
	function KSPCallLoops.pdrain( actionFn, errorFn, sourceFn )
		Call sourceFn repeatedly getting values until it returns nil. Invoke
		actionFn for each value. If the action fails, then if errorFn is
		non-nil, call errorFn with the message and the object. If errorFn
		throws, then stop. Returns true if either actionFn or errorFn
		succeeds for each value. Returns false and the message if errorFn
		throws. Errors in the source function are reported immediately as
		false, msg.
	
]]

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

local KSPCallLoops = { }

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

local pcall = pcall

local unpack = unpack

local KSArrayUtils = require 'KSArrayUtils'
local KSArrayUtils_overwriteAt = KSArrayUtils.overwriteAt

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

-- Store t[ 1 ] = N (select( '#', ... ))
-- t[ 2 ] .. t[ N + 1 ] = ...

local function store( t, ... )

	t[ 1 ] = KSArrayUtils_overwriteAt( t, 2, ... )

	return ...

end

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

local kFetching = { }

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

-- Run the action function if x is not nil. Returns false if x is nil and true
-- otherwise.

local function optAction( actionFn, x, ... )
	
	if x == nil then
		return false
	end
	
	actionFn( x, ... )
	
	return true

end

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

local function pdoWithIterator_imp( current, actionFn, iterFn, s, k )

	while true do
		if not optAction( actionFn, store( current, iterFn( s, k ) ) ) then
			break
		end
		k = current[ 2 ] -- Feed the key back into the iterator
		current[ 1 ] = kFetching -- Mark us as back in the fetch phase
	end

end

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

function KSPCallLoops.pdoWithIterator( actionFn, errorFn, iterFn, s, k )

	-- We expect to call the imp only once so we optimize storage allocation
	-- and access by constructing and passing an array to capture the current
	-- value.
	
	local current = { [ 1 ] = kFetching }
	
	while true do

		local success, msg = pcall( pdoWithIterator_imp, current, actionFn, iterFn, s, k )

		if success then
			break
		end

		-- An error in the source function is reported immediately.

		if current[ 1 ] == kFetching then
			return false, msg
		end

		-- If we have an error function, then we let it handle action
		-- function errors. We exit if the error function fails.

		if errorFn then
			local esuccess, emsg = pcall( errorFn, msg, unpack( current, 2, current[ 1 ] + 1 ) )
			if not esuccess then
				return false, ( emsg or msg )
			end
		end
		
		k = current[ 2 ]
			-- Update the iteration key from the most recent iteration result.

	end
	
	return true

end

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

local function pdrain_imp( current, actionFn, sourceFn )

	while true do
		local x = sourceFn()
		if x == nil then
			return
		end
		current[ 1 ] = x -- Save the value in the event of an error
		actionFn( x )
		current[ 1 ] = kFetching -- Mark us as back in the fetch phase
	end

end

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

function KSPCallLoops.pdrain( actionFn, errorFn, sourceFn )

	-- We expect to call the imp only once so we optimize storage allocation
	-- and access by constructing and passing an array to capture the current
	-- value.
	
	local current = { [ 1 ] = kFetching }
	
	while true do

		local success, msg = pcall( pdrain_imp, current, actionFn, sourceFn )

		if success then
			break
		end

		-- An error in the source function is reported immediately.

		if current[ 1 ] == kFetching then
			return false, msg
		end

		-- If we have an error function, then we let it handle action
		-- function errors. We exit if the error function fails.

		if errorFn then
			local esuccess, emsg = pcall( errorFn, msg, current[ 1 ] )
			if not esuccess then
				return false, ( emsg or msg )
			end
		end

	end
	
	return true

end

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

return KSPCallLoops
