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

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

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

--[[

KSFunctionUtils: Miscellaneous function utilities for working with Lua.

	Routines with names ending in Fn construct functions.

	function memoizeFn( fn )
		-- Returns a function that maintains a fully weak table mapping single
		-- parameter values to single results. This even properly handles nil
		-- and NaN as parameters and results.
		
	function constantFn( ... )
		-- Returns a function which returns the values passed to constant
			
	function bindAllToFront( fn, ... )
		-- Returns a function with the parameters passed here prepended to the parameters passed at invocation time.
		-- https://en.wikipedia.org/wiki/Partial_application
	
	function partial( fn, ... )
		-- Alias for bindAllToFront
	
	function nop()
		-- Does nothing. Returns nothing.
		
	function identity( ... )
		-- Returns its arguments.
		
	function fieldExtractorFn( fieldSelector )
		-- Returns a function( t ) return t[ fieldSelector ] end.
		
	function isFieldExtractorFn( fn )
		-- Returns either true, fieldSelector or false depending on whether the
		-- parameter is a field extractor function.

	function makeSingletonMemberFn( fn, name )
		-- Returns a function that is intended to be used as a method on a class.
		-- This method, when called, will call fn( self ) the first time for each
		-- object (i.e. each distinct value of self) and will then cache that result
		-- for each subsequent call.
		
		-- Name must be a distinct value (per object) to identify the singleton.
		-- Use it much as you would use a member name. (It is used to store an index
		-- off of a subsidiary table on the object.)
		
	function makeSingletonMemberFnE( fn, name )
		-- Same as makeSingletonMemberFn, but returns a :relayE copy of the
		-- singleton.

	function makeSingletonMemberFnB( fn, name )
		-- Same as makeSingletonMemberFn, but returns a :relayB copy of the
		-- singleton.
	

]]

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

local KSFunctionUtils = { }

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

local setmetatable = setmetatable
local select = select
local unpack = unpack

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

local kNil = { } -- encoding for nils in the memoize cache

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

local mtFullyWeak = { __mode = 'kv' }
	-- Avoids a dependence on KSTableUtils to simply build our own weak
	-- table metatable. That code should perhaps move below KSTableUtils
	-- to something we really trust to be free of import dependencies.

local memoize = assert( require 'KSFunctionUtils_memoize'.memoizeFn )
KSFunctionUtils.memoizeFn = memoize

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

function KSFunctionUtils.nop()
	-- Do nothing
end

local nop = KSFunctionUtils.nop

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

function KSFunctionUtils.identity( ... )
	return ...
end

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

function KSFunctionUtils.constantFn( ... )
	local n = select( '#', ... )
	if n == 0 then
		return nop
	elseif n == 1 then
		local value = ...
		return function() return value end
	else
		local values = { ... }
		return function() return unpack( values, 1, n ) end
	end
end

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

function KSFunctionUtils.bindAllToFront( f, ... )

	local boundArgsN = select( "#", ... )

	if boundArgsN == 0 then

		return f

	elseif boundArgsN > 9 then

		local boundArgs = { ... }

		return function( ... )

			local allArgs = {}

			for i = 1, boundArgsN do

				allArgs[ i ] = boundArgs[ i ]

			end

			local unboundArgsN = select( '#', ... )

			for i = 1, unboundArgsN do

				allArgs[ boundArgsN + i ] = select( i, ... )

			end

			return f( unpack( allArgs, 1, boundArgsN + unboundArgsN ) )

		end

	else

		-- for 9 or fewer bound args, use up values instead of calling unpack

		local boundArg1, boundArg2, boundArg3, boundArg4, boundArg5, boundArg6, boundArg7, boundArg8, boundArg9 = ...

		if boundArgsN == 1 then

			return function( ... )

				return f( boundArg1, ... )

			end

		elseif boundArgsN == 2 then

			return function( ... )

				return f( boundArg1, boundArg2, ... )

			end

		elseif boundArgsN == 3 then

			return function( ... )

				return f( boundArg1, boundArg2, boundArg3, ... )

			end

		elseif boundArgsN == 4 then

			return function( ... )

				return f( boundArg1, boundArg2, boundArg3, boundArg4, ... )

			end

		elseif boundArgsN == 5 then

			return function( ... )

				return f( boundArg1, boundArg2, boundArg3, boundArg4, boundArg5, ... )

			end

		elseif boundArgsN == 6 then

			return function( ... )

				return f( boundArg1, boundArg2, boundArg3, boundArg4, boundArg5, boundArg6, ... )

			end

		elseif boundArgsN == 7 then

			return function( ... )

				return f( boundArg1, boundArg2, boundArg3, boundArg4, boundArg5, boundArg6, boundArg7, ... )

			end

		elseif boundArgsN == 8 then

			return function( ... )

				return f( boundArg1, boundArg2, boundArg3, boundArg4, boundArg5, boundArg6, boundArg7, boundArg8, ... )

			end

		else

			return function( ... )

				return f( boundArg1, boundArg2, boundArg3, boundArg4, boundArg5, boundArg6, boundArg7, boundArg8, boundArg9, ... )

			end

		end

	end

end

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

KSFunctionUtils.partial = KSFunctionUtils.bindAllToFront

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

local fieldExtractor2selector = setmetatable( { }, mtFullyWeak )

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

KSFunctionUtils.fieldExtractorFn = memoize( function( fieldSelector )

	assert( fieldSelector ~= nil, "nil field selectors are not allowed" )
	
	local fn = function( t ) return t[ fieldSelector ] end
	
	fieldExtractor2selector[ fn ] = fieldSelector
	
	return fn

end )

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

function KSFunctionUtils.isFieldExtractorFn( fn )

	local selector = fieldExtractor2selector[ fn ]
	
	if selector ~= nil then
		return true, selector
	else
		return false
	end

end

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

local singletonsKeyMT = {
	__metatable = '< Singleton Key Metatable >',
	__tostring = function( value )
		return "Lazy Singletons"
	end,
}

local singletonsKey = setmetatable( {}, singletonsKeyMT )

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

function KSFunctionUtils.makeSingletonMemberFn( fn, name, optDepth )

	if type( fn ) ~= 'function' then
		error( "KSFunctionUtils.makeSingletonMemberFn: fn is not a function", optDepth or 2 )
	end

	if not name then
		error( "KSFunctionUtils.makeSingletonMemberFn: missing name", optDepth or 2 )
	end

	return function( self )
	
		local selfSingletons = rawget( self, singletonsKey )
		if not selfSingletons then
			selfSingletons = {}
			rawset( self, singletonsKey, selfSingletons )
		end
		
		local value = selfSingletons[ name ]
		
		if value == nil then
		
			value = fn( self )
			
			if value == nil then
				value = kNil
			end
			
			selfSingletons[ name ] = value
		
			return value
		
		elseif value == kNil then
			return nil
		else
			return value
		end
	
	end

end

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

function KSFunctionUtils.makeSingletonMemberFnE( fn, name )

	local rawSingletonAccessor = KSFunctionUtils.makeSingletonMemberFn( fn, name, 3 )
	
	return function( self )
		
		local rawValue = assert( rawSingletonAccessor( self ),
			"singleton didn't create a value ... surprise!" )
		
		return rawValue:relayE()
	
	end

end

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

function KSFunctionUtils.makeSingletonMemberFnB( fn, name )

	local rawSingletonAccessor = KSFunctionUtils.makeSingletonMemberFn( fn, name, 3 )
	
	return function( self )
		
		local rawValue = assert( rawSingletonAccessor( self ),
			"singleton didn't create a value ... surprise!" )
		
		return rawValue:relayB()
	
	end

end

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

return KSFunctionUtils
