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

WFQueryDrivenWorkQueueSchedulerTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2013 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.

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

	-- WF.core.lua.threading
local WFTimer = require 'WFTimer'

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

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

	-- WF.oz_client.doc_store
local WFDocStore = require 'WFDocStore'

	-- WF.oz_client.index_store
local WFIndexStore = require 'WFIndexStore'


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

local WFQueryDrivenWorkQueueSchedulerTest = {}

local docStore, indexStore, workQueueScheduler, queryB

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

function WFQueryDrivenWorkQueueSchedulerTest.setUp()

	docStore = WFDocStore( ':memory:' )

end

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

function WFQueryDrivenWorkQueueSchedulerTest.tearDown( coveragePath )

	if workQueueScheduler then
		workQueueScheduler:close()
		workQueueScheduler = nil
	end
	
	if queryB then
		queryB:close()
		queryB = nil
	end
	
	if indexStore then
	 	if coveragePath then
			indexStore:DEBUG_dumpCoverageP( coveragePath ):runUntilDone()
		end

		indexStore:close()
		indexStore = nil
	end
	
	if docStore then
		docStore:close()
		docStore = nil
	end

end

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

function WFQueryDrivenWorkQueueSchedulerTest.testHappyPath( test )

	indexStore = WFIndexStore( docStore, 'TestAspectRatio.lua' )

	queryB = indexStore:queryB(
		[[SELECT docId, aspectRatio FROM aspectRatio
					WHERE aspectRatio > 1.0
					ORDER BY aspectRatio DESC]] )

	local queryRowCalls = {}

	workQueueScheduler = WFQueryDrivenWorkQueueScheduler( queryB, 2, 50,
		function( queryRow )

			local copiedQueryRow = {}
			for k, v in pairs( queryRow ) do
				copiedQueryRow[ k ] = v
			end

			queryRowCalls[ #queryRowCalls + 1 ] = copiedQueryRow
			
			return WFTimer.afterDelayP( .25 )
					
		end )

	WFAsyncTestCase( test, 20 )

		:waitFor( function()
		
			assert( #queryRowCalls == 0 )
			
			return docStore:setDocValueP
				{
					_id = 'test1',
					_slot = 'main',
					height = 15,
					width = 10,
				}
		
		end )

		:waitFor( 1 )
		
		:waitFor( function()
		
			return queryRowCalls
		
		end )
		:continueWhenValueMatches( 'data',
			{
				{ 'test1', 1.5 },
			} )
		
		:waitFor( function()
		
			return docStore:setDocValueP
				{
					_id = 'test1',
					_slot = 'main',
					height = 5,
					width = 10,
				}
		
		end )

		:waitFor( function()
		
			return docStore:setDocValueP
				{
					_id = 'test2',
					_slot = 'main',
					height = 23,
					width = 10,
				}
		
		end )

		:waitFor( function()
		
			return docStore:setDocValueP
				{
					_id = 'test3',
					_slot = 'main',
					height = 19,
					width = 10,
				}
		
		end )

		:waitFor( 1 )
		
		:waitFor( function()
		
			return queryRowCalls
		
		end )
		:continueWhenValueMatches( 'data',
			{
				{ 'test1', 1.5 },
				{ 'test2', 2.3 },
				{ 'test3', 1.9 },
			} )
		
end

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

-- SPAM [matthewj 2015-05-08]: This test fails sporadically on all platforms and 
-- reasonably consistently on Android. It needs to be revisited and rewritten 
-- in such a way that it is not so fragile. 
--
--function WFQueryDrivenWorkQueueSchedulerTest.testMaxConcurrentTasks( test )
--
--	indexStore = WFIndexStore( docStore, 'TestAspectRatio.lua' )
--
--	queryB = indexStore:queryB(
--		[[SELECT docId, aspectRatio FROM aspectRatio
--					WHERE aspectRatio > 1.0
--					ORDER BY aspectRatio DESC]] )
--
--	local queryRowCalls = {}
--
--	workQueueScheduler = WFQueryDrivenWorkQueueScheduler( queryB, 1, 50,
--		function( queryRow )
--
--			local copiedQueryRow = {}
--			for k, v in pairs( queryRow ) do
--				copiedQueryRow[ k ] = v
--			end
--			
--			return WFTimer.afterDelayP( 1.5 )
--				:observeP( function()
--					 queryRowCalls[ #queryRowCalls + 1 ] = copiedQueryRow
--				end )
--					
--		end )
--
--	WFAsyncTestCase( test, 20 )
--
--		:waitFor( function()
--		
--			assert( #queryRowCalls == 0 )
--			
--			return docStore:setDocValueP
--				{
--					_id = 'test1',
--					_slot = 'main',
--					height = 15,
--					width = 10,
--				}
--		
--		end )
--
--		:waitFor( 2 )
--		
--		:waitFor( function()
--		
--			return queryRowCalls
--		
--		end )
--		:continueWhenValueMatches( 'data',
--			{
--				{ 'test1', 1.5 },
--			} )
--		
--		:waitFor( function()
--		
--			return docStore:setDocValueP
--				{
--					_id = 'test1',
--					_slot = 'main',
--					height = 5,
--					width = 10,
--				}
--		
--		end )
--
--		:waitFor( function()
--		
--			return docStore:setDocValueP
--				{
--					_id = 'test2',
--					_slot = 'main',
--					height = 23,
--					width = 10,
--				}
--		
--		end )
--
--		:waitFor( function()
--		
--			return docStore:setDocValueP
--				{
--					_id = 'test3',
--					_slot = 'main',
--					height = 19,
--					width = 10,
--				}
--		
--		end )
--
--		:waitFor( 2.5 )
--		
--		:waitFor( function()
--		
--			return queryRowCalls
--		
--		end )
--		
--		-- If we are properly limiting concurrent tasks we will not see the 'test3' update
--		-- and the test1 update will still be subject to blackout
--		:continueWhenValueMatches( 'data',
--			{
--				{ 'test1', 1.5 },
--				{ 'test2', 2.3 },
--			} )
--
--end

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

function WFQueryDrivenWorkQueueSchedulerTest.testBlackoutWindow( test )

	indexStore = WFIndexStore( docStore, 'TestAspectRatio.lua' )

	queryB = indexStore:queryB(
		[[SELECT docId, aspectRatio FROM aspectRatio
					WHERE aspectRatio > 1.0
					ORDER BY aspectRatio DESC]] )

	local queryRowCalls = {}

	workQueueScheduler = WFQueryDrivenWorkQueueScheduler( queryB, 2, 4,
		function( queryRow )

			local qrCopy = {}
			for k, v in pairs( queryRow ) do
				qrCopy[ k ] = v
			end
			
			queryRowCalls[ #queryRowCalls + 1 ] = qrCopy
			
			return WFTimer.afterDelayP( .25 )
					
		end )

	WFAsyncTestCase( test, 30 )

		:waitFor( function()
		
			assert( #queryRowCalls == 0 )
			
			return docStore:setDocValueP
				{
					_id = 'test1',
					_slot = 'main',
					height = 15,
					width = 10,
				}
		
		end )

		:waitFor( 3 )

		:waitFor( function()
		
			return queryRowCalls
		
		end )
		:continueWhenValueMatches( 'data',
			{
				{ 'test1', 1.5 },
			} )
		
		-- At this point, we should still be within the blackout
		-- window, so the underlying query update that this next
		-- call should trigger should not cause another call to
		-- the generate-task function.
		
		:waitFor( function()
		
			return docStore:setDocValueP
				{
					_id = 'test2',
					_slot = 'main',
					height = 23,
					width = 10,
				}
		
		end )
		
		:waitFor( .5 )
		:waitFor( function()
		
			return queryRowCalls
		
		end )
		:continueWhenValueMatches( 'data',
			{
				{ 'test1', 1.5 },
				{ 'test2', 2.3 },
			} )
		
		
		:waitFor( 2.5 )
		
		-- Now we should be out of the blackout window for the
		-- first item but not the second. So when we perturb
		-- the query again, it should cause a new task for the
		-- first item.

		:waitFor( function()
		
			return queryRowCalls
		
		end )
		:continueWhenValueMatches( 'data',
			{
				{ 'test1', 1.5 },
				{ 'test2', 2.3 },
				{ 'test1', 1.5 },
			} )
		
end

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

function WFQueryDrivenWorkQueueSchedulerTest.testClosesTasksWhenClosed( test )

	local KSReactive = require 'KSReactive'
	local KSReactivePromiseBridge = require 'KSReactivePromiseBridge'
	
	indexStore = WFIndexStore( docStore, 'TestAspectRatio.lua' )

	queryB = indexStore:queryB(
		[[SELECT docId, aspectRatio FROM aspectRatio
					WHERE aspectRatio > 1.0
					ORDER BY aspectRatio DESC]] )

	local promiseStarted, promiseStatus
	
	local stateB = KSReactive.variableB()

	workQueueScheduler = WFQueryDrivenWorkQueueScheduler( queryB, 2, 2.5,
		function( queryRow )

			promiseStarted = true
			stateB:set( "time_to_close" )

			return KSReactivePromiseBridge.behaviorToPromise( stateB,
				function( v ) return v == 'has_been_canceled' end )
				:observeP( function( status )
					promiseStatus = status
				end )
					
		end )

	WFAsyncTestCase( test, 20 )

		:waitFor( function()
		
			return docStore:setDocValueP
				{
					_id = 'test1',
					_slot = 'main',
					height = 15,
					width = 10,
				}
		
		end )
		
		:waitFor( stateB )
		
		:continueWhenValueMatches( "time_to_close" )
		
		:waitFor( function()

			assert( promiseStarted )

			workQueueScheduler:close()
			workQueueScheduler = nil
			stateB:set( "has_been_canceled" )

			return 'ok'

		end )
		
		:waitFor( 1 )
		
		:waitFor( function()
			
			return tostring( promiseStatus )
		
		end )
		:continueWhenValueMatches( 'data', 'nil' )

end

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

return WFQueryDrivenWorkQueueSchedulerTest
