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

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

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

-- Note: Implementation is below this documentation section.

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

-- @mark KSPromises namespace

--[[!
	\class KSPromises

	\brief This module provides support for the promises system for handling deferred
	evaluation.

	Provides map-based chaining, and the ability to combine promises
	in a variety of other ways.

	\sa KSPromise
--]]

local KSPromises = {}

--------------------------------------------------------------------------------
--[[!
	\brief Constructs a promise object and a resolver function whose argument is
		the resolution value for that promise.
	
	The resolution is either another promise, or else one or more status strings.
	If a promise resolves with another promise, it has a dependency on that other
	promise. The Wichita Framework does not detect circular dependencies.
	
	\param obeysStopResolution If true, then when the promise loses all actions,
	the resolver will be closed.

	\return (KSPromise) The promise object and its associated resolver object.

	\sa \c KSPromises.promiseForResolverP()
--]]

function KSPromises.promiseP( obeysStopResolution )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Constructs a promise that is already resolved.

	\param status The resolution status string.
	\param ... Optional additional resolution status strings.

	\return (KSPromise) The promise object.
--]]

function KSPromises.resolvedPromiseP( --[[string]] status, ... )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Constructs a promise that is already canceled.

	\return (KSPromise) The promise object.
--]]

function KSPromises.canceledPromiseP()
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Reports whether an object is a promise.

	\param x The object.
 
	\return (boolean) \c true if the object is a promise object, \c false otherwise.
--]]

function KSPromises.isPromise( --[[object]] x )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Applies a function that generates a promise to each element of an array.

	The application of the function can occur at an arbitrary time in the future
	and the order of application is not guaranteed. The returned promise reports on
	the success or failure of the entire operation.	If any promise resolved
	to an error, the returned promise resolves to the same error, but does not report
	which element in the array resulted in that error.
	
	\param fn (function) The function to apply. If this returns something other
		than a promise for any element, it is ignored.
	\param array (array) The array. No element can be nil or if there are nil elements
		then the array should also contain a field "n" that indicates the number of elements.
		We can also pass a promise for an array.
	\param optLimit (number) The maximum number of promises to execute at one time.
		Default is 1, meaning that promises are executed serially.

	\return (KSPromise) A promise object resolves to \c true if every generated promise
		resolved to data, or to an error if any of the promises resolved to an error
--]]

function KSPromises.allP( --[[function]] fn,
						  --[[array]] array,
						  --[[number]] optLimit )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Applies a function that generates a promise to each element of an array.
	
	The application of the function can occur at an arbitrary time in the future
	and the order of application is not guaranteed. The returned promise reports on
	the success or failure of the entire operation.	If any promise resolved
	to an error, the returned promise resolves to the same error, but does not report
	which element in the array resulted in that error. If all of the promises succeed,
	the promise resolves to an array of results.
	
	\param fn (function) The function to apply. If this returns a value rather than
		a promise, we simply store the value into the results array.
	\param array (array) The array. No element can be nil or if there are nil elements
		then the array should also contain a field "n" that indicates the number of elements.
		We can also pass a promise for an array.
	\param optLimit (number) The maximum number of promises to execute at one time.
		Default is 1, meaning that promises are executed serially.

	\return (KSPromise) A promise object resolves to \c an array of result if every
		generated promise resolved to data, or to an error if any of the promises
		resolved to an error
--]]

function KSPromises.mapArrayP( --[[function]] fn,
						  	   --[[array]] array,
						  	   --[[number]] optLimit )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Repeatedly generates sequentially evaluated promises until one of
	those promises either errors out or returns with a non-nil/non-false value.
	
	\param fnP (function) A function that generates a promise. If this returns
	something other than a promise, that will be reported as an error for the
	overall promise.

	\return (KSPromise) A promise object resolves to the value of the final
	promise evaluated.
--]]

function KSPromises.repeatUntilP( --[[function]] fnP )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Repeatedly generates sequentially evaluated promises until one of
	those promises either errors out or returns with a non-nil/non-false value.
	
	\param fn (function) A function.

	\return (KSPromise) A function that can work with promises. Essentially:
	<pre>
		function( ... ) return callP( fn, ... ) end
	<\pre>
--]]

function KSPromises.liftP( --[[function]] fn )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Convert a function into a function that can work with promises and
	return a promise.

	\param fnP (function) A function that generates a promise. If this returns
	something other than a promise, that will be reported as an error for the
	overall promise.

	\return (KSPromise) A promise object resolves to the value of the final
	promise evaluated.
--]]

function KSPromises.addPromiseResolverMethods( --[[table]] methodTable )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Constructs a new promise of a type determined by the resolver.

	\param resolver (KSPromiseResolver) A resolver created with
		<code>KSPromises.addPromiseResolverMethods()</code>
	\param options One retention option and one close option.
	<ul><li>"retained" | "unretained" determines whether the promise keeps
         	the resolver alive.</li>
	    <li>"closeTarget" | "noClose" determines whether loss of references
			to the promise should close the resolver or just disconnect
			it from this promise.</li>
	</ul>

	\return (KSPromise) The new promise object.

	\sa KSPromises.addPromiseResolverMethods()
--]]

function KSPromises.promiseForResolverP( --[[KSPromiseResolver]] resolver,
										 --[[table]] options )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Checks whether the run until done pool is empty. Intended only for
		testing.

	\param debugEntries Optional, a function that reports unexpected entries in
		the run-until-done pool.

	\return (boolean) \c true on success, \c false if an unexpected entry is encountered.
--]]

function KSPromises.testing_runUntilDonePoolIsEmpty( --[[function]] debugEntries )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Resets the run until done pool to the empty state. Existing
		runUntilDone actions are kept running because we keep the old pool and
		simply create a new one. Because we keep the old pool indefinitely, this
		routine is not intended to be used repeatedly in production code.
--]]

function KSPromises.testing_resetRunUntilDonePool()
	-- DOXYGEN STUB.
end


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

-- @mark KSPromise class

--[[!
	\class KSPromise
	\brief Represents an individual promise. See KSPromises for a general discussion of
	promises and how they are used.
--]]

local KSPromise = {}

--------------------------------------------------------------------------------
--[[!
	\brief Reports whether this promise has been canceled. Useful for filtering out
	canceled promises from a promise cache.

	\return (boolean) \c true if this promise has been canceled, \c false otherwise.
--]]

function KSPromise:isCanceled()
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Constructs a new promise by mapping the value of this promise through
		a given function.
     
	The mapping function takes as a parameter the resolution of the source promise.
	It can return nothing, in which case the resolved value for the source
	promise is passed through. Otherwise, it should return a promise or a
	status value of one or more strings. If it returns a promise, the mapped
	promise gets connected to that promise.

 	\param fn The mapping function.

	\return (KSPromise) The new promise object.

	\sa KSPromises.mapP( t )
--]]

function KSPromise:mapP( --[[function]] fn )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Applies a function that generates a new promise for each element of
		an array that results from a promise. No element of the array can be
		\c nil or the array also needs to contain a table element "n" giving the
		number of entries to process.

	The application of the function can occur at an arbitrary time in the future
	and the order of application is not guaranteed. The returned promise reports on
	the success or failure of the entire operation.	If any promise resolved
	to an error, the returned promise resolves to the same error, but does not report
	which element in the array resulted in that error.
	
	\param fn (function) The function to apply. If this returns something other
		than a promise for any element, it is ignored.
 	\param optLimit (number) The maximum number of promises to execute at one time.
		Default is 1, meaning that promises are executed serially.

	\return (KSPromise) A promise object resolves to \c true if every generated promise
		resolved to data, or to an error if any of the promises resolved to an error
--]]

function KSPromise:mapAllP( --[[function]] fn,
							--[[number]] optLimit )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Constructs a new promise by mapping the value of this promise through
		a given table.
         
	The table maps the source promise's resolution status values to functions. If
	there is no mapping for a status value, it is passed through. You can use this
	to remap errors, with something like:
	<pre>
	    promise:mapP { error = function( status, msg ) ... end }
	</pre>

	\param t The mapping table. t can also be a promise in which case resolution of
		self then leads to resolution of t irregardless of the resolved value of self.
		We can also pass promises instead of functions for the individual cases.

	\return (KSPromise) The new promise object.

	\sa KSPromises.mapP( f )
--]]

function KSPromise:mapP( --[[table]] t )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief
		A shorthand version of mapping for data. If a resolution status
		of the source promise is 'data', it is passed with any other resolution
		values to the given function.
	
		The mapping function can return nothing, in which case the resolved value
		for the source promise is passed through. Otherwise, it should return a
		promise or a status value of one or more strings. If it returns a promise,
		the mapped promise gets connected to that promise.
		
		We can also pass a promise for fn. If so, then this will be the value if
		self resolves successfully irregardless of the details of the resolved
		value of self.
		
	\param fn (function)  The mapping function.

	\return (KSPromise) The new promise.
--]]

function KSPromise:mapDataP( --[[function]] fn )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Reports when the promise resolves and how it resolves as name
		followed by values.

	\param name (string) The name to use when reporting
		
	\return A new promise that includes the effects of the debug logic.
--]]

function KSPromise:debugP( --[[string]] name )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief If the promise has not yet resolved, constructs and returns an
		action object that represents the deferred need to run the function.

	If this promise has resolved, invokes a given function with the value of the
	function in a protected context. This constructs and returns an
	action object that has already executed.

	\param fn The function, or a table mapping status values to functions.
	
	\return (KSPromiseAction) An action object.
--]]

function KSPromise:whenResolvedDo( --[[function]] fn )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Handles resource acquisition and release via promises.

	\param releaseFnP (function or 'close' or nothing) An optional function to
		apply to the value of self (if it was resolved successfully) to
		release the resource. If one passes 'close' then this will invoke the
		close method on the value. This should return a promise or nothing. If
		it returns a promise, we will wait for that promise to resolve. This
		does not receive a status value.
		
	\param processFnP (function) The action to take with the resource. This
		should return a promise or nothing and should signal errors by throwing
		or returning an error promise. If it returns a promise, we will wait
		for that promise to resolve. If it returns nothing, we will convert
		this into 'data', true.

	\return (KSPromise) A promise object. The promise resolves to the
		same value as processFnP if resourceP resolved successfully and
		to the value of self if self resolved unsuccessfully.
--]]

function KSPromise:usingDoP( --[[function]] releaseFnP,
							 --[[function]] processFnP )
	-- DOXYGEN STUB
end

--------------------------------------------------------------------------------
--[[!
	\brief Waits for a step to complete after self resolves, but returns the value
		of self.
	
	\param fnP (function | KSPromise) Either a function to evaluate with the
		resolved value of self. The function should return a promise or nothing.
		We can also just pass a promise on which to wait.

	\return (KSPromise) A promise object that resolves to the same value as self
		but does not resolve until after fnP has resolved.
--]]

function KSPromise:waitForP( --[[ function | KSPromise ]] fnP )
	-- DOXYGEN STUB
end

--------------------------------------------------------------------------------
--[[!
	\brief Allows observation of promise results. This will run a function when
	a promise resolves and then resolve as the same value as the underlying
	promise. It is much like waitForP but it can't return promises to cause further
	waiting.

	\param observeFnP (function) Receives status and values after promise
	resolves. We can also pass in a case table.

	\return (KSPromise) A promise for the same value as the underlying promise
	but after the observeP function has had a chance to execute.
--]]

function KSPromise:observeP( --[[function]] observeFn )
	-- DOXYGEN STUB
end

--------------------------------------------------------------------------------
--[[!
	\brief Map a promise to a behavior supplying the value to use until the
	promise is resolved.

	\param status, ... The default value for the behavior using the KSPromises
	status, ... representation.
	
	\return (Behavior) The behavior.
--]]

function KSPromise:untilResolvedB( status, ... )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Causes the promise to execute attached actions until it is completely
		resolved.
	
	This is the equivalent of
	<code>promise:whenResolvedDo( function() end ):runUntilDone()</code>
	
	\warning This does NOT cause the thread to wait until the promise is resolved.
		It merely ensures that the promise runs to completion.
	
	\param fn The function to apply.
	
	\return (KSPromiseAction) An action object that can be used to
		adjust the priority or cancel the operation.
--]]

function KSPromise:runUntilDone( --[[function]] fn )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Causes the promise to execute attached actions until it is completely
		resolved, and returns this promise object.

	\warning This does NOT cause the thread to wait until the promise is resolved.
		It merely ensures that the promise runs to completion.
	
	\param fn The function to apply.

	\return (KSPromise) This promise object.
--]]

function KSPromise:runUntilDoneP( --[[function]] fn )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Causes the promise to execute attached to a particular object's lifetime.
	This actually supports the full attachTo calling syntax. See attachTo.
--]]

function KSPromise:runAttachedTo( --[[object]] owner )
	-- DOXYGEN STUB.
end


--============================================================================--
	
-- @mark KSPromiseAction class

--[[!
	\class KSPromiseAction

	\brief Represents an action associated with a promise. An action is an
		operation that must be completed in order for the promise to be resolved.

	The constructor takes a source promise object, and the function
	that defines the operation. A new action must be attached to a promise or
	other object before continuing work in the promise system.
--]]

local KSPromiseAction = {}

--------------------------------------------------------------------------------
--[[!
	\brief Cancels this action.
--]]

function KSPromiseAction:close()
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Adjusts the priority for this action. Changing the priority of an
		action can cause the message that enqueued it to be resent.

	\param priority The priority, a numeric value which can be
		positive or negative. 1 is the default. Passing a value of 'very_low'
		will set the priority to a value below the activation/cancel threshold
		but not so low that the action and the promises feeding it won't run.
--]]

function KSPromiseAction:setPriority( --[[number]] priority )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Adds this action as a close target of another object, in order to
		keep it (and its associated promise) alive. Optionally stores this action
		for independant access (in order to close it or re-attach it).

	\param m A WFCloseSupport object.
	\param t Another table. If not provided, the action is stored in the object.
	\param k A key for storing this action as an entry in the table.
		If another action was already stored at this key, this replaces
		it and the previous action is closed.
		If not provided, the action object itself is used as the key.
	
	\return self.  Useful for chaining.
--]]

function KSPromiseAction:attachTo( --[[object]] m,
								   --[[table]] t,
								   --[[string]] k )
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Attaches this action to the global run-until-done set.
	
	This is equivalent to:

	<pre>
		promise:whenResolvedDo( function() end ):runUntilDone()
	</pre>
	
	\return The action object, which you can use to adjust its priority
	or cancel it.
--]]

function KSPromiseAction:runUntilDone()
	-- DOXYGEN STUB.
end


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

-- @mark KSPromiseResolver namespace

--[[!
	\class KSPromiseResolver

	\brief Represents a resolver method associated with a KSPromises object.
	
The more general system for building new promise types is based on resolvers.
A promise resolver may respond to two messages:
<pre>
	startResolution( priority )
	stopResolution()
</pre>
	
The first can be sent multiple times if the priority keeps shifting.
The second is sent once. We may restart a resolver unless it
responds to the stop resolution message by resolving the promise.
Note that these messages are informational only and are not required
to be supported by the resolver.

Resolvers are also closed if the promise is garbage collected,
so they should respond to the \c close message, which could be sent
instead of the \c stopResolution message.

An implementation of \c stopResolution without restart support would look
like this:
<pre>
	function resolver:stopResolution()
		self:close()
	end
</pre>
This resolves the promise as canceled if it wasn't already so
resolved. This is the default behavior.

To resolve the promise (if any) associated with a resolver, invoke:

<pre>	resolver:resolvePromise( ... )</pre>
	
Passing in nothing results in cancelation. Passing in a promise results in
rebinding to that promise. Otherwise, pass a status string plus any other
results. Your class gains this method by using:

<pre>	KSPromises.addPromiseResolverMethods( methodTable )</pre>
	
You could also use it directly on the object in question. (Don't forget to
also add close support to the resolver.) This function also adds the method

<pre>	resolver:canResolvePromise()  </pre>
	
This returns true if the promise is still available and unresolved.

Given a resolver, construct a promise like this:

<pre>	KSPromises.promiseForResolverP( resolver, options ) </pre>
	
The options are drawn from retained | unretained and closeTarget | noClose.
One value from each pair must be specified. The retention options govern
whether the promise will keep the resolver alive. The close options govern
whether loss of references to the promise will close the resolver or simply
result in it becoming disconnected.
--]]

local KSPromiseResolver = {}

--------------------------------------------------------------------------------
--[[!
	\brief Implement this method on your resolver instance to be notified
		when resolution is starting.
--]]

function KSPromiseResolver:startResolution()
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\brief Implement this method on your resolver instance to be notified
		when resolution is no longer needed.
--]]

function KSPromiseResolver:stopResolution()
	-- DOXYGEN STUB.
end

--------------------------------------------------------------------------------
--[[!
	\cond PRIVATE
	Doxygen will not try to parse anything after this point.
--]]

local assert = assert
local next = next
local pairs = pairs
local rawget = rawget
local rawset = rawset
local select = select
local type = type
local tostring = tostring
local setmetatable = setmetatable

local debug_print = debug.print
local string_format = string.format

local WFArguments = require 'WFArguments'
local WFArguments_check = assert( WFArguments.check )

local Rx = require 'Rx'

local WFCloseSupport = require 'WFCloseSupport'

local WFIdle = require 'WFIdle'
local WFIdle_doASAP = assert( WFIdle.doASAP )

local WFCloseSupport_enqueueTargetForClose = assert( WFCloseSupport.enqueueTargetForClose )
local WFCloseSupport_runCloseQueue = assert( WFCloseSupport.runCloseQueue )
local WFCloseSupport_makeCloser = assert( WFCloseSupport.makeCloser )
local WFCloseSupport_hasBeenOrWillBeClosed = assert( WFCloseSupport.hasBeenOrWillBeClosed )

local WFQueueing = require 'WFQueueing'

local KSPCallLoops = require 'KSPCallLoops'
local KSPCallLoops_pdrain = assert( KSPCallLoops.pdrain )

local KSOptions = require "KSOptions"
local KSOptions_exactlyOneOf = assert( KSOptions.exactlyOneOf )

local WFPackedValues = require 'WFPackedValues'
local WFCapabilities = require 'WFCapabilities'

local WFPackedValues_status = assert( WFPackedValues.status )
local WFPackedValues_pack = assert( WFPackedValues.pack )
local WFPackedValues_packError = assert( WFPackedValues.packError )
local WFPackedValues_unpack = assert( WFPackedValues.unpack )
local WFPackedValues_argTuple = assert( WFPackedValues.argTuple )

local WFVarArgs = require 'WFVarArgs'

local WFVarArgs_map = assert( WFVarArgs.map )

local WFDebugTraceback = require 'WFDebugTraceback'
local WFDebugTraceback_traceback = assert( WFDebugTraceback.traceback )
local WFDebugTraceback_describeFunction = assert( WFDebugTraceback.describeFunction )

local kIgnoreForTraceback = { "KSPromises.lua" }

local traceback = function()
	return WFDebugTraceback_traceback( kIgnoreForTraceback )
end

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

local KSPromises_isPromise -- forward
local KSPromise_removeFromLimbo -- forward

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

local resolverOptionsParser = KSOptions.parser 'retained | closeTarget | unretained | noClose'


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

local DEBUG = _G.DEBUG

local DEBUG_PROMISE_RESOLVE_FAILURES = false
	-- Set to true if you're getting "Error in promise transform: Status must
	-- be a string" errors. Use sparingly: It's pretty resource-hungry.

local DEBUG_LIMBO = DEBUG
	-- Set to true if you're getting the "Action left in limbo. Closing."
	-- assert and you can't figure out which one is thusly afflicted.
	
local DEBUG_UNCLAIMED_PROMISES_VIA_TASK = DEBUG
	-- Should we fire off a later task to check for promises left unclaimed?
	-- Doing so brings in more code and actually potentially keeps the promises
	-- around longer but catches places where we dropped the promises on the
	-- floor. We will check unclaimed promises at action creation time no
	-- matter what because we expect actions to demarcate the boundaries for
	-- promise graph construction.

local DEBUG_PROMISE_RESOLVER_GC = false
	-- Track down where promise resolvers that are getting GC'd early are being
	-- created.

local DEBUG_NODE_BURSTS = false
	-- Track down where bursts of promises are getting created.
	
local DEBUG_RUN_UNTIL_DONE_POOL = DEBUG
	-- Track down why we leak objects into the run until done pool.
	
local DEBUG_PROMISE_CREATION = DEBUG
	-- Controls whether we capture tracebacks in promiseForResolverP.
	
local RUN_ACTIONS_VIA_QUEUE = true
	-- Should we use a separate queue for running actions?

local STALL_INACTIVE_TRANSFORMS = true
	-- Should we stall evaluation of transforms on inactive promises?
	
local DEBUG_ACTION_CLOSURE = DEBUG
	-- Track down when we close actions before they get a chance to run

if WFCapabilities.getCapability( "KSPromises.debugTracebacks" ) == 'disabled' then
	traceback = function() return "" end
	DEBUG_PROMISE_RESOLVE_FAILURES = false
	DEBUG_LIMBO = false
	DEBUG_UNCLAIMED_PROMISES_VIA_TASK = false
	DEBUG_PROMISE_RESOLVER_GC = false
	DEBUG_NODE_BURSTS = false
	DEBUG_RUN_UNTIL_DONE_POOL = false
	DEBUG_PROMISE_CREATION = false
	DEBUG_ACTION_CLOSURE = false
end

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

local fullyWeakMT = { __mode = 'kv' }

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

local function makeFullyWeakTable()
	return setmetatable( { }, fullyWeakMT )
end

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

local gOwnedObjectsToOwners = makeFullyWeakTable()

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

local function KSOwnership_detachFromOwner( obj )

	if obj == nil then
		return
	end

	local owner = gOwnedObjectsToOwners[ obj ]
	
	if owner == nil then
		return
	end
	
	rawset( owner, rawget( obj, 'private:ownerKey' ), nil )
	
	rawset( obj, 'private:ownerKey', nil )
	
	gOwnedObjectsToOwners[ obj ] = nil

end

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

local function KSOwnership_store( owner, key, obj )

	rawset( owner, key, obj )
	
	if obj then
		gOwnedObjectsToOwners[ obj ] = owner
		rawset( obj, 'private:ownerKey', key )
	end

end

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

local function KSOwnership_attachToOwner( obj, owner, key, closeOld )

	WFArguments_check( '?table', 'table' )
	assert( key == key, "NaN is not a valid key" )
	
	-- Make sure we aren't attached somewhere already.
	
	assert( gOwnedObjectsToOwners[ obj ] == nil,
		"We should not be attaching an object that is already owned." )
	
	-- Decide whether to treat the new location as a set or a field
	
	if key == nil then
	
		-- Treat owner as set
		
		if obj == nil then
			return
		end
		
		KSOwnership_store( owner, obj, obj )
		
	else
	
		local old = rawget( owner, key )
		
		if old == obj then
			return
		end
		
		KSOwnership_store( owner, key, obj )
		
		if old and closeOld then
			old:close()
		end

	end

end


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

-- We number actions and promises for use in sequencing,

local gSequenceNumber = 0

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

local function nextSequenceNumber()
	gSequenceNumber = gSequenceNumber + 1
	return gSequenceNumber
end


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

local function nop() end
	-- Note: We don't use the version from KSFunctionUtils because that's
	-- a lot of code to pull in that we mostly don't need and it probably
	-- takes as much space as this function declaration.

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

local kDefaultActionPriority = 1
	-- The default priority for actions
	
local kMinActionPriority = -1000000
	-- A priority so low as to mean that we shouldn't start work
	
local kVeryLowActionPriority = kMinActionPriority + 1
	
local kMinActivationPriority = kMinActionPriority + 10

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

local function constrainPriority( priority )

	if priority == 'default' then
		priority = kDefaultActionPriority
	end
	
	return priority

end

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

local function assertValidPriority( priority, allowMin )

	assert( ( type( priority ) == 'number' ),
		"Priority must be a number" )
		
	if allowMin then
		assert( kMinActionPriority <= priority, "Priority must be at least minimum" )
	else
		assert( kMinActionPriority < priority, "Priority must be greater than minimum" )
	end

end


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

--[[

	We represent the resolved status for promises as one of:
		false: unresolved
		KSPromises_kCanceled: The promise was canceled
		packed value: A WFPackedValue

]]

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

local KSPromises_kCanceled = { }
local KSPromises_kCanceledString = 'canceled'
local KSPromises_kSame = { }

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

local function encodePromiseValue( self, status, ... )

	if type( status ) ~= 'string' then
		if DEBUG_PROMISE_RESOLVE_FAILURES then
			local tback = tostring( rawget( self, "_promiseTraceback" ) or "" )
			debug.print( "Bad promise resolution. Status must be string. Promise created at: " .. tback )
			return WFPackedValues_packError( 'error', "Bad promise resolution status" )
		end
	end
	
	if status == KSPromises_kCanceledString then
		if DEBUG and select( '#', ... ) ~= 1 then
			local tback = tostring( rawget( self, "_promiseTraceback" ) or "" )
			debug.print( "Unexpected extra values in the canceled status case. Ignoring. Promise created at: " .. tback )
		end
		return KSPromises_kCanceled
	end
	
	return WFPackedValues_pack( status, ... )
	
end

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

local function decodePromiseValue( v, returnCanceledStatus )

	if v == KSPromises_kCanceled then
		if returnCanceledStatus then
			return KSPromises_kCanceledString
		else
			return -- nothing
		end
	else
		return WFPackedValues_unpack( v )
	end

end


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

-- Convert a table of functions into a function if need be.

local function makeEncodedValueFunction( fn, allowPromises )

	WFArguments_check( 'callable | table | KSPromise' )
	
	-- We assume that tables with metatables are intended to be treated as callable
	-- objects. This isn't quite true, but it's close enough for all normal use
	-- cases.
	
	if KSPromises_isPromise( fn ) then
		assert( allowPromises, "Promises are not allowed here" )
		KSPromise_removeFromLimbo( fn )
			-- Assume that the owner of this function now owns the promise
		return function()
			return fn
		end
	elseif type( fn ) == 'table' and getmetatable( fn ) == nil then
		for k, v in pairs( fn ) do
			if KSPromises_isPromise( v ) then
				assert( allowPromises, "Promises are not allowed here" )
				KSPromise_removeFromLimbo( v )
			end
		end
		return function( v )
			if v == KSPromises_kCanceled then return end
			local status = WFPackedValues_status( v )
			local ffn = fn[ status ]
			if ffn then
				if KSPromises_isPromise( ffn ) then
					assert( allowPromises, "Promises are not allowed here" )
					return ffn
				else
					return ffn( decodePromiseValue( v ) )
				end
			else
				return -- return nothing to indicate that we want to keep the value
			end
		end
	else
		return function( v )
			return fn( decodePromiseValue( v ) )
		end
		
	end

end


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

local transformFunctionToSource = makeFullyWeakTable()

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

local function recordTransformFunctionToSource( fn, source )

	-- Scan backwards through the table so that we get the root source.

	local scan = source

	while scan do
		source = scan
		scan = transformFunctionToSource[ scan ]
	end

	transformFunctionToSource[ fn ] = source

end

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

local function findFunctionValueInTable( t )

	for k, v in pairs( t ) do
		if type( v ) == 'function' then
			return v
		end
	end
	
	return nil

end

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

local function transformFunctionName( fn )

	fn = transformFunctionToSource[ fn ] or fn
		-- map back to the source
		
	if type( fn ) == 'table' then
		fn = findFunctionValueInTable( fn )
	end
	
	if type( fn ) ~= 'function' then
		return "< unknown >"
	end
	
	return WFDebugTraceback_describeFunction( debug.getinfo( fn ) )

end

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

local KSPromise_removeTarget -- forward declaration


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

local function KSPromiseSource_isResolved( self )

	return not not self._value

end

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

local function KSPromiseSource_encodedValue( self )

	local value = self._value
	
	assert( value, "Promise has no value because it hasn't been resolved." )
	
	return value

end

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

local function KSPromiseSource_setResolvedWithEncodedValue( self, v )

	assert( not self._value, "We can only set the value once." )
	assert( v, "We must have a value." )
	
	self._value = v
	
end


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

local function KSPromise_setResolvedWithEncodedValue( self, v )

	self._transform = false
		-- Release the transform if we haven't done so already
	
	return KSPromiseSource_setResolvedWithEncodedValue( self, v )

end

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

local KSPromise_enqueueTargets
	-- forward declaration

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

local function KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, v )

	KSPromise_setResolvedWithEncodedValue( self, v )
	
	KSPromise_enqueueTargets( self )

end

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

local function priorityDequeueToDequeue( dq )

	return function()
		local priority, value = dq()
		return priority and value
	end

end

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

local needToRunResolveQueue = false
	-- Set if we need to run the resolve queue.

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

local enqueueResolvedTarget_pq, dequeueResolvedTarget_pq = WFQueueing.makePriorityQueue()
	-- We run a priority queue of resolved targets.
	
local dequeueResolvedTarget = priorityDequeueToDequeue( dequeueResolvedTarget_pq )
			
local enqueueAction_pq, dequeueAction_pq = WFQueueing.makePriorityQueue()
	-- We run a queue of actions to invoke ( { fn, n, ... } )
	
local dequeueAction = priorityDequeueToDequeue( dequeueAction_pq )

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

local KSPromiseTarget_invokeResolve
	-- forward: function( self ) do the processing for the resolved target

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

local function invokeAction( entry )

	local fn = entry[ 1 ]
	
	local v = entry[ 2 ]
	
	fn( v )

end

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

local function invokeHandleResolveError( msg, obj )
	obj:_KSPromiseTarget_handleResolveError( msg )
end

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

local function handleActionError( msg, entry )

	debug_print( string_format( "Error running promise action: %s", tostring( msg ) ) )

end

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

local runningQueue = false

local function runPromiseQueue()

	if runningQueue then
		return
	end

	runningQueue = true
	
	if RUN_ACTIONS_VIA_QUEUE then
	
		repeat
		
			KSPCallLoops_pdrain(
					KSPromiseTarget_invokeResolve,
					invokeHandleResolveError,
					dequeueResolvedTarget )
			
			needToRunResolveQueue = false -- at this point, we know the queue is empty
		
			KSPCallLoops_pdrain( invokeAction, handleActionError, dequeueAction )
			
		until not needToRunResolveQueue
		
	else
	
		KSPCallLoops_pdrain(
				KSPromiseTarget_invokeResolve,
				invokeHandleResolveError,
				dequeueResolvedTarget )
		
		needToRunResolveQueue = false -- at this point, we know the queue is empty
	
	end
	
	runningQueue = false

end


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

-- Support for adjusting the _source field of objects support target status for
-- KSPromise related operations (KSPromise and KSPomiseAction).

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

local function KSPromiseTarget_setSource( self, source )
	assert( not KSPromiseSource_isResolved( source ), "Source should not be resolved" )
	assert( not self._source, "Element should not already have a source" )
	self._source = source
	source:_KSPromiseSource_addTarget( self )
end

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

local function KSPromiseTarget_updateSource( self, oldSource, newSource )
	assert( not KSPromiseSource_isResolved( newSource ), "New source should not be resolved" )
	if self._source ~= oldSource then
		debug.print( "Unexpected old source for KSPromiseTarget_updateSource. Update ignored." )
		return
	end
	if oldSource == newSource then
		return
	end
	self._source = newSource
	newSource:_KSPromiseSource_addTarget( self )
	KSPromise_removeTarget( oldSource, self )
end

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

local function KSPromiseTarget_setSourceOrResolve( self, source )

	assert( not self._source, "Promise element already has source" )
	
	if KSPromiseSource_isResolved( source ) then
		local success, msg = pcall(
				self._KSPromiseTarget_resolveWithEncodedValue,
				self,
				KSPromiseSource_encodedValue( source ) )
		if not success then
			if msg == nil then
				msg = "< Unknown error (promise not resolved) >"
			end
			pcall( self._KSPromiseTarget_handleResolveError, self, msg )
		end
		runPromiseQueue()
	else
		KSPromiseTarget_setSource( self, source )
	end

end

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

local function KSPromiseTarget_enqueueResolvedTarget( self )

	needToRunResolveQueue = true

	enqueueResolvedTarget_pq( -self._sequence, self )

end

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

local function KSPromiseTarget_recalcSourcePriority( self )
	local source = self._source
	if source then
		return source:_KSPromiseSource_recalcPriority()
			-- tail call since we are running up the promise chain
	end
end

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

local function KSPromiseTarget_setPriority( self, priority, allowMin )

	priority = constrainPriority( priority )
	
	assertValidPriority( priority, allowMin )
	
	local oldPriority = self._priority
	
	if priority == oldPriority then
		return
	end

	self._priority = priority
	
	if STALL_INACTIVE_TRANSFORMS
		and self._sourceValue
		and oldPriority <= kMinActionPriority
		and kMinActionPriority < priority
	then
		local tback = rawget( self, '_promiseTraceback' )
		if tback then
			tback = tostring( tback )
			debug_print( "Enqueueing stalled transform promise created at: " .. tback )
		end
		KSPromiseTarget_enqueueResolvedTarget( self )
		runPromiseQueue()
	end

	return KSPromiseTarget_recalcSourcePriority( self )
		-- tail call since we are running up the promise chain

end

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

local function KSPromiseTarget_init( self )

	self._sequence = nextSequenceNumber()

end

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

local function KSPromiseTarget_enqueueResolvedWithSourceValue( self, sourceValue )

	assert( not self._sourceValue, "Already had source value" )
	
	self._sourceValue = sourceValue
	
	-- Once we have a source value we don't need a source. We shouldn't be
	-- needing to clear the back link on the source.
	
	self._source = false
	
	KSPromiseTarget_enqueueResolvedTarget( self )

end

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

KSPromiseTarget_invokeResolve = function( self )

	assert( not self._source, "We should not have a source" )

	local sourceValue = self._sourceValue
	
	if not sourceValue then
		return -- Apparently we've already used the value
	end
	
	-- Attempt to finish resolution using this source value. Note that this
	-- can end up leaving us instead back needing to resolve.
	
	self._sourceValue = false
	self:_KSPromiseTarget_resolveWithEncodedValue( sourceValue, true )

end

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

local function KSPromiseTarget_addMethods( methods )

	methods._sequence = false

end

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

local function doClose( self )
	self:close()
end

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

-- We maintain a limbo space for the last created action and promise so that
-- we can confirm that they get set up properly.

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

local gLimboAction = nil

local gLimboActionTraceback = nil

local gLimboPromises = { }

if not DEBUG_UNCLAIMED_PROMISES_VIA_TASK then
	setmetatable( gLimboPromises, fullyWeakMT )
end

local gLimboCount = 0

local gSuspendLimbo = 0
	-- When greater than zero, we don't do limbo checking

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

-- Make sure that gLimboAction is clear. If it isn't, log a message and close
-- the action as if it had been collected.

local function checkAndClearActionLimbo()

	if 0 < gSuspendLimbo then
		return
	end

	if gLimboAction then
		if DEBUG_LIMBO then
			debug.print( "Action limbo check failed at", traceback() )
		end
	end

	local limboAction = gLimboAction

	if limboAction then
	
		gLimboAction = nil
		
		gLimboCount = gLimboCount + 1

		if limboAction._action then
			debug_print( "Action left in limbo. Closing." .. transformFunctionName( limboAction._action ) )
		end

		if DEBUG_LIMBO and gLimboActionTraceback then
			debug_print( "Offending action was created at ", gLimboActionTraceback )
		end
		
		pcall( doClose, limboAction )

	end

end

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

-- Make sure that promise limbo is clear. If it isn't, log messages and clear
-- out promise limbo. Since there is no notion of closing promises, we can't do
-- much else here.

-- GRUNGE: Do we want to introduce the notion of sealing a promise such that it
-- can't have more successors? A sealed promise also couldn't be used as the result
-- of a map function. This could be interesting but might also complicate things.
-- Let's not do it just yet.

local function checkAndClearPromiseLimbo()

	if 0 < gSuspendLimbo then
		return
	end

	if next( gLimboPromises ) then
		if DEBUG_LIMBO then
			debug.print( "Promise limbo check failed at", traceback() )
		end
	end
	
	for promise, tback in pairs( gLimboPromises ) do
	
		gLimboPromises[ promise ] = nil
		
		gLimboCount = gLimboCount + 1

		if DEBUG_LIMBO and tback then
			debug_print( "Offending promise was created at ", tback )
		end
	
	end

end

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

local limboCheckQueued = false

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

local function checkAndClearLimbo()
	limboCheckQueued = false
	checkAndClearActionLimbo()
	checkAndClearPromiseLimbo()
end

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

local function queueLimboCheck()

	if not DEBUG_UNCLAIMED_PROMISES_VIA_TASK then
		return
	end

	if not limboCheckQueued then
		WFIdle_doASAP( checkAndClearLimbo )
		limboCheckQueued = true
	end

end

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

function KSPromises.DEBUG_getLimboCount()

	return gLimboCount

end

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

local function KSPromiseAction_addToLimbo( action )

	if 0 < gSuspendLimbo then
		return
	end
	
	checkAndClearActionLimbo()
		-- We're about to stomp action limbo so it better be empty

	gLimboAction = action
	
	if DEBUG_LIMBO then
		gLimboActionTraceback = traceback()
	end
	
	queueLimboCheck()

end

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

local function KSPromiseAction_removeFromLimbo( action )

	-- We remove items from limbo even when limbo checking is off

	if gLimboAction == action then
		gLimboAction = nil
		gLimboActionTraceback = nil
	end

end

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

local function KSPromise_addToLimbo( promise )

	if 0 < gSuspendLimbo then
		return
	end

	local tback = DEBUG_LIMBO and traceback()
	
	gLimboPromises[ promise ] = tback or false
	
	queueLimboCheck()

end

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

--[[ local ]] KSPromise_removeFromLimbo = function ( promise )

	-- We remove items from limbo even when limbo checking is off
	
	if promise ~= nil then
		gLimboPromises[ promise ] = nil
	end

end

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

local function finishSuspendLimbo( success, ... )

	gSuspendLimbo = gSuspendLimbo - 1
	
	if success then
		return ...
	else
		error( ( ... ), 2 ) -- Error at the caller's level
	end

end

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

function KSPromises.suspendLimbo( fn, ... )

	WFArguments_check( 'callable' )

	gSuspendLimbo = gSuspendLimbo + 1
	
	return finishSuspendLimbo( pcall( fn, ... ) )

end


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

local KSPromisesRunUntilDonePool = Rx.Class 'KSPromisesRunUntilDonePool' {

	count = function( self )
		local n = 0
		for _ in pairs( self ) do
			n = n + 1
		end
		return n
	end,
	
	WFCloseSupport.addCloseSupportMethods

}

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

KSPromises.makeRunUntilDonePool = function()
	return KSPromisesRunUntilDonePool()
end


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

-- If the action is at minimum priority, take it up to default unless it has
-- already executed.

local function KSPromiseAction_escalatePriority( self )

	if self._action and self._priority == kMinActionPriority then
		KSPromiseTarget_setPriority( self, kDefaultActionPriority )
	end
	
	return self

end

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

local gRunUntilDoneActions = KSPromisesRunUntilDonePool()

local keyRunUntilDoneTraceback = { }

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

local function KSPromiseAction_activate( self )

	if not self._owned then
		self._owned = true
		KSPromiseAction_removeFromLimbo( self )
		if self._pendingPriority then
			self:setPriority( self._pendingPriority )
		end
	end

end

--------------------------------------------------------------------------------
-- @mark KSPromiseAction class

KSPromiseAction = Rx.Class 'KSPromiseAction' {

	_action = false,
	_source = false,
	_sourceValue = false,
	_owned = false,
	_priority = kMinActionPriority,
	_pendingPriority = false,
	_suppressCloseLogging = false,

	KSPromiseTarget_addMethods,
	
	----------------------------------------------------------------------------
	--[[
		Constructs an action object.

		\param	source The source promise.
		\param action The function that defines the operation.

 	--]]
	

	__init = function( self, source, action )
	
		WFArguments_check( '_', 'KSPromise' )
	
		KSPromise_removeFromLimbo( source )
		
		local fn = makeEncodedValueFunction( action, false )
		
		assert( type( fn ) == 'function', "Expected function for action." )
		
		recordTransformFunctionToSource( fn, action )
		
		checkAndClearPromiseLimbo()
			-- When we create an action, there should be no promises in limbo
			
		KSPromiseTarget_init( self )
	
		self._action = fn
		
		KSPromiseTarget_setSourceOrResolve( self, source )
	
		KSPromiseAction_addToLimbo( self )
		
		assert( not KSPromiseSource_isResolved( source ) or not self._action,
			"If the promise is already resolved, we should not have an action to run." )

	end,
	
	----------------------------------------------------------------------------
	--[[
		Cancels this action, which detaches it from the promise and removes
		references to this object.
	--]]
	
	close = function( self )
		if DEBUG_ACTION_CLOSURE
			and self._action
			and not self._suppressCloseLogging
			and WFCloseSupport.isProcessingCloseNetwork()
				-- Only notify for indirect closure
		then
			local fn = self._action
			if fn == nop and self._source and self._source._transform then
				fn = self._source._transform
			end
			debug_print( string.format( "Closing action %s in thread %s from traceback: %s",
				transformFunctionName( self._action ),
				require 'WFCurrentThread'.DEBUG_getThreadName(),
				tostring( traceback() ) ) )
		end
		self._action = false
		KSOwnership_detachFromOwner( self )
		local source = self._source
		if source then
			-- Note that it is vital here that local source remain alive
			-- through this block and not get optimized away because we need
			-- the chain to be able to live through the removal of this target
			-- and any subsequent cancelation that would reflect back up.
			-- Lua doesn't do those sort of optimizations and we aren't
			-- using tail call on the target removal, so this should be okay.
			-- We could delay clearing _source until after the call to removeTarget
			-- but then there would be a strong tendency to want to pcall to
			-- removeTarget.
			self._source = false
			KSPromise_removeTarget( source, self )
		end
	end,
	
	----------------------------------------------------------------------------
	--[[ Adjusts the priority for this action. Changing the priority of an
		action can cause the message that enqueued it to be resent.
		
		\param priority The priority, a numeric value which can be positive
			or negative. 0 is the default.
	--]]

	setPriority = function( self, priority )
	
		if priority == 'very_low' then
			priority = kVeryLowActionPriority
		end
	
		-- We defer setting the priority until we are also owned.
	
		if not self._owned then
			self._pendingPriority = priority
		else
			KSPromiseTarget_setPriority( self, priority, false )
		end
		
		return self
	
	end,

	----------------------------------------------------------------------------
	--[[
		Keep this action from logging when closing early.

		\return self.
	--]]
	
	suppressCloseLogging = function( self )
	
		self._suppressCloseLogging = true
		
		return self
		
	end,

	----------------------------------------------------------------------------
	--[[
		Adds this action as a close target of another object, in order
		to keep it (and its associated promise) alive.
		Optionally stores this action for independant access (in order to
		close it or re-attach it).

		\param m A WFCloseSupport object.
		\param t Another table. If not provided, the action is stored in the object.
		\param k A key for storing this action as an entry in the table.
			If another action was already stored at this key, this replaces
			it and the previous action is closed.
			If not provided, the action object itself is used as the key.
	--]]

	attachTo = function( self, m, t, k )
	
		WFArguments_check( 'KSPromiseAction', '?table', '?table' )
		
		KSPromiseAction_activate( self )
		
		t = t or m

		-- When explicitly attaching to anything, we assume we want the
		-- default priority rather than the minium priority. The latter
		-- is resered for self attachment which won't generally extend the
		-- life expectancy of the action.
		
		KSPromiseAction_escalatePriority( self )
		
		-- If the action is nil, then we have presumably already detached and
		-- we should not re-attach. However, we may want to clear the old
		-- entry.
		
		if not self._action then
			KSOwnership_attachToOwner( nil, t, k, true )
			return self
		end
		
		KSOwnership_attachToOwner( self, t, k, true )
		
		if m then
			m:addCloseTarget( self )
		end
		
		return self
		
	end,

	----------------------------------------------------------------------------
	--[[
		Attaches this action to the global run-until-done set.
	--]]
	
	runUntilDone = function( self, pool )
	
		WFArguments_check( 'KSPromiseAction', '?KSPromisesRunUntilDonePool' )
	
		KSPromiseAction_activate( self )
	
		KSPromiseAction_escalatePriority( self )
		
		if not self._action then
			return
		end
		
		KSOwnership_attachToOwner( self, pool or gRunUntilDoneActions )
		
		if DEBUG_RUN_UNTIL_DONE_POOL and not pool then
			rawset( self, keyRunUntilDoneTraceback, traceback() )
		end
		
		return self
	
	end,

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

	_KSPromiseTarget_resolveWithEncodedValue = function( self, v )
	
		local action = self._action
		
		self._action = false
			-- We're done with the action as well after this.
			
		KSOwnership_detachFromOwner( self )
			-- We don't need to be kept alive any longer either
			
		if RUN_ACTIONS_VIA_QUEUE then
			
			WFCloseSupport_enqueueTargetForClose( self )
				-- "Safely" queue this object for closure.
				
			enqueueAction_pq( -self._sequence, { action, v } )
			
			-- If we make it to here, we can do the close.
			
			WFCloseSupport_runCloseQueue()
			
		else
		
			WFCloseSupport_enqueueTargetForClose( self )
				-- "Safely" queue this object for closure.
			
			if action then
				action( v )
			end
		
			-- If we make it to here, we can do the close.
			
			WFCloseSupport_runCloseQueue()
			
		end

	end,
	
	----------------------------------------------------------------------------

	_KSPromiseTarget_handleResolveError = function( self, msg )
		debug_print( string_format( "Error resolving promise action: %s", tostring( msg ) ) )
	end,
	
	WFCloseSupport.addCloseSupportMethods

}


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

local KSPromiseMethods = {
	_value = false,
	_targets = false,
	_source = false,
	_sourceValue = false,
	_transform = false,
	_mappingFunction = false, -- The function if any to use for identifying the transform
	_transformIsObserver = false,
	_priority = kMinActionPriority,
	KSPromiseTarget_addMethods
}

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

local WFNodeWatcher = require 'WFNodeWatcher'
local nodeWatcher = WFNodeWatcher( 'promise', DEBUG_NODE_BURSTS )

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

function KSPromiseMethods:__init( source, transformFn, transformIsObserver )

	WFArguments_check( '_', '?KSPromise | KSResolverPromiseSource', '?callable' )

	KSPromise_removeFromLimbo( source )
		-- If source is a promise, then remove it from limbo. If it isn't
		-- a promise, this will just do nothing.

	checkAndClearActionLimbo()
		-- We should not be creating more promises when we have an action
		-- in limbo.
		
	KSPromiseTarget_init( self )

	self._transform = transformFn or false
	self._transformIsObserver = transformIsObserver or false
		
	if DEBUG_PROMISE_RESOLVE_FAILURES then
		rawset( self, "_promiseTraceback", traceback() )
	end
	
	if source then
		KSPromiseTarget_setSourceOrResolve( self, source )
	end

	-- Where did this come from?
	
	if DEBUG_NODE_BURSTS then
		nodeWatcher:watchNode( self )
	end
	
	KSPromise_addToLimbo( self )
	
end

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

function KSPromiseMethods:whenResolvedDo( fn )

	WFArguments_check( 'KSPromise', '?callable | table' )
	
	if fn == nil then
		fn = nop
	end

	return KSPromiseAction( self, fn )

end

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

function KSPromiseMethods:runUntilDone( ... )

	WFArguments_check( 'KSPromise' )

	return self:whenResolvedDo():runUntilDone( ... )

end

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

function KSPromiseMethods:runUntilDoneP( ... )

	WFArguments_check( 'KSPromise' )

	self:runUntilDone( ... )
	
	return self

end

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

function KSPromiseMethods:runAttachedTo( ... )

	WFArguments_check( 'KSPromise' )

	return self:whenResolvedDo():attachTo( ... )

end

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

function KSPromiseMethods:isCanceled()

	WFArguments_check( 'KSPromise' )

	return self._value == KSPromises_kCanceled

end

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

KSPromise_enqueueTargets = function( self )

	local targets = self._targets
	
	if targets then
		self._targets = false
			-- We shouldn't need to track the targets after resolving and in
			-- fact they assume that this side of the link has been severed.
		local v = KSPromiseSource_encodedValue( self )
		for target in pairs( targets ) do
			KSPromiseTarget_enqueueResolvedWithSourceValue( target, v )
		end
	end

end

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

local function KSPromise_severSourceLink( self )

	local oldSource = self._source

	if oldSource then
		self._source = false
		if KSPromise:isClassOf( oldSource ) then
			KSPromise_removeTarget( oldSource, self )
		end
	end

end

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

local function KSPromise_updateTargetSources( self, newPromiseP )

	local targets = self._targets
	
	if targets then
		-- Replicate targets since the resolution process will modify it. We
		-- convert to an array as we go since we don't need the hash structure.
		local needResolve = { }
		for target in pairs( targets ) do
			needResolve[ #needResolve + 1 ] = target
		end
		-- Process each of the array entries.
		for i = 1, #needResolve do
			KSPromiseTarget_updateSource( needResolve[ i ], self, newPromiseP )
		end
	end

end

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

local function KSPromise_resolveWithPromise( self, promiseP )

	WFArguments_check( 'KSPromise', 'KSPromise' )
	
	-- We better not have already resolved.
	
	assert( not KSPromiseSource_isResolved( self ), "Promise is already resolved" )
	
	KSPromise_removeFromLimbo( promiseP )
		-- The source promise is no longer in limbo since if it isn't resolved
		-- already, it's a source for this promise.
	
	KSPromise_severSourceLink( self )
	
	if KSPromiseSource_isResolved( promiseP ) then
		KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, KSPromiseSource_encodedValue( promiseP ) )
		runPromiseQueue()
	else
		KSPromiseTarget_setSource( self, promiseP )
		-- But now if we don't have a transform to run, the targets for self
		-- can just link directly to this new promise.
		if not self._transform then
			KSPromise_updateTargetSources( self, promiseP )
		end
	end
	
end

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

local function KSPromise_resolveAsCanceled( self )

	WFArguments_check( 'KSPromise' )
	
	-- We better not have already resolved.
	
	assert( not KSPromiseSource_isResolved( self ), "Promise is already resolved" )
	
	KSPromise_severSourceLink( self )
	
	KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, KSPromises_kCanceled )

	runPromiseQueue()

end

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

local function KSPromise_resolveAsCanceledIfHasSource( self, source )

	WFArguments_check( 'KSPromise' )
	
	if self._source ~= source then
		return
	end
	
	if KSPromiseSource_isResolved( self ) then
		return
	end
	
	KSPromise_resolveAsCanceled( self )

end

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

local function KSPromise_finishPromiseTransform( self, v, ... )

	local count = select( '#', ... )
	
	if count == 0 then
		KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, v )
		return
	end
	
	local status = ( ... )
	
	-- If asked to propagate the same value via kSameValue then handle that.
	
	if status == KSPromises_kSame then
		if DEBUG and count ~= 1 then
			debug.print( "There should be no other results beyond 'same' (ignoring)" )
		end
		KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, v )
		return
	end
	
	-- If we cancel, then recognize that
	
	if status == KSPromises_kCanceled or status == KSPromises_kCanceledString then
		if DEBUG and count ~= 1 then
			debug.print( "There should be no other results beyond 'canceled' (ignoring)" )
		end
		KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, KSPromises_kCanceled )
		return
	end

	-- If we receive a promise, then we link to it.
	
	if KSPromise:isClassOf( status ) then
		if count ~= 1 then
			debug.print( "There should be no other results beyond the promise (ignoring): ", ... )
		end
		KSPromise_resolveWithPromise( self, status )
		return
	end
	
	-- Otherwise pack the value and resolve
	
	KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, encodePromiseValue( self, ... ) )

end

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

function KSPromiseMethods:_KSPromiseTarget_resolveWithEncodedValue( v, allowDeferral )

	-- Internal method. No type tests.

	assert( not self._value, "We should not have resolved yet" )

	-- Handle the transform and retransmission with the resolved
	-- source value.
	
	local transform = self._transform
	
	self._transform = false -- We only transform once
	
	-- Handle cancelation bypassing the transform
	
	if v == KSPromises_kCanceled and not self._transformIsObserver then
		KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, v )
		return
	end
	
	-- Defer if appropriate. This will run somewhat less often than the
	-- code that re-enables in that it blocks on more conditions. If that
	-- happens, however, we won't have a source value and so the extra
	-- invocations will do nothing.
	
	if STALL_INACTIVE_TRANSFORMS
		and transform
		and allowDeferral
		and not self._transformIsObserver -- don't stall observers
		and self._priority <= kMinActionPriority
	then
		local tback = rawget( self, '_promiseTraceback' )
		if tback then
			tback = tostring( tback )
			debug_print( "Stalling transform for promise created at: " .. tback )
		elseif DEBUG then
			debug_print( "Stalling promise transform: " .. transformFunctionName( transform ) )
		end
		self._transform = transform
		self._sourceValue = v
		return
	end
	
	-- Handle transforms
	
	if transform then
		if self._transformIsObserver then
			pcall( transform, v )
		else
			KSPromise_finishPromiseTransform( self, v, transform( v ) )
			return
		end
	end
	
	-- Otherwise accept the value and move on
	
	KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, v )
	
end

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

local function KSPromise_logTransformError( self, msg, ... )
	debug_print( string_format( "Error in promise transform: %s", tostring( msg ) ) )
end

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

function KSPromiseMethods:_KSPromiseTarget_handleResolveError( msg )

	-- Internal method no type checks

	KSPromise_logTransformError( self, msg )

	if msg == nil then
		msg = "< Unknown error (promise transform error)>"
	end

	if not KSPromiseSource_isResolved( self ) then
		KSPromise_setResolvedWithEncodedValueAndEnqueueTargets( self, WFPackedValues_packError( msg ) )
	else
		debug_print( "We appear to have had an error in the enqueue logic" )
	end

end

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

function KSPromiseMethods:mapP( fn )

	WFArguments_check( 'KSPromise', 'callable | table | KSPromise' )
	
	KSPromise_removeFromLimbo( self )

	local ffn = makeEncodedValueFunction( fn, true )
	
	recordTransformFunctionToSource( ffn, fn )

	local resultP = KSPromise( self, ffn )
	
	return resultP

end

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

function KSPromiseMethods:observeP( fn )

	WFArguments_check( 'KSPromise', 'callable | table' )
	
	KSPromise_removeFromLimbo( self )
	
	local observeFn
	
	if type( fn ) == 'table' and getmetatable( fn ) == nil then
		observeFn = function( v )
			local status
			if v == KSPromises_kCanceled then
				status = KSPromises_kCanceledString
			else
				status = WFPackedValues_status( v )
			end
			local ffn = fn[ status ]
			if ffn then
				-- pcall to avoid throwing
				pcall( ffn, decodePromiseValue( v ) )
			end
			return KSPromises_kSame
		end
	else
		observeFn = function( v )
			-- pcall to avoid throwing
			pcall( fn, decodePromiseValue( v ) )
			return KSPromises_kSame
		end
	end
	
	recordTransformFunctionToSource( observeFn, fn )

	local resultP = KSPromise( self, observeFn, true )
	
	return resultP

end

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

function KSPromiseMethods:mapDataP( fn )

	WFArguments_check( 'KSPromise', 'callable | KSPromise' )

	return self:mapP{ data = fn }

end

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

-- FIXME: Delete this routine

function KSPromiseMethods:mapAllP( fn, limit )

	WFArguments_check( 'KSPromise', 'callable', '?number' )
	
	return KSPromises.allP( fn, self, limit )

end

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

local function usingDo_doCloseResource( status, rsrc )
	if rsrc then
		rsrc:close()
	end
end

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

local function usingDo_promiseOrNothing( ... )

	local count = select( '#', ... )
	
	if count == 0 then
		return 'data', true
	end
	
	assert( count == 1 and KSPromises_isPromise( ... ),
		"Release and process functions should return a promise or nothing." )
		
	return ...
	
end

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

local function usingDo_wrapFn( dataFnP )

	return function( status, ... )
		return usingDo_promiseOrNothing( dataFnP( ... ) )
	end

end

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

function KSPromiseMethods:usingDoP( ... )

	local releaseFnP, processFnP

	local argCount = select( '#', ... )
	
	if argCount == 2 then
		releaseFnP, processFnP = ...
	elseif argCount == 1 then
		processFnP = ...
		releaseFnP = 'close'
	else
		assert( false, "Wrong number of arguments to KSPromise:usingDoP: " .. tostring( argCount ) )
	end
	
	-- Determine the release logic to run.

	local doReleaseFnP
	
	if releaseFnP == 'close' then

		doReleaseFnP = usingDo_doCloseResource
		
	elseif not releaseFnP then
	
		doReleaseFnP = nil
		
	elseif KSPromises_isPromise( releaseFnP ) then
	
		doReleaseFnP = releaseFnP

	elseif releaseFnP then

		assert( type( releaseFnP ) == 'function',
			"releaseFnP needs to be a function or promise or the special string 'close'" )
			
		doReleaseFnP = usingDo_wrapFn( releaseFnP )
		
	end
	
	-- Determine the processing to perform.
	
	local doProcessFnP
	
	if KSPromises_isPromise( processFnP ) then

		doProcessFnP = processFnP

	elseif not processFnP then

		doProcessFnP = function() return 'data', true end

	else

		doProcessFnP = usingDo_wrapFn( processFnP )

	end
	
	-- If we have no release function, then this is a simple mapping.
	
	if not doReleaseFnP then
		return self:mapDataP( doProcessFnP )
	end
	
	-- Calculate the processed value.
	
	local processedP = self:mapDataP( doProcessFnP )
	
	-- Always do the release. This runs out of line with the rest of the promise.
	
	local releasedP = nil
	
	local releaseObserverP = processedP
			:observeP( function()
				releasedP = self:mapDataP( doReleaseFnP )
				releasedP:runUntilDone()
			end )
			
	-- We want to run this at a very low priority. In fact, so low that it will
	-- fall into the cancelation range but not so low that promise transforms
	-- won't run.
	
	releaseObserverP
		:whenResolvedDo()
		:setPriority( 'very_low' )
		:runUntilDone()
			
	-- Return our result after waiting for the release in the normal case.
	
	return releaseObserverP:waitForP( function() return releasedP end )
			
end

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

local function waitForP_promiseOrNothing( ... )

	local count = select( '#', ... )
	
	if count == 0 then
		return 'data', true
	end
	
	if 1 < count then
		debug.print( "Function passed to KSPromise:waitForP should return only 0 or 1 results." )
	end
	
	local first = ( ... )
	
	if KSPromises_isPromise( first ) then
		return first
	end
	
	-- We allow false values here as well as legal results of the function

	if first then
		debug.print(
			"Function passed to KSPromise:waitForP should return a promise if it returns anything" )
	end
		
	return 'data', true

end

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

function KSPromiseMethods:waitForP( fnP )

	WFArguments_check( 'KSPromise', 'function | KSPromise' )
	
	local waitFor
	
	if KSPromises_isPromise( fnP ) then
		waitFor = fnP
	else
		waitFor = function( ... )
			return waitForP_promiseOrNothing( fnP( ... ) )
		end
	end
	
	return self:mapP( waitFor ):mapP( self )
	
end

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

local KSReactive_lazy = nil

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

local function getKSReactive()

	if not KSReactive_lazy then
		KSReactive_lazy = require 'KSReactive'
	end
	
	return KSReactive_lazy

end

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

local function constantB( status, ... )

	local KSReactive = getKSReactive()

	if status == 'data' then
		return KSReactive.constantB( ... )
	elseif status == 'error' then
		return KSReactive.constantErrorB( ( ... ) )
	else
		error( "We should either be working with data or errors not " .. tostring( status ) )
	end

end

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

function KSPromiseMethods:untilResolvedB( status, ... )

	WFArguments_check( 'KSPromise', 'string' )
	
	KSPromise_removeFromLimbo( self )

	if KSPromiseSource_isResolved( self ) then
		return constantB( decodePromiseValue( KSPromiseSource_encodedValue( self ) ) )
	end
	
	-- GC Notes (ownership)
	-- resultB -> varB
	-- resultB -> action -> promiseP
	
	-- GC Notes (shutdown)
	-- action is close target of resultB
	
	-- FIXME: This should report done when the promise resolves. (Maybe it does?)

	local varB = getKSReactive().variableB()
	
	-- Set the initial value
	
	if status == 'data' then
		varB:set( ... )
	elseif status == 'error' then
		varB:setError( ( ... ) )
	else
		error( "We should either be working with data or errors not " .. tostring( status ) )
	end
	
	-- We return a relay to hide the variable
	
	local resultB = varB:autoCloseRelayB() -- the result
	
	-- Hook up to set the variable when we resolve the promise
	
	self:whenResolvedDo( function( status, ... )
		if status == 'data' then
			varB:set( ... )
		elseif status == 'error' then
			varB:setError( ( ... ) )
		end
		varB:close()
	end )
	:suppressCloseLogging()
	:attachTo( resultB )
	
	return resultB
	
end

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

function KSPromiseMethods:debugP( name )

	WFArguments_check( 'KSPromise', 'string' )

	-- FIXME: This shouldn't actually take the promise out of limbo.
	
	return self:observeP( function( ... )
		if select( '#', ... ) == 0 then
			debug_print( "Canceled promise: ", name )
		else
			debug_print( "Resolved promise: ", name, ... )
		end
	end )

end

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

function KSPromiseMethods:_KSPromiseSource_recalcPriority()

	-- Internal method, no argument checks

	local newPriority = kMinActionPriority
	
	local targets = self._targets
	
	if targets then
	
		for target in pairs( targets ) do
		
			local targetPriority = target._priority
			
			if newPriority < targetPriority then
				newPriority = targetPriority
			end
		
		end
	
	end
	
	return KSPromiseTarget_setPriority( self, newPriority, true )
		-- tail call since we're running up the promise chain
	
end

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

function KSPromiseMethods:_KSPromiseSource_addTarget( target )

	-- Internal method, no argument checks

	assert( not KSPromiseSource_isResolved( self ),
				"We should never add targets to resolved promises" )

	KSPromise_removeFromLimbo( self )

	local targets = self._targets

	if not targets then
		targets = makeFullyWeakTable()
		self._targets = targets
	end

	targets[ target ] = true
	
	local targetPriority = target._priority
	
	local oldPriority = self._priority
	
	if oldPriority < targetPriority then
		KSPromiseTarget_setPriority( self, targetPriority )
	end
	
end

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

-- We want to be able to call remove target dynamically...

KSPromise_removeTarget = function( self, target )

	local targets = self._targets
	
	-- Check that this really is a target.

	if ( not targets ) or ( targets[ target ] == nil ) then
		debug_print( "Removing non-target (%s) from promise (%s)",
			tostring( target ), tostring( self ) )
		return
	end
	
	-- Clear it.

	targets[ target ] = nil
	
	-- Check to see whether we are out of targets and hence can drop the weak
	-- table more rapidly.
	
	if next( targets ) == nil then
		self._targets = false
	end
	
	-- Figure out our new priority.
	
	self:_KSPromiseSource_recalcPriority()
	
end


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

local gOldRunUntilDoneActions = makeFullyWeakTable()

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

function KSPromises.testing_runUntilDonePoolIsEmpty( debugEntries )

	-- Look for an entry that wasn't logged into the old actions

	local entry = nil
	
	for k, v in pairs( gRunUntilDoneActions ) do
		if gOldRunUntilDoneActions[ k ] == nil then
			entry = k
			break
		end
	end
	
	if entry == nil then
		return true
	end
	
	if DEBUG_RUN_UNTIL_DONE_POOL and debugEntries then
		debugEntries( "Unexpected entry in the run until done pool: ",
			tostring( entry ), tostring( rawget( entry, keyRunUntilDoneTraceback ) ) )
	end
		
	return false
		
end


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

function KSPromises.testing_resetRunUntilDonePool()

	for k, v in pairs( gRunUntilDoneActions ) do
		gOldRunUntilDoneActions[ k ] = k
	end
	
end


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

local function KSPromises_resolvedPromiseP( status, ... )

	WFArguments_check( 'string' )

	local promiseP = KSPromise()
	
	KSPromise_removeFromLimbo( promiseP )
		-- Pre-resolved promises probably shouldn't be in limbo
	
	KSPromise_setResolvedWithEncodedValue( promiseP, encodePromiseValue( promiseP, status, ... ) )
	
	return promiseP

end

KSPromises.resolvedPromiseP = KSPromises_resolvedPromiseP

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

function KSPromises.canceledPromiseP()

	local promiseP = KSPromise()
	
	KSPromise_setResolvedWithEncodedValue( promiseP, KSPromises_kCanceled )
	
	return promiseP

end

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

KSPromises_isPromise = function( x )
	return KSPromise:isClassOf( x )
end

KSPromises.isPromise = KSPromises_isPromise


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


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

local gResolverPromiseSourceToPromise = makeFullyWeakTable()
	-- A weak link from resolver promise sources to their promises
	
local gResolverPromiseSourceToResolver = makeFullyWeakTable()
	-- A weak link from resolver promise sources to their resolvers
	
--------------------------------------------------------------------------------



local function resolverStartResolution( resolver, priority )

	resolver:startResolution( priority )
	
end

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

local function resolverStopResolution( resolver )

	resolver:stopResolution()
	
end

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

local function KSResolverPromiseSource_stopResolutionWithResolver( self, resolver )

	if not resolver then
		return
	end
	
	if WFCloseSupport_hasBeenOrWillBeClosed( resolver ) then
		return
	end
	
	if not self._resolutionPriority then
		return
	end
	
	self._resolutionPriority = false
	
	if not pcall( resolverStopResolution, resolver ) then
		pcall( doClose, resolver )
	end

end

--------------------------------------------------------------------------------
 -- @mark KSResolverPromiseSource class

 --[[!
 	\class KSResolverPromiseSource

	\brief Represents the source promise for a resolver.

 The promise resolver mechanism is built around three objects: the <em>promise</em>,
 the <em>resolver promise source</em>, and the <em>resolver</em>.
 	
 These are connected as follows:
 <ul>
 	<li>promise -> _source -> source</li>
 	<li>promise -> keyPromisePromiseSourceCloser -> [ closer ] -> source
 	(not established until started)	</li>
 </ul>
 <ul>
 	<li>source -> gResolverPromiseSourceToPromise -> promise (weak)	</li>
 	<li>source -> gResolverPromiseSourceToResolver -> resolver (weak) </li>
 	<li>source -> _retainedResolver -> resolver (optional) </li>
 </ul>
 <ul>
 	<li>resolver -> keyResolverPromiseSource -> source </li>
 	<li>resolver -> keyResolverPromiseSourceCloser -> [ closer ] -> source </li>
 </ul>
 The source in general owns nothing unless it is instructed to retain the resolver
 in which case we have an ownership chain that runs promise -> source -> resolver.
 
 The two closers mean that losing all references to the promise or the resolver
 will result in the source being closed.
 
 The promise source is a close target of the resolver. The resolver is optionally
 a close target of the source. Closing the source will cancel the promise if it
hasn't already resolved.

--]]

local KSResolverPromiseSource = Rx.Class 'KSResolverPromiseSource' {

	_resolutionPriority = false,
		-- Resolution priority reflects the last priority sent through to
		-- startResolution. If we are currently stopped, it is false.
		
	_retainedResolver = false,
	
	_value = false,
	
	_tback = false,

	__init = function( self, resolver, options, tback )

		WFArguments_check( '_', 'table | userdata', 'string' )
			-- GRUNGE: It would be cool to check for protocols
			
		local parsedOptions = resolverOptionsParser( options )
		
		local isRetained = 'retained' == KSOptions_exactlyOneOf( parsedOptions,
								'retained | unretained' )
		
		local isCloseTarget = 'closeTarget' == KSOptions_exactlyOneOf( parsedOptions,
									'closeTarget | noClose' )
									
		checkAndClearActionLimbo()
			-- We should not create more promises while an action is in
			-- limbo.

		assert( not rawget( resolver, 'private:resolverPromiseSource' ),
				"Resolver is already connected to promise" )
				
		self._tback = tback or false
		
		-- Losing references to either the promise or the resolver will
		-- result in a close getting fired through the system.
		
		rawset( resolver, 'private:resolverPromiseSourceCloser',
					WFCloseSupport_makeCloser( self, 'resolver', tback or nil ) )

		if isCloseTarget then
			self:addCloseTarget( resolver )
		end
		
		resolver:addCloseTarget( self )
		
		-- Fill in the weak links
		
		gResolverPromiseSourceToResolver[ self ] = resolver
		
		rawset( resolver, 'private:resolverPromiseSource', self )
		
		-- Fill in the strong link if appropriate
		
		if isRetained then
			self._retainedResolver = resolver
		end
		
		-- Optionally add support for figuring out what items are getting GC'd.
		
		if DEBUG_PROMISE_RESOLVER_GC then
			rawset( self, '_creationBacktrace', traceback() )
		end

	end,
	
	close = function( self )
	
		local promiseP = gResolverPromiseSourceToPromise[ self ]
		
		-- Mark the promise as canceled if it hasn't been resolved
		-- yet. We also clear the link to the closer so that we don't
		-- keep self alive.
		
		if promiseP then
			KSPromise_resolveAsCanceledIfHasSource( promiseP, self )
			rawset( promiseP, 'private:promisePromiseSourceCloser', nil )
		end
		
		assert( not promiseP or promiseP._source ~= self,
				"Promise is still linked to source." )
		
		-- Clear the various links and closers
	
		local resolver = gResolverPromiseSourceToResolver[ self ]
		
		if resolver then
		
			-- Stop resolution on the resolver in case it isn't a close
			-- target. We only need to do this if it isn't being or
			-- hasn't been closed.
			
			KSResolverPromiseSource_stopResolutionWithResolver( self, resolver )

			rawset( resolver, 'private:resolverPromiseSourceCloser', nil )
			rawset( resolver, 'private:resolverPromiseSource', nil )

		end
		
		gResolverPromiseSourceToPromise[ self ] = nil
		gResolverPromiseSourceToResolver[ self ] = nil
		
		self._retainedResolver = false
		
		-- We don't bother calling stopResolution because the resolver
		-- is a close target for the promise source.
				
	end,
	
	WFCloseSupport.addCloseSupportMethods,

	_KSPromiseSource_addTarget = function( self, target )
		assert( not gResolverPromiseSourceToPromise[ self ],
					"We should not have a target promise yet" )
		gResolverPromiseSourceToPromise[ self ] = target
	end,
	
	_KSPromiseSource_recalcPriority = function( self )

		local promiseP = gResolverPromiseSourceToPromise[ self ]
		
		local resolver = gResolverPromiseSourceToResolver[ self ]
		
		-- Loss of the resolver is presumably a prelude to being closed and sending
		-- out a cancel message. In any event, without a resolver, we have no one
		-- to tell about the change in priorities.
		
		if not resolver then
			return
		end

		local wantRequest = promiseP and ( kMinActivationPriority <= promiseP._priority )

		if wantRequest then
		
			local priority = promiseP._priority
			
			if priority ~= self._resolutionPriority then
				self._resolutionPriority = priority
				pcall( resolverStartResolution, resolver, priority )
				if not rawget( promiseP, 'private:promisePromiseSourceCloser' ) then
					rawset( promiseP, 'private:promisePromiseSourceCloser',
							WFCloseSupport_makeCloser( self, 'promise', self._tback or nil ) )
				end
			end
														
		else
		
			KSResolverPromiseSource_stopResolutionWithResolver( self, resolver )
			
		end
		
	end,
		
}

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

function KSPromises.promiseForResolverP( resolver, options, tback )

	WFArguments_check( 'table | userdata' )

	assert( resolver, "No resolver" )
	
	if DEBUG_PROMISE_CREATION and not tback then
		tback = traceback()
	end
	
	local source = KSResolverPromiseSource( resolver, options, tback or nil )
	
	assert( source )
	
	return KSPromise( source )

end

local KSPromises_promiseForResolverP = KSPromises.promiseForResolverP

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

local function Resolver_canResolvePromise( self )

	local promiseSource = rawget( self, 'private:resolverPromiseSource' )
	
	if not promiseSource then
		return false
	end
	
	if WFCloseSupport_hasBeenOrWillBeClosed( promiseSource ) then
		return false
	end
	
	if KSPromiseSource_isResolved( promiseSource ) then
		return false
	end
	
	local promise = gResolverPromiseSourceToPromise[ promiseSource ]
	
	if not promise then
		return false
	end
	
	if KSPromiseSource_isResolved( promise ) then
		return false
	end
	
	return true

end

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

local function Resolver_resolve( resolver, ... )

	local promiseSource = rawget( resolver, 'private:resolverPromiseSource' )
	
	assert( promiseSource, "No promise source for resolver. Already resolved or type error?" )
	
	-- It's possible that the promise would get GC'd before the resolver.
	-- If that happens, we force things to cancel in case there was no
	-- closer yet on the promise.
	
	local promiseP = gResolverPromiseSourceToPromise[ promiseSource ]
	
	if not promiseP then
		promiseSource:close()
		resolver:close()
		return
	end
	
	-- Validate that the links still look correct.
	
	assert( promiseP._source == promiseSource, "Promise has already disconnected from source." )
	
	assert( not KSPromiseSource_isResolved( promiseSource ), "Promise source has already resolved" )
	
	-- We will either have a new source or an encoded value in a successful resolution
	
	local newSource, encodedValue
	
	local n = select( '#', ... )

	if n == 0 then

		encodedValue = KSPromises_kCanceled
		
	else
	
		local first = ( ... )
		
		if KSPromise:isClassOf( first ) then
		
			if n ~= 1 then
				debug.print( "Attempt to resolve promise to a promise plus something else." )
			end
			newSource = first
			
		else
		
			encodedValue = WFPackedValues_pack( ... )

		end
		
	end
	
	-- Now do the update
	
	gResolverPromiseSourceToPromise[ promiseSource ] = nil -- Break the link

	if newSource then
	
		KSPromise_resolveWithPromise( promiseP, newSource )
		
	else

		KSPromiseSource_setResolvedWithEncodedValue( promiseSource, encodedValue )
		
		KSPromise_severSourceLink( promiseP )
	
		KSPromiseTarget_enqueueResolvedWithSourceValue( promiseP, encodedValue )
	
		runPromiseQueue()
		
	end

	-- It's possible that we're resolving from the close operation itself, but
	-- it's also possible that we're resolving explicitly in which case we want
	-- to trigger a close since we've completed our task.
	
	if not WFCloseSupport_hasBeenOrWillBeClosed( promiseSource ) then
		promiseSource:close()
	end
	
	-- We also want to close the resolver itself if it hasn't already closed.
	
	resolver:close()

end

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

function KSPromises.addPromiseResolverMethods( methodTable )

	WFArguments_check( 'table' )

	if methodTable.stopResolution == nil then
		methodTable.stopResolution = doClose
	end
	
	if methodTable.startResolution == nil then
		methodTable.startResolution = nop
	end
	
	assert( methodTable.resolvePromise == nil,
			"Method table must not include a definition of resolvePromise" )
			
	assert( methodTable.canResolvePromise == nil,
			"Method table must not include a definition of canResolvePromise" )
			
	methodTable.resolvePromise = Resolver_resolve
	
	methodTable.canResolvePromise = Resolver_canResolvePromise
	
	return methodTable

end

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

local KSPromises_addPromiseResolverMethods = KSPromises.addPromiseResolverMethods

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

-- @mark KSPromiseResolver class


KSPromiseResolver = Rx.Class 'KSPromiseResolver' {

	_didResolve = false,
	
	_obeysStopResolution = false,

	__init = function( self, obeysStopResolution )
	
		checkAndClearActionLimbo()
			-- Don't create resolvers when we're still working on an action.
			
		self._obeysStopResolution = obeysStopResolution or false

	end,
	
	__call = function( self, ... )

		WFArguments_check( 'KSPromiseResolver' )
		
		assert( not self._didResolve, "Promise has already been resolved." )
		
		self._didResolve = true
		
		if self:canResolvePromise() then
			self:resolvePromise( ... )
		end

		self:close()

	end,
	
	close = function( self )
	
		-- The promise source will be a close target which will then
		-- cancel the promise if it hasn't been resolved. On the other,
		-- hand, we don't actually take other action to close the resolver
		-- when the promise source goes away since the assumption is that
		-- other code owns the promise resolver.
	
	end,
	
	startResolution = nop, 	-- Nothing to do
	
	stopResolution = function( self )
		if self._obeysStopResolution then
			self:close()
		end
	end,
	
	KSPromises_addPromiseResolverMethods,
	
	WFCloseSupport.addCloseSupportMethods

}

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

function KSPromises.promiseP( obeysStopResolution )

	local resolver = KSPromiseResolver( obeysStopResolution )
	
	local options = obeysStopResolution and 'unretained | closeTarget'
										 or 'unretained | noClose'
	
	local promise = KSPromises_promiseForResolverP( resolver, options )
	
	return promise, resolver

end

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

local KSParallelResolver = Rx.Class 'KSParallelResolver' {

	_priority = false,
	
	_fn = false,
	
	_array = false,
	_startLimit = false,
	_startedCount = false,
	_nilTerminates = false,
	
	_result = false,
	
	_runLimit = false,

	_runningActions = false,
	_runningCount = false,
	
	_startingPromises = false,
	
	_didResolve = false,

	__init = function( self, fn, array, limit, result )
	
		WFArguments_check( '_', 'callable', 'table', '?number' )
		
		assert( ( not limit ) or ( 0 < limit ), "Limit must be positive" )
		
		local array_n = array.n
	
		self._fn = fn
		self._array = array
		self._startLimit = array_n or #array
		self._startedCount = 0
		self._nilTerminates = not array_n
		self._result = result or false
		
		self._runLimit = limit or self._startLimit
		
		self._runningActions = { }
		self._runningCount = 0
		
	end,
	
	_resolvePromise = function( self, ... )
		
		if self._didResolve then
			return -- already resolved
		end
		
		self._didResolve = true
		
		self:resolvePromise( ... )
	
	end,
	
	_startPromises = function( self )
	
		-- If we are already resolved, then don't start anything else.
		
		if self._didResolve then
			return
		end
	
		-- We want to avoid recursion since that makes the book
		-- keeping a bit more funky.
	
		if self._startingPromises then
			return
		end
	
		self._startingPromises = true
		
		-- Start promises as long as we are below the limits

		local runLimit = self._runLimit
		
		local startLimit = self._startLimit
		
		while ( self._runningCount < runLimit ) and ( self._startedCount < startLimit ) do repeat
		
			local idx = self._startedCount + 1
		
			self._startedCount = idx
			
			local v = self._array[ idx ]
			
			-- A nil terminates the process
			
			if v == nil and self._nilTerminates then
				self._startedCount = startLimit
				break -- continue
			end
			
			local success, p = pcall( self._fn, v )
			
			if not success then
				self:_resolvePromise( 'error', p or "< Unknown error generating promise >" )
				return
			end
			
			if not KSPromises_isPromise( p ) then
				if self._result then
					self._result[ idx ] = p
				end
				break -- continue
			end
			
			KSPromise_removeFromLimbo( p )
			
			if KSPromiseSource_isResolved( p ) then
				-- A resolved promise either terminates (for errors) or is skipped
				local pv = KSPromiseSource_encodedValue( p )
				if pv == KSPromises_kCanceled then
					self:_resolvePromise( p )
					return
				end
				local status, data = WFPackedValues_unpack( pv )
				if status == 'error' then
					self:_resolvePromise( p )
					return
				elseif status == 'data' then
					if self._result then
						self._result[ idx ] = data
					end
				end
				break -- continue
			end
			
			-- We trust that we won't get into recursion here since we just checked
			-- that the p isn't resolved. If p resolves with data, we can start more
			-- promises. If it resolves as an error or cancelation, then we resolve
			-- the compound promise with that value if we haven't resolved it already.
			
			local action = p:whenResolvedDo( function( ... )
				if ( ... ) == 'data' then
					self._runningCount = self._runningCount - 1
					self:_startPromises()
					if self._result then
						local _, v = ...
						self._result[ idx ] = v
					end
				else
					self:_resolvePromise( ... )
				end
			end )
			
			action:setPriority( self._priority )
			action:attachTo( self, self._runningActions )
			
			self._runningCount = self._runningCount + 1
		
		until true end
		
		-- Remove the block on starting promises
		
		self._startingPromises = false
	
		-- If nothing is running when we're done, then we apparently had nothing
		-- to resolve.
		
		if self._runningCount == 0 then
			self:_resolvePromise( 'data', self._result or true )
		end
		
	end,
	
	startResolution = function( self, priority )
	
		self._priority = priority
		
		-- Update existing actions
		
		for action in pairs( self._runningActions ) do
			action:setPriority( priority )
		end
		
		-- Start new promises since this may be our first call
		
		self:_startPromises()
	
	end,
	
	KSPromises_addPromiseResolverMethods,
	
	WFCloseSupport.addCloseSupportMethods
	
}

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

function KSPromises.allP( --[[function]] fn, --[[array]] array, --[[number]] limit )

	if KSPromises.isPromise( array ) then
		return array:mapDataP( function( _, av )
			return KSPromises.allP( fn, av, limit )
		end )
	end

	WFArguments_check( 'callable', 'table', '?number' )

	local resolver = KSParallelResolver( fn, array, limit or 1 )
	
	return KSPromises_promiseForResolverP( resolver, 'retained | closeTarget' )

end

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

function KSPromises.mapArrayP( --[[function]] fn, --[[array]] array, --[[number]] limit )

	if KSPromises.isPromise( array ) then
		return array:mapDataP( function( _, array )
			return KSPromises.mapArrayP( fn, array, limit )
		end )
	end

	WFArguments_check( 'callable', 'table', '?number' )

	local resolver = KSParallelResolver( fn, array, limit or 1, { } )
	
	return KSPromises_promiseForResolverP( resolver, 'retained | closeTarget' )

end


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

-- @mark tableP

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

-- FIXME_MCH: Much of this is shared with KSReactive.tableB and could be moved into
-- shared code.

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

-- We use special designators for inputs to the template. This is a collection
-- we build as need be.

local inputObjects = { }

-- We maintain a mapping from input objects to the index they represent

local inputObjectToIndex = { }

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

local function getInputObject( idx )

	local obj = inputObjects[ idx ]
	
	if not obj then
	
		obj = { }
		
		inputObjects[ idx ] = obj
		
		inputObjectToIndex[ obj ] = idx
	
	end
	
	return obj

end

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

local isSource = KSPromises_isPromise

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

local isEventNode_lazy = nil

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

local function checkValidTableForTemplate( v )

	if isEventNode_lazy( v ) then
		error "We do not support event nodes in templates for tableP."
	end
	
end

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

local function convertToTemplate_imp( t, inputList, inputMap )

	local tt = type( t )
	
	if tt ~= 'table' then
		return t
	end
	
	checkValidTableForTemplate( t )
	
	if isSource( t ) then
		local idx = inputMap[ t ]
		if not idx then
			idx = #inputList + 1
			inputList[ idx ] = t
			inputMap[ t ] = idx
		end
		return getInputObject( idx )
	end
	
	local result = { }
	
	for k, v in pairs( t ) do
		result[ k ] = convertToTemplate_imp( v, inputList, inputMap )
	end
	
	return result

end

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

local function convertToTemplate( t )

	if isEventNode_lazy == nil then
		isEventNode_lazy = getKSReactive().isEventNode
	end
	
	local inputList = { }
	local inputMap = { }
	
	local template = convertToTemplate_imp( t, inputList, inputMap )
	
	return template, inputList

end

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

local function templateContainsPromise_imp( t )

	local tt = type( t )
	
	if tt ~= 'table' then
		return false
	end
	
	checkValidTableForTemplate( t )

	if KSPromises_isPromise( t ) then
		return true
	end
	
	if getmetatable( t ) ~= nil then
		return false
	end
	
	for k, v in pairs( t ) do
		if templateContainsPromise_imp( v ) then
			return true
		end
	end
	
	return false
	
end

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

local function templateContainsPromise( t )

	if isEventNode_lazy == nil then
		isEventNode_lazy = getKSReactive().isEventNode
	end
	
	return templateContainsPromise_imp( t )

end

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

local function instantiateTemplate( t, inputs )

	local tt = type( t )
	
	if tt ~= 'table' then
		return t
	end
	
	local idx = inputObjectToIndex[ t ]
	
	if idx then
		return inputs[ idx ]
	end
	
	local result = { }
	
	for k, v in pairs( t ) do
		result[ k ] = instantiateTemplate( v, inputs )
	end
	
	return result

end

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

local function KSPromises_tableP( t )

	local template, inputList = convertToTemplate( t )
	
	return KSPromises.argTupleP( unpack( inputList ) ):mapDataP( function( status, ... )
		return 'data', instantiateTemplate( template, { ... } )
	end )

end

KSPromises.tableP = KSPromises_tableP


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

-- @mark argTupleP

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

local function KSPromise_packedValue( p )

	if not KSPromises_isPromise( p ) then
		return p
	end

	assert( KSPromiseSource_isResolved( p ), "promise needs to have been resolved" )
	
	return KSPromiseSource_encodedValue( p )

end

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

local function constructArgTupleData( args )

	return WFPackedValues_unpack(
				WFPackedValues_argTuple(
					WFVarArgs_map(
						KSPromise_packedValue, unpack( args, 1, args.n ) ) ) )

end

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

local function convertTablePromises( x )

	-- x needs to be a plain table without a metatable

	if type( x ) ~= 'table' then
		return x
	end
	
	if getmetatable( x ) ~= nil then
		return x
	end
	
	-- If it contains a promise, then convert it. Otherwise, leave it unchanged.

	if templateContainsPromise( x ) then
		return KSPromises_tableP( x )
	else
		return x
	end

end

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

local function identity( x )
	return x
end

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

local function doArgTuple( forcePromise, ... )

	local n = select( '#', ... )
	
	if n == 0 then
		if forcePromise then
			return KSPromises_resolvedPromiseP( 'data' )
		else
			return 'data'
		end
	end
	
	if n == 1 then
		local x = ( ... )
		x = convertTablePromises( x )
		if KSPromises_isPromise( x ) then
			return x
		elseif forcePromise then
			return KSPromises_resolvedPromiseP( 'data', x )
		else
			return 'data', x
		end
	end

	local args = { n = n, ... }
	
	-- Convert any table promises as appropriate
	
	for i = 1, n do
		args[ i ] = convertTablePromises( args[ i ] )
	end
	
	-- Remove any promises in the arguments from limbo. Also detect promises early.
	
	local hasPromise = false
	
	for i = 1, n do
		local p = args[ i ]
		if KSPromises_isPromise( p ) then
			hasPromise = true
			KSPromise_removeFromLimbo( p )
		end
	end
	
	-- If we have no promises, then just return as resolved data.
	
	if not hasPromise then
		if forcePromise then
			return KSPromises_resolvedPromiseP( 'data', ... )
		else
			return 'data', ...
		end
	end
	
	-- Analyze the arguments.
	
	local isCanceledP = false -- if non-false, then a canceled promise
	local isErrorP = false -- if non-false, then an error promise
	local unresolvedPromises = { }
	
	for i = 1, n do
		local p = args[ i ]
		if KSPromises_isPromise( p ) then
			if not KSPromiseSource_isResolved( p ) then
				unresolvedPromises[ #unresolvedPromises + 1 ] = p
			elseif p:isCanceled() then
				isCanceledP = isCanceledP or p
			elseif WFPackedValues_status( KSPromiseSource_encodedValue( p ) ) == 'error' then
				isErrorP = isErrorP or p
			end
		end
	end
	
	-- Errors get propagated through as such. Note that in the event of multiple errors,
	-- we just take the first one.
	
	if isErrorP then
		return isErrorP
	end
	
	-- Cancelation also stops further work.
	
	if isCanceledP then
		return isCanceledP
	end
	
	-- If everything is resolved, we can just construct the results now.
	
	if #unresolvedPromises == 0 then
		if forcePromise then
			return KSPromises_resolvedPromiseP( constructArgTupleData( args ) )
		else
			return constructArgTupleData( args )
		end
	end
	
	-- Otherwise, wait for the unresolved promises to finish and then build
	-- a result based on the arguments. Note that allP only returns data if
	-- all of the promises succeed, so we know that we have data for all of
	-- the promises.
	
	local resolvedP =
		KSPromises.allP( identity, unresolvedPromises, 100 )
		-- Run up to 100 at a time
		
	return resolvedP:mapDataP( function()
			return constructArgTupleData( args )
		end )

end

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

local function KSPromises_argTupleP( ... )
	return doArgTuple( true, ... )
end

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

KSPromises.argTupleP = KSPromises_argTupleP


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

-- @mark callP

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

local function KSPromises_callP( fn, ... )

	assert( fn, "No function provided." )

	if KSPromises_isPromise( fn ) then
		return KSPromises.argTupleP( fn, ... ):mapDataP( function( status, f, ... )
			return doArgTuple( false, f( ... ) )
		end )
	else
		WFArguments_check( 'callable' )
		return KSPromises.argTupleP( ... ):mapDataP( function( status, ... )
			return doArgTuple( false, fn( ... ) )
		end )
	end

end

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

KSPromises.callP = KSPromises_callP


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

-- @mark liftP

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

local liftPCache = makeFullyWeakTable()
	-- functions to their lifted forms. This makes it safer to lift repeatedly.

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

KSPromises.liftP = function( fn )

	WFArguments_check( 'function' )
	
	local cached = liftPCache[ fn ]
	
	if cached then
		return cached
	end
	
	local result = function( ... )
		return KSPromises.callP( fn, ... )
	end
	
	liftPCache[ fn ] = result
	liftPCache[ result ] = result -- No sense in lifting twice
	
	return result

end


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

-- @mark repeatUntilP

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

function KSPromises.repeatUntilP( --[[ function ]] fnP )

	WFArguments_check( 'function' )
	
	local function makePromiseP()
	
		local p = KSPromises_argTupleP( fnP() )
		
		p = p:mapDataP_new( function( x )
		
			if x then
				return KSPromises_kSame
			else
				return makePromiseP()
			end
		
		end )
		
		return p
	
	end
	
	return makePromiseP()

end


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

-- @mark New-style API

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

-- Export kSame and kCanceled

KSPromises.SAME = KSPromises_kSame
KSPromises.CANCELED = KSPromises_kCanceled

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

local function finishNewMap( ... )

	local first = ( ... )
	
	if first == KSPromises_kSame or first == KSPromises_kCanceled then
		if DEBUG and select( '#', ... ) ~= 1 then
			debug.print( "Ignoring extra values from mapDataP_new function when leading result is kSame or kCanceled." )
		end
		return first
	end
	
	return doArgTuple( false, ... )

end

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

local function constructNewMapForFunction( f )

	local result = function( status, ... )
	
		-- We only process data letting everything else pass through unchanged
	
		if status ~= 'data' then
			return KSPromises_kSame
		end
		
		-- We don't bother with a pcall here because the transform is run
		-- within a pcall context.
		
		return finishNewMap( f( ... ) )
	
	end
	
	recordTransformFunctionToSource( result, f )
	
	return result

end

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

local function constructNewMap( ... )

	local n = select( '#', ... )
	
	if n == 1 then
	
		local x = ( ... )
		
		if type( x ) == 'function' then
			return constructNewMapForFunction( x )
		end
		
		if KSPromises_isPromise( x ) then
			return function( status )
				if status == 'data' then
					return x
				else
					return KSPromises_kSame
				end
			end
		end
	
	end
	
	error( "Unsupported case for mapDataP_new", 2 ) -- FIXME: Check error level

end

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

function KSPromiseMethods:mapDataP_new( ... )

	return self:mapP( constructNewMap( ... ) )

end

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

-- @mark KSPromises class

KSPromise = Rx.Class 'KSPromise' ( KSPromiseMethods )


--------------------------------------------------------------------------------
--[[!
	\endcond
--]]

return KSPromises
