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

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

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

local T = { }

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

local assert = assert
local collectgarbage = collectgarbage
local select = select

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

local Rx = require 'Rx'

local KSTestUtils = require 'KSTestUtils'

local KSReactive = require 'KSReactive'

local testResults = KSTestUtils.testResults

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

local KSFunctionUtils = require 'KSFunctionUtils'

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

-- Test that the memoized function returns the same values as the original
-- function where the values are supplied by the additional parameters.

local function testMemoizeWithFunction( fn, ... )

	local memoizeFn = assert( KSFunctionUtils.memoizeFn, "No memoizeFn function provided" )

	local n = select( '#', ... )
	
	local mfn = memoizeFn( fn )
	
	for i = 1, n do
		local x = select( i, ... )
		local fx = fn( x )
		local mfx = mfn( x )
		assert( fx == mfx, "Values should be equal" )
	end
	
	-- Test again. This should be testing against the cache
	-- though we obviously have no way to test this here.
	
	for i = 1, n do
		local x = select( i, ... )
		local fx = fn( x )
		local mfx = mfn( x )
		assert( fx == mfx, "Values should be equal" )
	end

end

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

function T.testMemoize()

	local function factorial( n )
		local prod = 1
		for i = 1, n do
			prod = prod * i
		end
		return prod
	end
	
	testMemoizeWithFunction( factorial, 0, 1, 2, 3, 4, 5, 6 )

end

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

function T.testMemoizeCaches()

	-- Our "function" will return new tables. Since it returns new values for
	-- each call even if the parameters are the same, it isn't really a function.
	
	local memoizeFn = assert( KSFunctionUtils.memoizeFn, "No memoizeFn function provided" )

	local count = 0

	local function fn()
		count = count + 1
		return { }
	end
	
	local mfn = memoizeFn( fn )
	
	local NaN = 0 / 0
	
	assert( NaN ~= NaN, "Failed to generate NaN" );
	
	assert( mfn( 1 ) == mfn( 1 ), "Repeated calls should generate a single value" )
	assert( mfn( 1 ) ~= mfn( 2 ), "Different parameters should generate different values" )
	assert( mfn( 1 ) == mfn( 1 ), "Repeated calls should still generate the same value" )
	assert( mfn( nil ) == mfn( nil ), "Caching should work for nil" )
	assert( mfn( NaN ) == mfn( NaN ), "Caching should work for NaN" )
	
	assert( count == 4, "We should have invoked the function four times" )

end

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

function T.testMemoizeCacheWeak()

	-- Our "function" will return new tables. Since it returns new values for
	-- each call even if the parameters are the same, it isn't really a function.
	
	local memoizeFn = assert( KSFunctionUtils.memoizeFn, "No memoizeFn function provided" )

	local lastValue = setmetatable( { }, { __mode = 'kv' } )
	
	local count = 0
	
	local function fn()
		count = count + 1
		local result = { }
		lastValue[ 1 ] = result
		return result
	end
	
	local mfn = memoizeFn( fn )
	
	local x = mfn( 0 )
	
	assert( lastValue[ 1 ] == x, "We should have cached x" )
	
	assert( count == 1, "We should have called fn once." )
	
	x = nil
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( lastValue[ 1 ] == nil, "We should have collected the value" )
	
	mfn( 0 ) -- Calculate again
	
	assert( count == 2, "We should have called fn twice." )

end

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

function T.testMemoizeNilResult()

	local memoizeFn = assert( KSFunctionUtils.memoizeFn, "No memoizeFn function provided" )

	local count = 0
	
	local function fn()
		count = count + 1
		return nil
	end
	
	local mfn = memoizeFn( fn )
	
	assert( mfn( 1 ) == nil, "mfn( 1 ) value should be nil" )
	assert( mfn( 2 ) == nil, "mfn( 2 ) value should be nil" )
	
	mfn( 1 ) -- call it again
	
	assert( count == 2, "We should have invoked the function twice." )

end

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

function T.testNop()

	local nop = assert( KSFunctionUtils.nop, "No nop function provided." )

	assert( testResults( nop )( ), "nop should return no values" )

	assert( testResults( nop, 1, 2, 3 )( ), "nop with parameters should return no values" )

end

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

function T.testIdentity()

	local identity = assert( KSFunctionUtils.identity, "No identity function provided." )
	
	assert( testResults( identity )( ), "Identity function of nothing should return nothing." )

	assert( testResults( identity, nil )( nil ), "Identity function of nil should return nil." )

	assert( testResults( identity, 1 )( 1 ), "Identity function of 1 should return 1." )

	assert( testResults( identity, 1, 2, 3 )( 1, 2, 3 ), "Identity function of 1, 2, 3 should return 1, 2, 3." )

end

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

function T.testConstantFn()

	local constantFn = assert( KSFunctionUtils.constantFn, "No constantFn function provided." )
	
	local fx = constantFn( 'x' )
	
	local fy = constantFn( 'y' )
	
	local fxyz = constantFn( 'x', 'y', 'z' )
	
	local fnone = constantFn()
	
	local fnil = constantFn( nil )
	
	assert( testResults( fx )( 'x' ), "constantFn( 'x' ) should be a function that returns 'x'" )
	
	assert( testResults( fx, 1 )( 'x' ), "constantFn( 'x' ) should be a function that returns 'x' despite arguments" )
	
	assert( testResults( fy )( 'y' ), "constantFn( 'y' ) should be a function that returns 'y'" )
	
	assert( testResults( fxyz )( 'x', 'y', 'z' ), "constantFn( 'x', 'y', 'z' ) should be a function that returns 'x', 'y', 'z'" )
	
	assert( testResults( fnone )( ), "constantFn() should be a function that returns nothing." )

	assert( testResults( fnil )( nil ), "constantFn( nil ) should be a function that returns nil." )

end


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

function T.testBindAllToFront()

	local bindAllToFront = assert( KSFunctionUtils.bindAllToFront, "No bindAllToFront function provided." )

	local function returnSame( ... )

		return ...

	end

	local function copyArrayWithNilAtN( arr, n )

		local copy = {}

		for i = 1, #arr do

			if i ~= n then

				copy[ i ] = arr[ i ]

			end

		end

		return copy

	end

	local function returnValuesMatchExpected( desc, expected, expectedN, f, ... )

		local ret = { f( ... ) }

		for i = 1, expectedN do

			if ret[ i ] ~= expected[ i ] then

				return false, string.format( "%s FAILED: return value %s is %s which does match %s expected %s",
												desc, i, ret[ i ], expected[ i ] )

			end
	
		end

		return true

	end
	
	local values = { 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }

	local argArraysToDescs = {}

	argArraysToDescs[ values ] = "all values"

	for nilIndex = 1, #values do

		argArraysToDescs[ copyArrayWithNilAtN( values, nilIndex ) ] =
			string.format( "vals with nil at %s", nilIndex )

	end

	for argArray, desc in pairs( argArraysToDescs ) do

		for i = 0, #values - 1 do

			local testDesc = string.format( "binding %s items from '%s' to returnSame is the same not binding any arggs to returnSame", i, desc )

			assert( type( argArray ) == "table", string.format( "argArray is %s and desc is %s which is wrong", argArray, desc ) )

			local boundReturnSame = 
					bindAllToFront( returnSame, unpack( argArray, 1, i ) ) 

			assert( returnValuesMatchExpected( 
						testDesc, argArray, #values,
						boundReturnSame, 
							unpack( argArray, i + 1, #values ) ) )

		end

	end

end

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

function T.testPartial()

	local bindAllToFront = assert( KSFunctionUtils.bindAllToFront, "No bindAllToFront function provided." )

	local partial = assert( KSFunctionUtils.bindAllToFront, "No partial function provided." )

	assert( partial == bindAllToFront )

end
--------------------------------------------------------------------------------

function T.testFieldExtractors()

	local fn = assert( KSFunctionUtils.fieldExtractorFn( 'foo' ), "Failed to construct field extractor" )
	
	assert( fn{ foo = 3, baz = 4 } == 3, "fieldExtractor retrieved the wrong value" )
	
	local isExtractor, selector = KSFunctionUtils.isFieldExtractorFn( fn )
	
	assert( isExtractor, "isFieldExtractorFn failed to recognize function as a field selector" )

	assert( selector == 'foo', "isFieldExtractorFn returned the wrong field selector" )
	
	local fn2 = KSFunctionUtils.fieldExtractorFn( 'baz' ) -- make another extractor
	
	local isExtractor2, selector2 = KSFunctionUtils.isFieldExtractorFn( fn2 )

	assert( isExtractor2, "isFieldExtractorFn failed to recognize second function as a field selector" )

	assert( selector2 == 'baz', "isFieldExtractorFn returned the wrong field selector (expected 'baz')" )
	
	local isExtractor3, selector3 = KSFunctionUtils.isFieldExtractorFn( fn )
	
	assert( isExtractor3, "isFieldExtractorFn failed to recognize function as a field selector on second access" )

	assert( selector3 == 'foo', "isFieldExtractorFn returned the wrong field selector (expected 'foo')" )
	
	local weak = setmetatable( { }, { __mode = 'kv' } )
	
	weak[ 1 ] = fn
	weak[ 2 ] = fn2
	
	fn = nil
	fn2 = nil
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( weak[ 1 ] == nil, "Failed to collect first field extractor." )
	assert( weak[ 2 ] == nil, "Failed to collect second field extractor." )
	
	assert( not KSFunctionUtils.isFieldExtractorFn( true ), "True should not be reported as a field extractor." )
	assert( not KSFunctionUtils.isFieldExtractorFn( nil ), "Nil should not be reported as a field extractor." )
	assert( not KSFunctionUtils.isFieldExtractorFn( function() end ),
				"Arbitrary functions should not be reported as field extractors." )
	
end

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

function T.testMakeSingletonMember()

	local hasBeenCalledForObject = {}
	local counter = 0

	local function foo( self )
	
		assert( not hasBeenCalledForObject[ self ] )
		hasBeenCalledForObject[ self ] = true
		
		counter = counter + 1
		return counter
	
	end

	local fooClass = Rx.Class 'foo' {
		blah = KSFunctionUtils.makeSingletonMemberFn( foo, 'blah' )
	}
	
	local foo1 = fooClass()
	local foo2 = fooClass()
	
	assert( counter == 0, "didn't create singletons before first request" )
	
	assert( foo1:blah() == 1, "created singleton" )
	assert( foo1:blah() == 1, "didn't create another singleton for first object" )
	assert( counter == 1, "didn't call singleton creation func an extra time" )
	
	assert( foo2:blah() == 2, "created new singleton for new object" )
	assert( foo2:blah() == 2, "didn't create second singleton for new object" )
	assert( counter == 2, "didn't call singleton creation func an extra time (2)" )

	-- test invalid uses of makeSingletonMemberFn
	local success = pcall( KSFunctionUtils.makeSingletonMemberFn, 1, 'blah' )
	assert( not success, "makeSingletonMemberFn shouldn't allow a non-function as first param" )

	local function fooNil()
		return nil
	end

	success = pcall( KSFunctionUtils.makeSingletonMemberFn, fooNil )
	assert( not success, "makeSingletonMemberFn requires second parameter" )

	local fooNilClass = Rx.Class 'fooNil' {
		blah = KSFunctionUtils.makeSingletonMemberFn( fooNil, 'blah' )
	}

	local fooNil1 = fooNilClass();
	assert( fooNil1:blah() ~= nil, "makeSingletonMemberFn returns kNil on 1st call with function returning nil" )
	assert( fooNil1:blah() == nil, "makeSingletonMemberFn returns nil on 2nd call with function returning nil" )

	-- exercise singletonsKeyMT.__tostring
	success = false
	for k in pairs( foo1 ) do
		if tostring(k) == "Lazy Singletons" then
			success = true;
		end
	end
	assert(success, "able to toString 'Lazy SIngletons' key in the class")

end

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

function T.testMakeSingletonMemberE( test )

	local fooSourceForObject = {}
	local counter = 0

	local function fooE( self )
	
		assert( not fooSourceForObject[ self ] )
		fooSourceForObject[ self ] = KSReactive.sourceE()
		
		counter = counter + 1
		return fooSourceForObject[ self ]
	
	end

	local fooClass = Rx.Class 'foo' {
		blahE = KSFunctionUtils.makeSingletonMemberFnE( fooE, 'blah' )
	}
	
	local foo1 = fooClass()
	
	assert( counter == 0, "didn't create singletons before first request" )

	local foo1BlahE1 = foo1:blahE()
	local foo1BlahE2 = foo1:blahE()
	assert( foo1BlahE1 ~= foo1BlahE2, "didn't create independent relays" )
	
	assert( counter == 1, "didn't call singleton creation func an extra time" )

	assert( fooSourceForObject[ foo1 ], "created a source object for foo1" )
	
	local didCallBlah1, didCallBlah2
	foo1BlahE1:ownedOnDataE( test, function( value ) didCallBlah1 = value end )
	foo1BlahE2:ownedOnDataE( test, function( value ) didCallBlah2 = value end )

	fooSourceForObject[ foo1 ]:post( 42 )
	
	assert( didCallBlah1 == 42, "didn't call relay 1" )
	assert( didCallBlah2 == 42, "didn't call relay 2" )

end

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

function T.testMakeSingletonMemberB( test )

	local fooSourceForObject = {}
	local counter = 0

	local function fooB( self )
	
		assert( not fooSourceForObject[ self ] )
		counter = counter + 1

		fooSourceForObject[ self ] = KSReactive.variableB( counter )
		return fooSourceForObject[ self ]
	
	end

	local fooClass = Rx.Class 'foo' {
		blahB = KSFunctionUtils.makeSingletonMemberFnB( fooB, 'blah' )
	}
	
	local foo1 = fooClass()
	
	assert( counter == 0, "didn't create singletons before first request" )

	local foo1BlahB1 = foo1:blahB()
	local foo1BlahB2 = foo1:blahB()
	assert( foo1BlahB1 ~= foo1BlahB2, "didn't create independent relays" )
	
	assert( counter == 1, "didn't call singleton creation func an extra time" )

	assert( fooSourceForObject[ foo1 ], "created a source object for foo1" )
	
	assert( foo1BlahB1:valueNow() == 1, "seeded with correct value" )
	assert( foo1BlahB2:valueNow() == 1, "seeded with correct value" )
	
	local didCallBlah1, didCallBlah2
	foo1BlahB1:ownedOnDataE( test, function( value ) didCallBlah1 = value end )
	foo1BlahB2:ownedOnDataE( test, function( value ) didCallBlah2 = value end )

	fooSourceForObject[ foo1 ]:set( 42 )
	
	assert( didCallBlah1 == 42, "didn't call relay 1" )
	assert( didCallBlah2 == 42, "didn't call relay 2" )

end

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

return T
