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

WFRequestDispatcherTest.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'
local Observable = Rx.Observable

	-- WF.core.diagnostic_log
local WFDiagnosticLog = require 'WFDiagnosticLog'

	-- WF.core.lua
local KSData = require 'KSData'

	-- WF.core.reactive
local KSPromises = require 'KSPromises'
local KSReactive = require 'KSReactive'

	-- WF.core.rxlua
local WFRxScheduler = require 'WFRxScheduler'

	-- WF.core.self_test
local WFAsyncTestCase = require 'WFAsyncTestCase'
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.core.threading
local WFWorkerThread = require 'WFWorkerThread'

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSTestUtils = require 'KSTestUtils'

local WFPlatformInfo = require 'WFPlatformInfo'

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

local WFRequestDispatcherTest = {}

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

-- FIXME [scouten 2011-12-16]: Convert to WFAsyncTestCase. Note that this
-- requires implementation of the planned :onErrorE handling.

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

local requestTestHandlerFailed = nil

local requestTestHandler = WFWorkerThread( 'WFRequestTestHandler.lua' )
local simpleWorkerThread = WFWorkerThread( 'WFRequestTestHandler_simple.lua' )

local currentTest = nil
local expectedTestResults = nil

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

function WFRequestDispatcherTest.setUp()

	if requestTestHandlerFailed then
		error( requestTestHandlerFailed )
	end

end

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

function WFRequestDispatcherTest.tearDownClass( coveragePath )

	if requestTestHandler and coveragePath then

		requestTestHandler:requestP( 'DEBUG_dumpCoverageP', coveragePath )
			:runUntilDone()
		
	end

	if simpleWorkerThread and coveragePath then

		simpleWorkerThread:requestP( 'DEBUG_dumpCoverageP', coveragePath )
			:runUntilDone()
		
	end

end


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

-- @mark :request testing (RxLua support)

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

function WFRequestDispatcherTest.rxTestRequest( rxTest )

	rxTest:expectOnlyCompleted( requestTestHandler:request( 'observableEmpty' ) )

end

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

function WFRequestDispatcherTest.rxTestRequest_echo( rxTest )

	local id = KSIdGenerator.uuid()

	local o = rxTest:observe( requestTestHandler:request( 'observableDelayAndWatchForCancel', 1, id ) )

	o:expectData( "OBSERVABLE FINISHED:" .. id )
	o:expectCompleted()

end

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

function WFRequestDispatcherTest.rxTestRequest_sequence( rxTest )

	local o = rxTest:observe( requestTestHandler:request( 'observableTimedSequence', 5 ) )

	o:expectData( 1 )
	o:expectData( 2 )
	o:expectData( 3 )
	o:expectData( 4 )
	o:expectData( 5 )
	o:expectCompleted()

end

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

function WFRequestDispatcherTest.rxTestRequest_delay( rxTest )

	local o = rxTest:observe( requestTestHandler:request( 'observableEcho', 1, 2, 3 ) )

	o:expectData( 3, 1, 2, 3 ) -- echo inserts a count at front
	o:expectCompleted()

end

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

function WFRequestDispatcherTest.rxTestRequest_dataThenError( rxTest )

	local o = rxTest:observe( requestTestHandler:request( 'observableSendValueThenError' ) )
	
	o:expectData( 1 )
	o:expectError( '2' )
		
end

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

function WFRequestDispatcherTest.rxTestRequest_cancel( rxTest )

	local id = KSIdGenerator.uuid()

	local req = requestTestHandler:request( 'observableDelayAndWatchForCancel', 1, id )

	local gotData
	local unsubscriber = req:subscribe( function() gotData = true end )
	
	local o = rxTest:observe( Observable.timer( .25, WFRxScheduler.sharedScheduler() ) )
	o:expectData( 0 )
	o:expectCompleted()
	
	debug.print( "Client side cancel" )
	unsubscriber:dispose()
	
	o = rxTest:observe( Observable.timer( 1, WFRxScheduler.sharedScheduler()) )
	o:expectData( 0 )
	o:expectCompleted()
	
	o = rxTest:observe( requestTestHandler:request( 'didPreviousDelayGetCanceled' ) )
	
	o:expectData( id )
	o:expectCompleted()
		
end


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

-- @mark :requestP testing

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

local function checkTestResultsAndNotify( test, ... )

	local n = select( '#', ... )
	local expectedResults = { ... }

	return function( ... )

		if pcall( function( ... )
				KSTestUtils.assertMatchArgs( unpack( expectedResults, 1, n ) )( ... )
			end, ... ) == true
		then
			test:notifyTestDidSucceed()
		else
			test:notifyTestDidFail()
		end

	end

end

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

function WFRequestDispatcherTest.testRequestP( test )

	local p = requestTestHandler:requestP( 'echo', 1, 2, 3 )

	p:whenResolvedDo( checkTestResultsAndNotify( test, 'data', 3, 1, 2, 3 ) ):runUntilDone()
	
	test:waitForTestToFinish( 2 )

end

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

function WFRequestDispatcherTest.testRequestP_simple( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return simpleWorkerThread:requestP( 'echo', 1, 2, 3 )
		
		end )
		
		:continueWhenValueMatches( 'data', 3, 1, 2, 3 )

end

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

function WFRequestDispatcherTest.testRequestP_emptyResponse( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return requestTestHandler:requestP( 'returnNothing' )
		
		end )
		
		:continueWhenValueMatches( 'data', true )

end

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

function WFRequestDispatcherTest.testRequestP_target( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return requestTestHandler:requestP( 'doSomethingWithTargetObject', 41 )
		
		end )
		
		:continueWhenValueMatches( 'data', "did something", 83 )

end

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

function WFRequestDispatcherTest.testRequestPTransitWithNil( test )

	local p = requestTestHandler:requestP( 'echo', 1, nil, 3 )

	p:whenResolvedDo( checkTestResultsAndNotify( test, 'data', 3, 1, nil, 3 ) ):runUntilDone()
	
	test:waitForTestToFinish( 2 )

end

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

function WFRequestDispatcherTest.testRequestPWithData( test )

	local p = requestTestHandler:requestP( 'echo', "The data vaue" )

	p:whenResolvedDo( checkTestResultsAndNotify( test, 'data', 1, "The data vaue" ) ):runUntilDone()
	
	test:waitForTestToFinish( 2 )

end

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

function WFRequestDispatcherTest.testRequestPWithError( test )

	local p = requestTestHandler:requestP( 'sendError', "Expecting this error" )

	p:whenResolvedDo( checkTestResultsAndNotify( test, 'error',
												   "Expecting this error" ) )
		:runUntilDone()
	
	test:waitForTestToFinish( 2 )

end

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

function WFRequestDispatcherTest.testRequestP_errorNoSuchHandler( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
			
			return requestTestHandler:requestP( 'noSuchHandler' )
		
		end )
		:continueWhenValueMatches( 'error', ':ignore:' )
	
end

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

function WFRequestDispatcherTest.testRequestPWithDeferredClose( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return requestTestHandler:requestP( 'sendDataWithDeferredClose', "Some data" )
				:mapDataP( function( _, data )
				
					return 'data', KSData.isData( data ), tostring( data )
				
				end )
		
		end )
		:continueWhenValueMatches( 'data', true, "Some data" )
		
		:waitFor( function()
			
			return requestTestHandler:requestP( 'didClosePreviousData' )
		
		end )
		:continueWhenValueMatches( 'data', true )

end

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

function WFRequestDispatcherTest.testRequestPWithDeferredClose_nil( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return requestTestHandler:requestP( 'deferredCloseThis', nil )
		
		end )
		:continueWhenValueMatches( 'data', nil )

end

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

function WFRequestDispatcherTest.testRequestPWithDeferredClose_noCloseMethod( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return requestTestHandler:requestP( 'deferredCloseThis', {} )
		
		end )
		:continueWhenValueMatches( 'error', ':ignore:' )

end

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

function WFRequestDispatcherTest.testRequestPWithDeferredClose_notAnObject( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return requestTestHandler:requestP( 'deferredCloseThis', 42 )
		
		end )
		:continueWhenValueMatches( 'error', ':ignore:' )

end

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

-- FIXME kcarlson 2013-07-18 if this test is run when requestTestHandler isn't
-- ready it will not cancel the requestP, need to write a test case that
-- validates the correct behavior for canceling WFWorkerThreads that aren't
-- loaded yet ( is it correct to do nothing? is it invalid to requestP before
-- thread is ready?

--function WFRequestDispatcherTest.testRequestPCancelation( test )
--
--	local p = requestTestHandler:requestP( 'echo', "Hello!" )
--
--	local canceledB = KSReactive.variableB( false )
--
--	p = p:observeP( function( ... ) canceledB:set( select( '#', ... ) == 0 ) end )
--
--	local a1 = p:whenResolvedDo( function() end ):runUntilDone()
--
--	a1:close()
--
--	WFAsyncTestCase( test )
--
--		:waitFor( canceledB )
--		:continueWhenValueMatches( true )
--
--end

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

function WFRequestDispatcherTest.testRequestPCancelation( test )

	local canceledB = KSReactive.variableB( false )

	WFAsyncTestCase( test )

		:waitFor( requestTestHandler:threadIsReadyP() )
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			local p = requestTestHandler:requestP( 'echo', "Hello!" )

			p = p:observeP( function( ... ) canceledB:set(
					select( '#', ... ) == 0 )
				end )
		
			local a1 = p:whenResolvedDo( function() end ):runUntilDone()
		
			a1:close()
			
			return canceledB
			
		end )
		:continueWhenValueMatches( true )
	
end

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

function WFRequestDispatcherTest.testRequestPWithServerSidePromise( test )

	WFAsyncTestCase( test, 4 )
	
		:waitFor( function()	-- step 2
		
			return requestTestHandler:requestP( 'echoDelay', 1, "Hello!" )
	
		end )
		
		:continueWhenValueMatches( 'data', "Hello!" )
		
end

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

function WFRequestDispatcherTest.testRequestPCancelationWithDelay( test )

	local echoDelayP = requestTestHandler:requestP( 'echoDelay', 2, "Hello!" )
	
	local a1 = echoDelayP:whenResolvedDo( function() end ):runUntilDone()
	
	WFAsyncTestCase( test, 4 )
	
		:waitFor( .5 )		 	-- step 1
		
		:waitFor( function()	-- step 2
		
			a1:close()
			
			return echoDelayP
		
		end )
		
		:continueWhenValueMatches() -- no results on cancel
		
end


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

-- @mark :requestE testing

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

local function testCallback( ... )

	local testResults = { ... }
	
	local success = true
	
	if testResults[ 1 ] ~= expectedTestResults.n then
		debug.print( "echo didn't respond with correct # of results" )
		success = false
	end
	
	for i = 1, expectedTestResults.n do
	
		if( testResults[ i + 1 ] ~= expectedTestResults[ i ] ) then
			debug.print( "unexpected result at index ", i, "  actual: ", tostring( testResults[ i + 1 ] ),
								"   expected: ", tostring( expectedTestResults[ i ] ) )
			success = false
		end
	
	end
	
	if success then
		currentTest:notifyTestDidSucceed()
	else
		currentTest:notifyTestDidFail()
	end

end

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

local function testRequestFinishedWithResults( test, _, ... )

	currentTest = test

	expectedTestResults = { ... }
	expectedTestResults.n = select( '#', ... )

	test:waitForTestToFinish( 2 )

end

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

function WFRequestDispatcherTest.testRequestE( test )

	testRequestFinishedWithResults( test,
		requestTestHandler:requestE( 'echo', 1, 2, 3 ):ownedOnDataE( test, testCallback ),
		1, 2, 3 )

end

if WFPlatformInfo.isAndroid then
	WFRequestDispatcherTest.ROUTINELY_FAILING_testRequestE = WFRequestDispatcherTest.testRequestE
	WFRequestDispatcherTest.testRequestE = nil
		-- TODO [scouten 2015-06-13]: This test has failed frequently on Android
		-- in the last week. Disable until we figure out why.
end

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

function WFRequestDispatcherTest.testRequestETransitWithNil( test )

	testRequestFinishedWithResults( test,
		requestTestHandler:requestE( 'echo', 1, nil, 3 ):ownedOnDataE( test, testCallback ),
		1, nil, 3 )

end

if WFPlatformInfo.isAndroid then
	WFRequestDispatcherTest.ROUTINELY_FAILING_testRequestETransitWithNil= WFRequestDispatcherTest.testRequestETransitWithNil
	WFRequestDispatcherTest.testRequestETransitWithNil = nil
		-- TODO [scouten 2015-06-13]: This test has failed frequently on Android
		-- in the last week. Disable until we figure out why.
end


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

function WFRequestDispatcherTest.testRequestEWithData( test )

	testRequestFinishedWithResults( test,
		requestTestHandler:requestE( 'echo', "The data value" )
			:ownedOnDataE( test, testCallback )
			:ownedOnErrorE( test, function() test:notifyTestDidFail() end ),
		"The data value" )

end

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

function WFRequestDispatcherTest.testRequestEWithError( test )

	requestTestHandler:requestE( 'sendError', "expecting this error" )
		:ownedOnDataE( test, function() test:notifyTestDidFail() end )
		:ownedOnErrorE( test, function( message )
			if message == 'expecting this error' then
				test:notifyTestDidSucceed()
			else
				test:log( "wrong message: " .. message )
				test:notifyTestDidFail()
			end
		end )

	test:waitForTestToFinish( 2 )

end

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

function WFRequestDispatcherTest.testRequestEWithDataAndErrorSequence( test )

	local dataThenErrorB = requestTestHandler:requestE( 'sendValueThenError' )
		:holdB( 'nothing yet' )
	
	WFAsyncTestCase( test, 4 )

		:waitFor( function()
			return dataThenErrorB
		end )
		:continueWhenValueMatches( 1 )
		
		:waitFor( function()
			return dataThenErrorB
		end )
		:continueWhenValueMatchesError( '2' )
		
		:waitFor( .5 )
		
		:waitFor( function()
			return KSPromises.resolvedPromiseP( 'data', dataThenErrorB:isDone() )
		end )
		:continueWhenValueMatches( 'data', true )
		
end

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

function WFRequestDispatcherTest.testRequestEImmediateErrorViaEventStream( test )

	WFAsyncTestCase( test, 4 )

		:waitFor( function()

			return requestTestHandler:requestE( 'sendImmediateErrorViaEventStream',
												 'expected error' )
				:holdB( 'nothing yet' )
			
		end )
		:continueWhenValueMatchesError( 'expected error' )
		
end

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

function WFRequestDispatcherTest.testRequestEImmediateDoneViaEventStream( test )

	local immediateDoneB = requestTestHandler:requestE( 'sendNothing' )
		:holdB( 'nothing yet' )
	
	local doneB = KSReactive.variableB( false )
	immediateDoneB:ownedOnDoneE( test, function()
		doneB:set( true )
	end )

	WFAsyncTestCase( test, 4 )

		:waitFor( function()
			return KSReactive.closingDataCallB( function( _, isDone )
				return isDone, immediateDoneB:isDone()
			end,
			immediateDoneB,
			doneB )
		end )
		
		:continueWhenValueMatches( true, true )
		
end

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

function WFRequestDispatcherTest.testRequestEUpdates( test )

	local counterB = requestTestHandler:requestE( 'counterE', 4, 7 ):holdB( nil )
	
	local values = { }
	local valuesB = counterB:mapB( function( value )
		values[ #values + 1 ] = value
		return unpack( values )
	end )
	
	WFAsyncTestCase( test, 4 )

		:waitFor( valuesB )
		:continueWhenValueMatches( 4, 5, 6, 7 )
		
		-- test for counterB closing?
		
end


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

-- @mark miscellaneous APIs

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

function WFRequestDispatcherTest.testGenerateDiagnosticLog( test )

	WFDiagnosticLog.generateHtmlReportP {}
		:runUntilDone()

end

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

function WFRequestDispatcherTest.testGetInstalledMessages( test )

	WFAsyncTestCase( test )

		:waitFor( function()
		
			return requestTestHandler:requestP( 'getInstalledMessages' )
				:mapDataP( function( _, messages )
				
					assert( type( messages ) == 'table' )
					
					for _, value in ipairs( messages ) do
						if value == 'getInstalledMessages' then
							return 'data', true, messages
						end
					end
					
					return 'data', false, messages
				
				end )
			
		end )
		:continueWhenValueMatches( 'data', true, ':ignore:' )

end

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

function WFRequestDispatcherTest.testZZ_DebugDumpUniverse( test )

	-- Funky naming means this runs later in the sequence and thus
	-- covers more cases.

	requestTestHandler:requestP( 'debug_dumpUniverse', {} )
		:runUntilDone()

end

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

return WFRxTestCase.wrapSuite( 'WFRequestDispatcherTest', WFRequestDispatcherTest )
