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

KSLifoLater.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.

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

-- @mark KSLifoLater namespace

--[[!
	\class KSLifoLater:

	\brief The namespace provides a mechanism for deferring work in batches
	that will be processed in LIFO order.

]]--

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

local Rx = require 'Rx'

local KSReactive = require 'KSReactive'

local KSReactive_variableB = assert( KSReactive.variableB )

local WFIdle = require 'WFIdle'

local WFCloseSupport = require 'WFCloseSupport'

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

local KSLifoLater = { }

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

local standardEpochStacks = { }

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

local KSEpochStack = Rx.Class 'KSEpochStack' {

	-- We store a stack of behaviors that will fire
	-- one by one on successive later passes.

	_epochStack = false,
	_epochStackTop = false,

	-- The public face of the epoch at the top of the stack

	_currentEpochB = false,
	
	-- Are we queued to run?
	
	_isQueued = false,
	
	-- The function to make new promises to drive the stack
	
	_makePromiseP = false,
	_whenResolved = false,
	
	-- A behavior indicating whether or not the epoch stack is
	-- empty.
	
	_epochStackActiveB = false,
	
	__init = function( self, makePromiseP )
	
		self._epochStack = { }
		self._epochStackTop = 0
		self._currentEpochB = false
		self._isQueued = false
		self._makePromiseP = makePromiseP
		
		self._whenResolved = function() self:_processEpochAndRequeue() end
	
		self:createReactiveVariable( '_epochStackActiveB', false )
		
	end,
	
	_processEpochAndRequeue = function( self )
	
		self._isQueued = false
		self._currentEpochB = false
	
		local epochStackTop = self._epochStackTop

		if epochStackTop < 1 then
			self._epochStackActiveB:set( false )
			return
		end
		
		local epochStack = self._epochStack
		
		local epochB = epochStack[ epochStackTop ]
		epochStack[ epochStackTop ] = nil
		epochStackTop = epochStackTop - 1
		
		self._epochStackTop = epochStackTop
		
		if 0 < epochStackTop then
			self:_queueProcessEpoch()
		else
			self._epochStackActiveB:set( false )
		end
		
		epochB:set( true )
		epochB:close()
	
	end,
	
	_queueProcessEpoch = function( self )
	
		self._epochStackActiveB:set( true )

		if not self._isQueued and self._makePromiseP then
			self._makePromiseP():whenResolvedDo( self._whenResolved ):runUntilDone()
			self._isQueued = true
		end
	
	end,
	
	_finishEpoch = function( self )
	
		self._currentEpochB = false
	
	end,
	
	getEpochB = function( self )
	
		local currentEpochB = self._currentEpochB
	
		if not currentEpochB then
		
			local epochVarB = KSReactive_variableB( false )
			self:addCloseTarget( epochVarB )
			
			local epochStack = self._epochStack
			local epochStackTop = self._epochStackTop
			
			epochStackTop = epochStackTop + 1
			epochStack[ epochStackTop ] = epochVarB
			
			self._epochStackTop = epochStackTop
			
			self:_queueProcessEpoch()
			
			currentEpochB = epochVarB:noCloseRelayB()
			
			self._currentEpochB = currentEpochB
			
			-- This epoch shouldn't last past ASAP time if we automatically
			-- queue epochs. If we don't automatically queue epochs, then
			-- we need to clear this manually.
			
			if self._makePromiseP then
				WFIdle.asapP():whenResolvedDo( function()
					self._currentEpochB = false
				end ):runUntilDone()
			end
			
		end
		
		return currentEpochB
	
	end,
	
	activeB = function( self )
	
		return self._epochStackActiveB:relayB()

	end,
	
	signalEpochBoundary = function( self, process )
		if process then
			self:_processEpochAndRequeue()
		else
			self:_finishEpoch()
		end
	end,
	
	WFCloseSupport.addCloseSupportMethods,
	
	KSReactive.addReactiveSupportMethods

}

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

standardEpochStacks[ 'idle' ] = KSEpochStack( assert( WFIdle.onIdleP ) )
standardEpochStacks[ 'later' ] = KSEpochStack( assert( WFIdle.laterP ) )

--------------------------------------------------------------------------------
 
--[[!
     Retrieves the currently open epoch behavior, so that you can attach an action
 	to run when the value becomes true. For example:
	<pre>
	local laterB = KSLifoLater_epochB( 'idle' )
	
	local resultB = laterB:onDataB( function( isLater )
	   if isLater then
	      return doSomething()
	   end
	end )
 	</pre>


	\param selector	(string) When to execute this epoch's actions, one of 'idle' or 'later'.
		<ul><li>'idle' events are processed when WFIdle.onIdleP resolves.</li>
		<li>'later' events are processed when 'WFIdle.laterP resolves.</li></ul>
	\return The behavior object, which at some point will return the value \c true.
]]--

function KSLifoLater.epochB( --[[string]] selector )

	return standardEpochStacks[ selector or 'later' ]:getEpochB()

end

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

--[[!
    Finishes the current epoch and starts a new epoch when we next invoke \c epochB.
    The new epoch is processed before the old epoch (in LIFO order).
    You can use this to ensure that one set of actions execute before some other
    set of actions. This finishes all of the standard epochs.

]]--

function KSLifoLater.finishStandardEpochs()

	for _, epochStack in pairs( standardEpochStacks ) do
		epochStack:_finishEpoch()
	end

end

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

function KSLifoLater.makeEpochStack()
	return KSEpochStack( false ) -- This will require manual triggering and epoch boundaries
end

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

return KSLifoLater
