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

WFRxContinuousTaskTest.lua

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

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

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

	-- RX
local Rx = require 'Rx'
local Observable = Rx.Observable
local Subject = Rx.Subject

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

	-- WF.core.utils
local WFRxContinuousTask = require 'WFRxContinuousTask'
local WFTimer = require 'WFTimer'
local KSDate = require 'KSDate'

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

local WFRxContinuousTaskTest = {}

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

function WFRxContinuousTaskTest.rxTestBasic( rxTest )

	rxTest:setTimeLimit( 3 )

	local taskCount = 0

	local continuousTask

	local function continuousTaskFunc()

		taskCount = taskCount + 1
		
		if taskCount < 3 then
			continuousTask:taskDidWork()
		end
		
		return Observable.just( true )

	end

	local continuousTaskDelays = {
		progressiveBackoff = true,
		noWorkDelay = .1,
		errorDelay = 10,
		maxDelay = 10,
	}

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", continuousTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	continuousTask:startContinuousTask()

	taskDoingWork:expectDataWithinTime( 1, true )

	continuousTask:stopContinuousTask()
	continuousTask:close()

	rxTest:expectEqual( true, taskCount > 1 )

end

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

function WFRxContinuousTaskTest.rxTestStopContinousTask( rxTest )

	rxTest:setTimeLimit( 3 )

	local continuousTaskDelays = {
		progressiveBackoff = true,
		noWorkDelay = .1,
		errorDelay = 10,
		maxDelay = 10,
	}

	local taskCount = 0

	local continuousTask

	local function continuousTaskFunc()

		taskCount = taskCount + 1
		
		if taskCount < 3 then
			continuousTask:taskDidWork()
		end
		
		return Observable.just( true )

	end

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", continuousTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	rxTest:expectEqual( false, continuousTask:isRunning() )

	continuousTask:startContinuousTask()

	rxTest:expectEqual( true, continuousTask:isRunning() )

	taskDoingWork:expectDataWithinTime( 1, true )

	rxTest:expectEqual( true, taskCount > 1 )

	continuousTask:stopContinuousTask()

	rxTest:expectEqual( false, continuousTask:isRunning() )

	local taskCountAfterStop = taskCount

	rxTest:sleep( 0.2 )

	continuousTask:close()

	rxTest:expectEqual( taskCountAfterStop, taskCount )

end

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

function WFRxContinuousTaskTest.rxTestTaskDidWork( rxTest )

	rxTest:setTimeLimit( 3 )

	-- Set delays of unexpected paths to exceed the 3 second time limit so the
	-- test will fail with a timeout.

	local continuousTaskDelays = {
		progressiveBackoff = true,
		noWorkDelay = 10,
		errorDelay = 10,
		maxDelay = 10,
	}

	local taskCount = 0

	local continuousTask

	local function continuousTaskFunc()

		taskCount = taskCount + 1

		if taskCount <= 5 then
			continuousTask:taskDidWork()
		end

		return Observable.just( true )

	end

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", continuousTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	taskDoingWork:expectData( false )

	continuousTask:startContinuousTask()

	taskDoingWork:expectDataWithinTime( 1, true )

	taskDoingWork:expectDataWithinTime( 3, false )

	continuousTask:stopContinuousTask()
	continuousTask:close()

	rxTest:expectEqual( true, taskCount >= 5 )

end

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

function WFRxContinuousTaskTest.rxTestNonCompletingTaskFunc( rxTest )

	rxTest:setTimeLimit( 5 )

	-- Set delays of unexpected paths to exceed the 5 second time limit so the
	-- test will fail with a timeout.

	local continuousTaskDelays = {
		progressiveBackoff = true,
		noWorkDelay = 10,
		errorDelay = 10,
		maxDelay = 10,
	}

	local subject = Subject()

	local continuousTask
	local callCount = 0

	local function nonCompletingTaskFunc()

		WFTimer.afterDelayDo( .25, function()

			callCount = callCount + 1

			if callCount == 1 then
				continuousTask:taskDidWork()
			else
				continuousTask:taskDidWork( false )
			end

			subject:onNext( true )

			if continuousTask:isRunning() then
				nonCompletingTaskFunc()
			end

		end )
		
		return subject
			:flatMapLatest( function( value )
				return continuousTask:throwIfNotRunning( value )
			end )
			:catch( function( errorMessage )
				return continuousTask:throwIfNotStopped( errorMessage )
			end )
	end

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", nonCompletingTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	taskDoingWork:expectData( false )

	continuousTask:startContinuousTask()

	taskDoingWork:expectDataWithinTime( 1, true )

	taskDoingWork:expectDataWithinTime( 1, false )

	continuousTask:stopContinuousTask()

	rxTest:expectEqual( false, continuousTask:isRunning() )

	continuousTask:close()

end

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

function WFRxContinuousTaskTest.rxTestTaskError( rxTest )

	rxTest:setTimeLimit( 3 )

	local continuousTaskDelays = {
		progressiveBackoff = true,
		noWorkDelay = 10,
		errorDelay = .1,
		maxDelay = .2,
	}

	local taskCount = 0

	local continuousTask

	local function continuousTaskFunc()

		taskCount = taskCount + 1

		if taskCount == 2 then
			return Observable.throw( "error!" )
		else
			continuousTask:taskDidWork()
		end
		
		return Observable.just( true )
		
	end

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", continuousTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	continuousTask:startContinuousTask()

	taskDoingWork:expectDataWithinTime( 1, true )
	taskDoingWork:expectDataWithinTime( 1, false )
	taskDoingWork:expectDataWithinTime( 1, true )

	continuousTask:stopContinuousTask()
	continuousTask:close()

end

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

function WFRxContinuousTaskTest.rxTestTaskErrorDelayNotAffectedByNoWork( rxTest )

	rxTest:setTimeLimit( 3 )

	local continuousTaskDelays = {
		progressiveBackoff = true,
		noWorkDelay = 10,
		maxNoWorkDelay = .1,
		errorDelay = .1,
		maxDelay = 10,
	}

	local taskCount = 0

	local continuousTask

	local didWork = false
	local hitError = false
	
	local function continuousTaskFunc()

		taskCount = taskCount + 1

		if taskCount == 5 then
		
			didWork = false
				-- This test is trying to show that the error retry count
				-- won't be affected by the noWork retry count
				
			hitError = true
			return Observable.throw( "error!" )
				-- The error should retry in one second if it's unaffected by
				-- no work count.
				
		else
			continuousTask:taskDidWork()
		end

		didWork = true
		
		return Observable.just( true )

	end

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", continuousTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	continuousTask:startContinuousTask()

	taskDoingWork:expectDataWithinTime( 1, true )
	taskDoingWork:expectDataWithinTime( 1, false )
	taskDoingWork:expectDataWithinTime( 1, true )

	continuousTask:stopContinuousTask()
	continuousTask:close()

	rxTest:expectEqual( true, hitError )
	rxTest:expectEqual( true, didWork )

end

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

function WFRxContinuousTaskTest.rxTestTriggerTask( rxTest )

	rxTest:setTimeLimit( 3 )

	local continuousTaskDelays = {
		progressiveBackoff = true,
		noWorkDelay = 10,
		errorDelay = 10,
		maxDelay = 100,
	}

	local taskCount = 0

	local continuousTask

	local function continuousTaskFunc()

		taskCount = taskCount + 1

		if taskCount == 2 then
			continuousTask:taskDidWork()
		end

		return Observable.just( true )

	end

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", continuousTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	taskDoingWork:expectData( false )

	continuousTask:startContinuousTask()

	taskDoingWork:expectDataWithinTime( 1, false )

	continuousTask:triggerTask()

	taskDoingWork:expectDataWithinTime( 1, true )

	continuousTask:stopContinuousTask()
	continuousTask:close()

end

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

function WFRxContinuousTaskTest.rxTestBackoffStrategies_Linear( rxTest )

	rxTest:setTimeLimit( 5 )

	local taskCount = 0

	local continuousTask

	local function continuousTaskFunc()

		taskCount = taskCount + 1
		
		if taskCount == 5 then
			continuousTask:taskDidWork()
		end
		
		return Observable.just( true )

	end

	local continuousTaskDelays = {
		progressiveBackoff = true,
		backoffStrategy = WFRxContinuousTask.BackoffStrategy_Linear,
		noWorkDelay = 0.1,
		errorDelay = 10,
		maxDelay = 10,
		maxNoWorkDelay = 10,
	}

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", continuousTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	continuousTask:startContinuousTask()

	-- Since the backoff is linear with a 0.1s delay, and the task only reports work
	-- the 5th time it's activated, we should expect the task to be triggered 5 times at the
	-- following intervals:
	
	-- 0s
	-- 0.1s (+0.1s)
	-- 0.3s (+0.2s)
	-- 0.6s (+0.3s)
	-- 1.0s (+0.4s)
	
	-- On the 5th time the task is triggered, the following observable will resolve.
	
	local expectedTime = 1.5 -- half second buffer
	
	taskDoingWork:expectDataWithinTime( expectedTime, true )

	continuousTask:stopContinuousTask()
	continuousTask:close()

	rxTest:expectEqual( true, taskCount >= 5 )

end

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

function WFRxContinuousTaskTest.rxTestBackoffStrategies_Exponential( rxTest )

	rxTest:setTimeLimit( 20 )

	local taskCount = 0

	local continuousTask

	local function continuousTaskFunc()

		taskCount = taskCount + 1
		
		if taskCount == 5 then
			continuousTask:taskDidWork()
		end
		
		return Observable.just( true )

	end

	local continuousTaskDelays = {
		progressiveBackoff = true,
		backoffStrategy = WFRxContinuousTask.BackoffStrategy_Exponential,
		noWorkDelay = 0.1,
		errorDelay = 10,
		maxNoWorkDelay = 10,
		maxDelay = 10,
	}

	continuousTask = WFRxContinuousTask( "WFRxContinuousTaskTest", continuousTaskFunc, continuousTaskDelays )

	local taskDoingWork = rxTest:observe( continuousTask:isTaskDoingWork() )

	local startTime = KSDate.timeNow()

	continuousTask:startContinuousTask()

	-- Since the backoff is linear with a 0.1s delay, and the task only reports work
	-- the 5th time it's activated, we should expect the task to be triggered 5 times at the
	-- following intervals:
	
	-- 0s
	-- 0.2s (+0.2s)
	-- 0.6s (+0.4s)
	-- 1.4s (+0.8s)
	-- 3.0s (+1.6s)
	
	-- On the 5th time the task is triggered, the following observable will resolve.
	
	local expectedTime = 5 -- +2 second buffer
	
	taskDoingWork:expectDataWithinTime( expectedTime, true )

	local totalTime = KSDate.timeNow() - startTime

	rxTest:expectEqual( true, totalTime >= 3 )

	continuousTask:stopContinuousTask()
	continuousTask:close()

	rxTest:expectEqual( true, taskCount >= 5 )

end

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

return WFRxTestCase.wrapSuite( 'WFRxContinuousTaskTest', WFRxContinuousTaskTest )
