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

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

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

local WFRateLimitersTest = {}

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

local assert = assert

-- local KSTestUtils = require 'KSTestUtils'

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

function WFRateLimitersTest.testNamespace()

	local WFRateLimiters = require 'WFRateLimiters'
	
	assert( WFRateLimiters.makeRateLimiter, "No makeRateLimiter routine" )
	assert( WFRateLimiters.makeDualRateLimiters, "No makeDualRateLimiters routine" )

end

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

function WFRateLimitersTest.testMakeRateLimiter()

	local WFRateLimiters = require 'WFRateLimiters'
	local WFQueueing = require 'WFQueueing'

	local kNumTasksToRun = 20
	local kSimultaneousTasksLimit = 5
	
	local limiter = WFRateLimiters.makeRateLimiter(
			WFQueueing.makeQueue,
			kSimultaneousTasksLimit,
			{
				otherMethod = function( self, ... )
					return 42
				end,
			} )

	local numStarted = 0
	local numRunning = 0
	local maxRunning = 0
	
	local function functionLimitChecker()
	
		numStarted = numStarted + 1
		
		numRunning = numRunning + 1
		if numRunning > maxRunning then
			maxRunning = numRunning
		end
		
		limiter:startedTask( numStarted )

		if limiter.callFinishedTask and numStarted > kNumTasksToRun / 2 then
			numRunning = numRunning - 1
			limiter:finishedTask( numStarted )
		end
				
	end

	for x = 1, kNumTasksToRun do
		limiter:enqueueTask( functionLimitChecker )
	end
	
	for taskID = 1, kNumTasksToRun do
		numRunning = numRunning - 1
		limiter:finishedTask( taskID )
	end

	assert( numStarted == kNumTasksToRun)
	assert( maxRunning <= kSimultaneousTasksLimit, "simultaneous task limit exceeded" )
	assert( maxRunning == kSimultaneousTasksLimit, "simultaneous task limit wasn't reached" )
	assert( numRunning == 0, "call enqueueTask functionLimitChecker the correct number of times" )
	assert( limiter:otherMethod() == 42, "otherMethod successfully copied from parameters" )


	-- now test calling finishedTask from within limited function (this hits the recursion guard in startActions)

	limiter.callFinishedTask = true
	
	numStarted = 0
	numRunning = 0
	maxRunning = 0

	for x = 1, kNumTasksToRun do
		limiter:enqueueTask( functionLimitChecker )
	end

	-- the last half the functions should have finished immedately, so just finish the first half
	for taskID = 1, kNumTasksToRun / 2 do
		numRunning = numRunning - 1
		limiter:finishedTask( taskID )
	end

	assert( numStarted == kNumTasksToRun)
	assert( maxRunning <= kSimultaneousTasksLimit, "simultaneous task limit exceeded" )
	assert( maxRunning == kSimultaneousTasksLimit, "simultaneous task limit wasn't reached" )
	assert( numRunning == 0, "call enqueueTask functionLimitChecker the correct number of times" )
	assert( limiter:otherMethod() == 42, "otherMethod successfully copied from parameters" )


	-- test nil function is handled
	
	limiter:enqueueTask( nil )

end


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

function WFRateLimitersTest.testMakeRateLimiterWithTimedOutTasks( test )

	local WFRateLimiters = require 'WFRateLimiters'
	local WFQueueing = require 'WFQueueing'
	local WFTimer = require 'WFTimer'
	
	local kNumTasksToRun = 20
	local kSimultaneousTasksLimit = 5

	local limiter = WFRateLimiters.makeRateLimiter(
			WFQueueing.makeQueue,
			kSimultaneousTasksLimit,
			{
				otherMethod = function( self, ... )
					return 42
				end,
			} )

	local numStarted = 0
	local numRunning = 0
	local maxRunning = 0

	local function functionLimitChecker()

		numStarted = numStarted + 1

		numRunning = numRunning + 1
		if numRunning > maxRunning then
			maxRunning = numRunning
		end

		limiter:startedTask( numStarted )
		
		if numStarted == 1 then
			limiter:startedTask( numStarted ) -- incorrectly try to start the 1st task twice to increase coverage in taskPool_addTask
		end

	end

	for x = 1, kNumTasksToRun do
		limiter:enqueueTask( functionLimitChecker )
	end


	-- delay for 16 seconds so currently running tasks are considered timed out by WFRateLimiter
	
	WFTimer.afterDelayDo( 18,
		function( status, blah )

			-- finish the tasks
			for taskID = 1, kNumTasksToRun do
				numRunning = numRunning - 1
				limiter:finishedTask( taskID )
			end

			assert( numStarted == kNumTasksToRun)

--			assert( maxRunning <= kSimultaneousTasksLimit, "simultaneous task limit exceeded" )
			assert( maxRunning == kSimultaneousTasksLimit*2, "simultaneous task limit should be double because we considered 5 are timedout. Instead of expected value of " .. tostring(kSimultaneousTasksLimit*2) .. " we got " .. tostring( maxRunning ) )

			assert( numRunning == 0, "functionLimitChecker must not have been called the correct number of times" )

			test:notifyTestDidSucceed()
			
		end )
	
	
	test:waitForTestToFinish( 23 )

end

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

function WFRateLimitersTest.testMakeDualRateLimiters()

	local WFRateLimiters = require 'WFRateLimiters'
	local WFQueueing = require 'WFQueueing'

	local kNumLowTasksToRun = 20
	local kNumHighTasksToRun = 30
	local kSimultaneousTasksLimit = 5
	
	local highLimiter, lowLimiter = WFRateLimiters.makeDualRateLimiters(
			WFQueueing.makeQueue,
			kSimultaneousTasksLimit,
			{
				otherMethod = function( self, ... )
					return 42
				end,
			} )

	lowLimiter.callFinishedTaskOnLastItem = true

	local numHighStarted = 0
	local numLowStarted = 0
	local numRunning = 0
	local maxRunning = 0
	
	local function functionHighTaskChecker()
	
		numHighStarted = numHighStarted + 1
		
		numRunning = numRunning + 1
		if numRunning > maxRunning then
			maxRunning = numRunning
		end

		highLimiter:startedTask( numHighStarted )

	end

	local function functionLowTaskChecker()
	
		numLowStarted = numLowStarted + 1
		
		numRunning = numRunning + 1
		if numRunning > maxRunning then
			maxRunning = numRunning
		end
		
		lowLimiter:startedTask( numLowStarted )

		-- finish the last low priority task immeidately, to hit a recursion guard in startActions
		if lowLimiter.callFinishedTaskOnLastItem and numLowStarted == kNumLowTasksToRun then
			numRunning = numRunning - 1
			lowLimiter:finishedTask( numLowStarted )
		end
				
	end

	-- queue up the low priority tasks first
	for x = 1, kNumLowTasksToRun do
		lowLimiter:enqueueTask( functionLowTaskChecker )
	end
	
	assert( numLowStarted == kSimultaneousTasksLimit, "kSimultaneousTasksLimit low priority tasks should have started" )

	-- now queue up the high priority tasks, these should get run before the rest of the low priority tasks
	-- the first 5 (kSimultaneousTasksLimit) low priority tasks will get started but then the high priority tasks
	-- should take precedence
	for x = 1, kNumHighTasksToRun do
		highLimiter:enqueueTask( functionHighTaskChecker )
	end
	
	assert( numLowStarted == kSimultaneousTasksLimit, "kSimultaneousTasksLimit low priority tasks should have started" )
--	assert( numHighStarted == 1, "The first high priority tasks should have started immediately" )
	assert( numHighStarted == kSimultaneousTasksLimit, "kSimultaneousTasksLimit high priority tasks should have started immediately" )

	-- let the first high priority task finish to see that we will start another even though we are still over the limit
	numRunning = numRunning - 1
	highLimiter:finishedTask( 1 )
	
--	assert( numHighStarted == 2, "The 2nd high priority tasks should have started" )
	assert( numHighStarted == kSimultaneousTasksLimit+1, "The 6th high priority tasks should have started" )
	assert( numLowStarted == kSimultaneousTasksLimit, "no new low priority tasks should have started" )

	-- finish 5 low prioirity tasks that should have started
	for taskID = 1, kSimultaneousTasksLimit do
		numRunning = numRunning - 1
		lowLimiter:finishedTask( taskID )
	end

	assert( numHighStarted == kSimultaneousTasksLimit + 1, "kSimultaneousTasksLimit + 1 high priority tasks should have started" )
	
	-- finish the rest of the high priority tasks
	for taskID = 2, kNumHighTasksToRun do
		numRunning = numRunning - 1
		highLimiter:finishedTask( taskID )
	end

	assert( numHighStarted == kNumHighTasksToRun, "kSimultaneousTasksLimit all high priority tasks should have started" )
	assert( numLowStarted == kSimultaneousTasksLimit * 2, "kSimultaneousTasksLimit (5) more low priority tasks should have started" )

	-- call finishedTask on the rest of the low priority items, except the last one which will finish itself automatically when started
	for taskID = kSimultaneousTasksLimit + 1, kNumLowTasksToRun - 1 do
		numRunning = numRunning - 1
		lowLimiter:finishedTask( taskID )
	end

	assert( numHighStarted == kNumHighTasksToRun)
	assert( numLowStarted == kNumLowTasksToRun)
--	assert( maxRunning <= kSimultaneousTasksLimit, "simultaneous task limit exceeded" )
--	assert( maxRunning == kSimultaneousTasksLimit, "simultaneous task limit wasn't reached" )
	assert( maxRunning <= kSimultaneousTasksLimit*2, "simultaneous task limit * 2 exceeded" )
	assert( maxRunning == kSimultaneousTasksLimit*2, "simultaneous task limit * 2 wasn't reached" )
	assert( numRunning == 0, "call enqueueTask functionLimitChecker the correct number of times" )
	assert( highLimiter:otherMethod() == 42, "otherMethod successfully copied from parameters to lowLimiter" )
	assert( lowLimiter:otherMethod() == 42, "otherMethod successfully copied from parameters to highLimiter" )


	-- test nil function is handled
	
	highLimiter:enqueueTask( nil )
	lowLimiter:enqueueTask( nil )
	
end

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

return WFRateLimitersTest
