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

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

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

	-- RxLua
local Rx = require 'Rx'

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


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

local check = assert( WFArguments.check )


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

local WFArgumentsTest = {}

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

local function mustFail( ... )

	local success, _ = pcall( ... )

	if success then
		error( "Expected to fail: " .. debug.traceback() )
	else
		-- debug.print( "Test failed with message:", _ )
	end

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

function WFArgumentsTest.testArgumentChecking()

	local function takeNumberAndString( x, y )
		check( 'number', 'string' )
	end
	
	takeNumberAndString( 4, 'foo' )
	
	mustFail( takeNumberAndString, 'baz', 'foo' )
	mustFail( takeNumberAndString, { }, 'foo' )
	mustFail( takeNumberAndString, nil, 'foo' )
	mustFail( takeNumberAndString, 4, 3 )
	mustFail( takeNumberAndString, 4, nil )
	mustFail( takeNumberAndString, 4, { } )
	mustFail( takeNumberAndString, 4 )
	
end

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

function WFArgumentsTest.testSumArguments()

	local function takeOptNumberOrTableAndStringOrTable( x, y )
		check( '? number | table', '  string|table  ' )
		check( '?number|table', 'string | table' )
	end
	
	takeOptNumberOrTableAndStringOrTable( 4, 'foo' )
	takeOptNumberOrTableAndStringOrTable( nil, 'foo' )
	takeOptNumberOrTableAndStringOrTable( { }, 'foo' )
	takeOptNumberOrTableAndStringOrTable( 4, { } )
	takeOptNumberOrTableAndStringOrTable( nil, { } )
	takeOptNumberOrTableAndStringOrTable( { }, { } )
	
	mustFail( takeOptNumberOrTableAndStringOrTable, 'baz', 'foo' )
	mustFail( takeOptNumberOrTableAndStringOrTable, 4, 3 )
	mustFail( takeOptNumberOrTableAndStringOrTable, 4, nil )
	mustFail( takeOptNumberOrTableAndStringOrTable, 4 )
	
end
	
--------------------------------------------------------------------------------

function WFArgumentsTest.testOptTrailing()

	local function testOptNumbers( x, y )
		check( '?number', '?number' )
	end
	
	testOptNumbers( 4, 5 )
	testOptNumbers( nil, 5 )
	testOptNumbers( 4, nil )
	testOptNumbers( nil, nil )
	testOptNumbers( 4 )
	testOptNumbers( nil )
	testOptNumbers( )
	
	mustFail( testOptNumbers, 'baz', 'foo' )
	
end

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

function WFArgumentsTest.testOptionalArguments()

	local function takeOptNumberAndString( x, y )
		check( '?number', 'string' )
		check( '? number', 'string' )
	end
	
	takeOptNumberAndString( 4, 'foo' )
	takeOptNumberAndString( nil, 'foo' )
	
	mustFail( takeOptNumberAndString, 'baz', 'foo' )
	mustFail( takeOptNumberAndString, { }, 'foo' )
	mustFail( takeOptNumberAndString, 4, 3 )
	mustFail( takeOptNumberAndString, 4, nil )
	mustFail( takeOptNumberAndString, 4, { } )
	mustFail( takeOptNumberAndString, 4 )
	
end
	
--------------------------------------------------------------------------------

function WFArgumentsTest.testCustomChecker()

	WFArguments.registerChecker( 'port', function( p )
		return type( p ) == 'number' and 0 < p and p < 1000
	end )
	
	local aSocket = setmetatable( { }, { __type = 'socket' } )
	
	local function testSocketPortOptString( socket, port, str )
		check( 'socket', 'port', '?string' )
	end
	
	testSocketPortOptString( aSocket, 500, 'hello' )
	testSocketPortOptString( aSocket, 500 )
	
	mustFail( testSocketPortOptString, { }, 500, 'string' )
	mustFail( testSocketPortOptString, aSocket, -1, 'string' )
	mustFail( testSocketPortOptString, aSocket, 500, { } )

	WFArguments.unregisterChecker( 'port' )
		
end

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

function WFArgumentsTest.testTableChecker()

	local checker = {}
	
	WFArguments.registerChecker( 'baz', checker )
	
	local x = {}
	checker[ x ] = true
	
	local function testBazOptString( val, str )
		check( 'baz', '?string' )
	end
	
	testBazOptString( x, 'hello' )
	testBazOptString( x )
	
	mustFail( testBazOptString, {}, 'string' )
	mustFail( testBazOptString, 'baz', 'string' )
	mustFail( testBazOptString, true, 'string' )
	mustFail( testBazOptString, x, { } )
	
	local function testBazOrString( val )
		check( 'baz | string' )
	end
	
	testBazOrString( x )
	testBazOrString( 'foo' )
	
	mustFail( testBazOrString, { } )
	mustFail( testBazOrString, nil )

	WFArguments.unregisterChecker( 'baz' )
	
end

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

function WFArgumentsTest.testCallableChecker()

	local function testCallable( val, str )
		check( 'callable' )
	end
	
	testCallable( function() end )
	testCallable( setmetatable( { }, { __call = function() end, __metatable = 'private' } ) )
	
	mustFail( testCallable )
	mustFail( testCallable, nil )
	mustFail( testCallable, true )
	mustFail( testCallable, 3 )
	mustFail( testCallable, 'foo' )
	mustFail( testCallable, { } )
		
end

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

function WFArgumentsTest.testUnderscore()

	local function testUnderscoreNumber( val, str )
		check( '_', 'number' )
	end
	
	testUnderscoreNumber( 1, 2 )
	testUnderscoreNumber( 'foo', 2 )
	testUnderscoreNumber( true, 2 )
	testUnderscoreNumber( {}, 2 )
	testUnderscoreNumber( nil, 2 )
	
	mustFail( testUnderscoreNumber, 1, 'foo' )
	mustFail( testUnderscoreNumber, 'foo', 'foo' )
	mustFail( testUnderscoreNumber, true, 'foo' )
	mustFail( testUnderscoreNumber, { }, 'foo' )
	mustFail( testUnderscoreNumber, nil, 'foo' )
		
end

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

function WFArgumentsTest.testRegisterCheckerOnlyOnce()

	local registerChecker = assert( WFArguments.registerChecker )
	
	registerChecker( 'foo', function() end )
	
	mustFail( registerChecker, 'foo', function() end )

	WFArguments.unregisterChecker( 'foo' )

end

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

function WFArgumentsTest.testRegisterCheckerRejectsUnhandledTypes()

	local registerChecker = assert( WFArguments.registerChecker )
	
	registerChecker( 'skralk', function() end )
	
	mustFail( registerChecker, 'skralk_boolean', true )
	mustFail( registerChecker, 'skralk_string', 'foo' )
	mustFail( registerChecker, 'skralk_nil', nil )
	mustFail( registerChecker, 'skralk_number', 7 )

	WFArguments.unregisterChecker( 'skralk' )

end

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

function WFArgumentsTest.testCheckTable()

	local checkTable = assert( WFArguments.checkTable )
	
	local template = { foo = 'number', baz = 'string | boolean', bleen = '?number', blech = '!nil' }
	
	local function runTableCheck( tt )
		checkTable( template, tt )
	end
	
	local function failTableCheckNonStringKey( tt )
		checkTable( { [ 3 ] = 'string' }, tt )
	end
	
	local function failTableCheckNonStringType( tt )
		checkTable( { foo = 7 }, tt )
	end
	
	runTableCheck( { foo = 4, baz = false, blech = { } } )
	runTableCheck( { foo = 4, baz = "Hello!", bleen = 7, blech = { } } )
	
	mustFail( runTableCheck, { foo = 5, baz = false } )
	mustFail( runTableCheck, { foo = "Text", baz = false, blech = { } } )
	mustFail( runTableCheck, { foo = 4, baz = false, bleen = "Howdy", blech = { } } )
	
	mustFail( runTableCheck, "Not a table" )
	
	mustFail( checkTable, "Not a table", { foo = 4, baz = false, blech = { } } )
	mustFail( failTableCheckNonStringKey, { [ 3 ] = 'Hello' } ) -- only support string keys
	mustFail( failTableCheckNonStringType, { foo = 7 } ) -- only support strings as check values (at least for now)

end

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

function WFArgumentsTest.testCheckNonNilChecker()

	local function testNonNil( val )
		check( '!nil' )
	end
	
	testNonNil( 'foo' )
	testNonNil( 3 )
	
	mustFail( testNonNil )
	mustFail( testNonNil, nil )

end

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

function WFArgumentsTest.testCheckNonEmptyStringChecker()

	local function testNonEmptyString( val )
		check( '+string' )
	end
	
	testNonEmptyString( 'foo' )
	
	mustFail( testNonEmptyString )
	mustFail( testNonEmptyString, nil )
	mustFail( testNonEmptyString, '' )
	mustFail( testNonEmptyString, 3 )

end

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

function WFArgumentsTest.testCheckTables()

	local function testTables( arg1, arg2 )
		check( { foo = 'string', baz = 'number' }, { bleen = 'number' } )
	end
	
	testTables( { foo = 'Hello', baz = 4 }, { bleen = 7 } )
	
	mustFail( testTables, { foo = 'Hello', baz = false }, { bleen = 7 } )
	mustFail( testTables, { foo = 'Hello', baz = 4 }, { bleen = false } )
	mustFail( testTables, { foo = 'Hello', baz = 4 }, { } )
	mustFail( testTables, { foo = 'Hello', baz = 4 }, 'not a table' )
	mustFail( testTables, { foo = 'Hello', baz = 4 } )
	mustFail( testTables, 3.1415, { bleen = 7 } )

end

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

function WFArgumentsTest.testCheckRxClass()
	
	local myTestClass = Rx.Class 'WFArguments_TestClass' {}
	
	local myOtherClass = Rx.Class 'WFArguments_OtherClass' {}
	
	local myInstance = myTestClass()
	local otherInstance = myOtherClass()
	
	local function checkMyClass( obj )
		check( 'WFArguments_TestClass' )
	end

	local function checkOptMyClass( obj )
		check( '?WFArguments_TestClass' )
	end
	
	checkMyClass( myInstance )
	mustFail( checkMyClass, otherInstance )

	checkOptMyClass( myInstance )
	checkOptMyClass( nil )
	mustFail( checkOptMyClass, otherInstance )

end

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

return WFArgumentsTest
