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

WFTimer.lua

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

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

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

	-- Rx
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.rxlua
local WFRxPromiseBridge = require 'WFRxPromiseBridge'
local WFRxRunUntilDone -- delay load to avoid recursive require
local WFRxScheduler = require 'WFRxScheduler'

	-- WF.core.utils
local WFArguments = require 'WFArguments'
local WFCloseSupport = require 'WFCloseSupport'


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

local check = assert( WFArguments.check )

local asKSPromise = WFRxPromiseBridge


--============================================================================--
--[[!
	\class WFTimer

	\brief This namespace allows you to cause events to happen after
	a specified delay.
	
	These are not high-precision timers. "Nearby" timer events (currently defined
	as within the same 1/16 second interval) are coalesced together to save
	on system resources.
	
	This namespace wraps the underlying native APIs.
--]]

-- @mark WFTimer namespace

local WFTimer = {}

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

local startedNewTimer
local timersHaveShutDown

local shuttingDownSource = Rx.Subject()
local triggerShuttingDown = function()
	shuttingDownSource:onNext(true)
end

--------------------------------------------------------------------------------
--[[!
	\brief Returns a promise that resolves after the specified time interval
	has elapsed.
	
	This is not a high-precision timer. "Nearby" timer events (currently defined
	as within the same 1/16 second interval) are coalesced together to save
	on system resources.
	
	NOTE: We make a serious effort to fully drain any pending delay timers
	upon message processor shutdown; this means that when the message processor
	is closing afterDelayP will resolve considerably sooner than delay.  Additionally
	if enough recursive calls to afterDelayP are called during shutdown, eventually
	they will stop resolving.
	
	\param delay (number) how many seconds to wait (may be fractional)
	
	\return <code>P< true ></code> this promise will resolve to true after specified
		time interval has elapsed
]]--

function WFTimer.afterDelayP( delay )

	check( 'number' )

	if delay < 0 then
		error( "WFTimer.afterDelayP: delay must be a non-negative number", 2 )
	end
	
	if timersHaveShutDown then
		error( "WFTimer.afterDelayP: can not start timers while shutting down thread", 2 )
	end
	
	startedNewTimer = true

	local rawTimer = Observable.timer( delay, WFRxScheduler.sharedScheduler() )
	
	return rawTimer:amb( shuttingDownSource )
		:map( true )
		:call( asKSPromise )
	
end

--------------------------------------------------------------------------------
--[[!
	\brief Call this function once after the specified time interval
	has elapsed.
	
	This is not a high-precision timer. "Nearby" timer events (currently defined
	as within the same 1/16 second interval) are coalesced together to save
	on system resources.
	
	NOTE: We make a serious effort to fully drain any pending delay timers
	upon message processor shutdown; this means that when the message processor
	is closing WFTimer.afterDelayDo will be called considerably sooner than delay.
	Additionally, if enough recursive calls to afterDelayP are called during shutdown,
	eventually they will stop getting called.
	
	\param delay (number) how many seconds to wait (may be fractional)
	\param func (function) function to be called when current thread becomes idle
]]--

function WFTimer.afterDelayDo( delay, func )

	check( 'number', 'function' )

	if delay <= 0 then
		error( "WFTimer.afterDelayDo: delay must be a positive number", 2 )
	end
	
	local rawTimer = Observable.timer( delay, WFRxScheduler.sharedScheduler() )

	WFRxRunUntilDone = WFRxRunUntilDone or require 'WFRxRunUntilDone'
	
	rawTimer:amb( shuttingDownSource )
		:map( 'data', true ) -- bw compatibility from when afterDelayDo was promise based
		:map( func )
		:call( WFRxRunUntilDone )

end

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

local function shutdown()

	local shutdownLimit = 100

	while startedNewTimer and shutdownLimit > 0 do
		
		shutdownLimit = shutdownLimit - 1
		
		startedNewTimer = false

		local success, message = pcall( function()
			triggerShuttingDown()
		end )
		
		if not success then
			debug.print( "WFTimer: Encountered error while shutting down timers: ", message )
		end
	
		timersHaveShutDown = shutdownLimit < 2
			
	end

end

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

local closer = {
	close = shutdown
}

WFCloseSupport.mustCloseAtThreadTermination( closer )
	-- Run our shutdown loop when thread terminates

WFTimer._closer = closer
	-- Prevent closer from getting GC'd

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

return WFTimer
