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

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

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

-- NOTE: This namespace is fundamental to the framework operation. Please avoid
-- introducing any other dependencies.

	-- wichitafoundation.core.lua.threading
local WFCurrentThread = require 'WFCurrentThread'

	-- wichitafoundation.core.reactive
local KSPromises_lazy = nil

local FINE_GRAINED_IDLE = true

--============================================================================--
--[[!
	\class WFIdle
	\brief This namespace allows you to request time when the current
	thread becomes idle.
	
	This namespace wraps the underlying native APIs.
--]]

-- @mark WFIdle namespace

local WFIdle = {}


--============================================================================--

local runUntilDonePool = nil

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

local function runUntilDone( p )

	if KSPromises_lazy == nil then
		KSPromises_lazy = require 'KSPromises'
	end
	
	if runUntilDonePool == nil then
		runUntilDonePool = KSPromises_lazy.makeRunUntilDonePool()
	end
	
	-- We don't want to fire off limbo checks for promises here because we're
	-- basically just creating this action to get this particular promise out
	-- of limbo and we know that this action isn't going to be left in limbo.
	
	KSPromises_lazy.suspendLimbo( p.runUntilDone, p, runUntilDonePool )

end

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

local function KSPromises_promiseP()

	if KSPromises_lazy == nil then
		KSPromises_lazy = require 'KSPromises'
	end
	
	return KSPromises_lazy.promiseP()

end

--============================================================================--

	-- wichitafoundation.core.lua.threading
local KSSystemNotifications_lazy
	-- Lazy load to avoid cycles
	
--------------------------------------------------------------------------------

local registered = { }

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

local function registerHandler( message, handler )

	if registered[ message ] then
		return
	end
	
	if KSSystemNotifications_lazy == nil then
		KSSystemNotifications_lazy = require 'KSSystemNotifications'
	end
	
	KSSystemNotifications_lazy.onNotificationDo( message, handler )
	
	registered[ message ] = true
	
end

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

local function runHandler( message, handler, asap )

	registerHandler( message, handler )
	
	local selector = asap and 'sendNotificationASAP' or 'sendNotification'

	WFCurrentThread[ selector ]( message )
		
end


--============================================================================--

-- FIXME: Move this into a utility module for unit testing

local function reverseArray( arr )

	local n = #arr
	
	for i = 1, n / 2 do
		local opposite = n + 1 - i
		arr[ i ], arr[ opposite ] = arr[ opposite ], arr[ i ]
	end

end

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

local function doRunTasks( tasks )

	for i = #tasks, 1, -1 do
		local task = tasks[ i ]
		tasks[ i ] = nil
		task()
	end

end

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

local function runTasks( tasks )

	if not tasks then
		return
	end
	
	-- Reverse the task list so that we can process it from the end
	
	reverseArray( tasks )
	
	-- Use doRunTasks until it succeeds
	
	while not pcall( doRunTasks, tasks ) do
		-- debug.print( "WFIdle: task failed (catching)" )
	end

end


--============================================================================--

local function makeTaskSetLogic( name, enqueueHandler )

	local p = nil -- the current promise
	local tasks = nil
	
	-- Handle being triggered to execute
	
	local function handler()
	
		p = nil -- We always want a new promise
		
		-- Clear the task list running anything currently on the list
		
		local existingTasks = tasks
		tasks = nil
		
		runTasks( existingTasks )
	
	end
	
	-- Enqueue a subsequent execution of task()
	
	local function doTask( task )
	
		if not task then
			return
		end
		
		if not tasks then
			tasks = { task }
			enqueueHandler( handler )
		else
			tasks[ #tasks + 1 ] = task
		end
	
	end
	
	-- Provide a promise that will subsequently resolve to 'data', true
	
	local function promiseP()
		
		if p then
			return p
		end
		
		local r
		
		p, r = KSPromises_promiseP()
		
		runUntilDone( p )
			-- Take it out of limbo since we don't support cancelation
		
		doTask( function()
			r( 'data', true )
		end )
		
		return p
		
	end
	
	return doTask, promiseP

end


--============================================================================--

local WFIdle_onIdleDo, WFIdle_onIdleP

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

if FINE_GRAINED_IDLE then

	--------------------------------------------------------------------------------
	
	local WFQueueing = require 'WFQueueing'

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

	local enqueueIdleTask, dequeueIdleTask = WFQueueing.makeQueue()
	local idleTasksCount = 0

	--------------------------------------------------------------------------------
	
	local function handleIdle()
	
		if idleTasksCount == 0 then
			return
		end
	
		local idleTask = dequeueIdleTask()
		
		if not idleTask then
			idleTasksCount = 0
			return
		end
		
		idleTasksCount = idleTasksCount - 1
		
		-- Figure out whether we need to request more idle time
		
		if idleTasksCount == 0 then
			-- No need to ask for more time
		else
			WFCurrentThread.requestIdle()
		end
		
		-- Run the task
		
		pcall( idleTask )

	end
	
	--------------------------------------------------------------------------------
	
	WFCurrentThread.registerIdleHandler( handleIdle )

	--------------------------------------------------------------------------------
	
	--[[ local ]] WFIdle_onIdleDo = function ( func )
	
		if not func then
			return
		end
	
		if idleTasksCount == 0 then
			WFCurrentThread.requestIdle()
		end
		
		idleTasksCount = idleTasksCount + 1
		
		enqueueIdleTask( func )
	
	end
	
	--------------------------------------------------------------------------------
	
	--[[ local ]] WFIdle_onIdleP = function()
	
		local idleP, idleResolver = KSPromises_promiseP()
		
		WFIdle_onIdleDo( function() idleResolver( 'data', true ) end )
		
		return idleP
	
	end

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

else

	--------------------------------------------------------------------------------
	
	local registeredIdleHandler = false

	--------------------------------------------------------------------------------
	
	local function enqueueIdleHandler( handler )
				
		if not registeredIdleHandler then
		
			registeredIdleHandler = handler
		
			WFCurrentThread.registerIdleHandler( handler )
		
		else
			
			assert( registeredIdleHandler == handler,
				"We should only register a single idle handler" )

		end
		
		WFCurrentThread.requestIdle()
	
	end
	
	--------------------------------------------------------------------------------
	
	WFIdle_onIdleDo, WFIdle_onIdleP = makeTaskSetLogic( 'idle', enqueueIdleHandler )
	
	--------------------------------------------------------------------------------
	
end

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

--[[!
	\brief Call this function once when the current thread becomes idle
	(that is, has no pending requests).
	
	\param func (function) function to be called when current thread becomes idle
]]--

function WFIdle.onIdleDo( --[[function]] func )
end

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

--[[!
	\brief Creates a promise that resolves when the current thread becomes idle
	(that is, when it has no pending requests).
	
	\return <code>P< true ></code>, A Promise object that resolves to true when the
		current thread is idle.
]]--

function WFIdle.onIdleP()
end

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

WFIdle.onIdleDo = WFIdle_onIdleDo
WFIdle.onIdleP = WFIdle_onIdleP


--============================================================================--

--[[!
	\brief Call this function after all of the currently queued requests have
	processed (but before any other requests process).
	
	\param func (function) function to be called when it reaches the front
	of the request queue.
]]--

function WFIdle.doLater( --[[function]] func )
end

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

--[[!
	\brief Creates a promise that resolves when the messages currently pending
	in the queue have processed.
	
	\return <code>P< true ></code>, A Promise object that resolves to true when
		we reach the appropriate point in the queue.
]]--

function WFIdle.laterP()
end

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

WFIdle.doLater, WFIdle.laterP = makeTaskSetLogic( 'later', function( handler )
	runHandler( 'WFIdle_doLater', handler )
end )


--============================================================================--

--[[!
	\brief Call this function before any of the currently queued requests get
	processed.
	
	\param func (function) function to be called on the next message cycle.
]]--

function WFIdle.doASAP( --[[function]] func )
end

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

--[[!
	\brief Creates a promise that resolves before any other messages are
	processed.
	
	\return <code>P< true ></code>, A Promise object that resolves to true when
		we reach the appropriate point in the queue.
]]--

function WFIdle.asapP()
end

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

WFIdle.doASAP, WFIdle.asapP = makeTaskSetLogic( 'asap', function( handler )
	runHandler( 'WFIdle_doASAP', handler, true )
end )


--============================================================================--

return WFIdle
