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

WFRxLowFidelityTimerTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2016 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remains 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 are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

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

	-- WF.core.utils
local KSDate = require 'KSDate'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFRxLowFidelityTimer = require 'WFRxLowFidelityTimer'

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

local WFRxLowFidelityTimerTest = {}

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

function WFRxLowFidelityTimerTest.rxTestBasic( rxTest )

	rxTest:setTimeLimit( 10 )

	local nSec = 1

	local startTime = KSDate.timeNow()
	local endTime
	
	local lowFidelityTimer = WFRxLowFidelityTimer.timer( nSec )
		:tap( function()
			endTime = KSDate.timeNow()
		end )
	
	-- Make sure the timer fires at a minimum of nSec and a maximum of nSec + 3.
	
	local observer = rxTest:observe( lowFidelityTimer )
	
	local maxDiff = nSec + 3
	local minDiff = nSec
	
	observer:expectDataWithinTime( maxDiff, ':ignore:' )
	
	local diff = endTime - startTime
	
	rxTest:expectEqual( true, diff <= maxDiff and diff >= minDiff )

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

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

function WFRxLowFidelityTimerTest.rxTestTimerStopsAndStarts_testNightly( rxTest )

	rxTest:setTimeLimit( 30 )

	local nSec = 1
	local maxDiff = nSec + 3

	local lowFidelityTimer = WFRxLowFidelityTimer.timer( nSec )
	local tickCount = WFRxLowFidelityTimer.getTickCount()
	-- Make sure the timer fires at a minimum of nSec and a maximum of nSec + 3.
	
	local observer = rxTest:observe( lowFidelityTimer )
	observer:expectDataWithinTime( maxDiff, ':ignore:' )
	rxTest:expectEqual( tickCount + 1, WFRxLowFidelityTimer.getTickCount() )
	
	-- Sleep. Tick count shouldn't change since we have no oustanding requests
	
	rxTest:sleep( 3.5 )
	
	rxTest:expectEqual( tickCount + 1, WFRxLowFidelityTimer.getTickCount() )
	
	-- Add a new timer. We should resume appropriately.
	
	local observer2 = rxTest:observe( lowFidelityTimer )
	observer2:expectDataWithinTime( maxDiff, ':ignore:' )
	rxTest:expectEqual( tickCount + 2, WFRxLowFidelityTimer.getTickCount() )

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to complete
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

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

function WFRxLowFidelityTimerTest.rxTestTimerStartsWithSubscription( rxTest )

	rxTest:setTimeLimit( 10 )

	local nSec = 1

	local endTime
	
	local lowFidelityTimer = WFRxLowFidelityTimer.timer( nSec )
		:tap( function()
			endTime = KSDate.timeNow()
		end )
	
	-- Sleep for 3 seconds. If the timer is starting prematurely, this will
	-- help us detect that.
	
	rxTest:sleep( 3 ) 
	
	local startTime = KSDate.timeNow()
	
	local observer = rxTest:observe( lowFidelityTimer )
	
	local maxDiff = nSec + 3
	local minDiff = nSec
	
	observer:expectDataWithinTime( maxDiff, ':ignore:' )
	
	local diff = endTime - startTime
	
	rxTest:expectEqual( true, diff <= maxDiff and diff >= minDiff )

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

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

function WFRxLowFidelityTimerTest.rxTestMultipleTimers( rxTest )

	rxTest:setTimeLimit( 10 )

	local endTime1, endTime2
	
	-- Create two timers that should fire on the same tick within the low
	-- fidelity timer.
	
	local lowFidelityTimer1 = WFRxLowFidelityTimer.timer( 1 )
		:tap( function()
			endTime1 = KSDate.timeNow()
		end )
		
	local lowFidelityTimer2 = WFRxLowFidelityTimer.timer( 2 )
		:tap( function()
			endTime2 = KSDate.timeNow()
		end )
	
	-- Start the timers.
	
	local observer = rxTest:observe( lowFidelityTimer1:merge( lowFidelityTimer2 ) )
	
	-- Wait for the timer to fire.
	
	observer:expectDataWithinTime( 3, ':ignore:' )

	-- Give the tap events some time to process.

	rxTest:sleep( 0.2 )

	local endTimeDiff = math.abs( endTime2 - endTime1 )
	
	-- Make sure the timers fired at the same time, meaning they shared a tick within
	-- the WFRxLowFidelityTimer.
	
	rxTest:expectEqual( true, endTimeDiff <= 0.1 )

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end


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

function WFRxLowFidelityTimerTest.rxTestTimerHandlesDispose( rxTest )

	rxTest:setTimeLimit( 10 )

	local nSec = 1

	local timer1Fired = false
	local timer2Fired = false
	local timer3Fired = false

	local timer1Sub = WFRxLowFidelityTimer.timer( nSec )
		:subscribe( function() timer1Fired = true end )

	local timer2Sub = WFRxLowFidelityTimer.timer( nSec )
		:subscribe( function() timer2Fired = true end )

	local timer3Sub = WFRxLowFidelityTimer.timer( nSec )
		:subscribe( function() timer3Fired = true end )

	timer1Sub:dispose()
	timer2Sub:dispose()

	rxTest:sleep( 3.5 )

	rxTest:expectEqual( false, timer1Fired )
	rxTest:expectEqual( false, timer2Fired )
	rxTest:expectEqual( true, timer3Fired )

	timer3Sub:dispose()

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

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

function WFRxLowFidelityTimerTest.rxTestTimerHandlesDuplicates( rxTest )

	rxTest:setTimeLimit( 10 )

	local nSec = 1

	local timer1Fired = false
	local timer2Fired = false
	local timer3Fired = false

	local timer1Sub = WFRxLowFidelityTimer.timer( nSec )
		:subscribe( function() timer1Fired = true end )

	local timer2Sub = WFRxLowFidelityTimer.timer( nSec )
		:subscribe( function() timer2Fired = true end )

	local timer3Sub = WFRxLowFidelityTimer.timer( nSec )
		:subscribe( function() timer3Fired = true end )

	rxTest:sleep( 3.5 )

	rxTest:expectEqual( true, timer1Fired )
	rxTest:expectEqual( true, timer2Fired )
	rxTest:expectEqual( true, timer3Fired )

	timer1Sub:dispose()
	timer2Sub:dispose()
	timer3Sub:dispose()

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

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

-- TODO [jandrew 2025-03-25]: Disabled on Android due to instability.

if not WFPlatformInfo.isAndroid then

function WFRxLowFidelityTimerTest.rxTestDocValueTimer( rxTest )

	rxTest:setTimeLimit( 10 )

	local nSec = 1

	local startTime = KSDate.timeNow()
	local endTime
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local docStore = wfCatalog:docStore()
	
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore )
	
	local expectedDocValue
	local id = addAssetFn()
	
	local lowFidelityTimer = docStore:docValue( id )
	
		:flatMapLatest( function( docValue )
			expectedDocValue = docValue
			return WFRxLowFidelityTimer.docValueTimer( nSec, docStore, docValue._localId )
		end )

		:tap( function( docValue )
			endTime = KSDate.timeNow()
			rxTest:expectEqual( docValue, expectedDocValue )
		end )
	
	-- Make sure the timer fires at a minimum of nSec and a maximum of nSec + 3.
	
	local observer = rxTest:observe( lowFidelityTimer )
	
	local maxDiff = nSec + 3
	local minDiff = nSec
	
	observer:expectDataWithinTime( maxDiff, ':ignore:' )
	
	local diff = endTime - startTime
	
	rxTest:expectEqual( true, diff <= maxDiff and diff >= minDiff )

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

end

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

function WFRxLowFidelityTimerTest.rxTestDocValueTimerHandlesDispose( rxTest )

	rxTest:setTimeLimit( 10 )

	local nSec = 1

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )

	local docStore = wfCatalog:docStore()

	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore )

	local id = addAssetFn()

	local timer1Fired = false
	local timer2Fired = false
	local timer3Fired = false

	local timer1Sub = WFRxLowFidelityTimer.docValueTimer( nSec, docStore, id )
		:subscribe( function() timer1Fired = true end )

	local timer2Sub = WFRxLowFidelityTimer.docValueTimer( nSec, docStore, id )
		:subscribe( function() timer2Fired = true end )

	local timer3Sub = WFRxLowFidelityTimer.docValueTimer( nSec, docStore, id )
		:subscribe( function() timer3Fired = true end )

	timer1Sub:dispose()
	timer2Sub:dispose()

	rxTest:sleep( 3.5 )

	rxTest:expectEqual( false, timer1Fired )
	rxTest:expectEqual( false, timer2Fired )
	rxTest:expectEqual( true, timer3Fired )

	timer3Sub:dispose()

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

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

function WFRxLowFidelityTimerTest.rxTestDocValueTimerHandlesDuplicates( rxTest )

	rxTest:setTimeLimit( 10 )

	local nSec = 1

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )

	local docStore = wfCatalog:docStore()

	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore )

	local id = addAssetFn()

	local timer1Fired = false
	local timer2Fired = false
	local timer3Fired = false

	local timer1Sub = WFRxLowFidelityTimer.docValueTimer( nSec, docStore, id )
		:subscribe( function() timer1Fired = true end )

	local timer2Sub = WFRxLowFidelityTimer.docValueTimer( nSec, docStore, id )
		:subscribe( function() timer2Fired = true end )

	local timer3Sub = WFRxLowFidelityTimer.docValueTimer( nSec, docStore, id )
		:subscribe( function() timer3Fired = true end )

	rxTest:sleep( 3.5 )

	rxTest:expectEqual( true, timer1Fired )
	rxTest:expectEqual( true, timer2Fired )
	rxTest:expectEqual( true, timer3Fired )

	timer1Sub:dispose()
	timer2Sub:dispose()
	timer3Sub:dispose()

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

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

function WFRxLowFidelityTimerTest.rxTestDocValueTimerMixedIds( rxTest )

	rxTest:setTimeLimit( 10 )

	local nSec = 1

	local startTime = KSDate.timeNow()
	local endTime
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	
	local docStore = wfCatalog:docStore()
	
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore )
	
	local expectedDocValue1, expectedDocValue2
	
	local id = addAssetFn()
	local id2 = addAssetFn()

	local localId2
	
	rxTest:expectSingleValue( docStore:docValue( id2 )
				:tap( function( docValue )
					expectedDocValue2 = docValue
					localId2 = docValue._localId
				end ),
				":ignore:" )
	
	local gothere1 = false
	local gothere2 = false
	
	local lowFidelityTimer = docStore:docValue( id )
	
		:flatMapLatest( function( docValue )
			expectedDocValue1 = docValue
			return WFRxLowFidelityTimer.docValueTimer( nSec, docStore, id )
		end )

		:merge( WFRxLowFidelityTimer.docValueTimer( nSec, docStore, localId2 ) )

		:tap( function( docValue )
		
			if docValue._id == id then
				gothere1 = true
				rxTest:expectEqual( docValue, expectedDocValue1 )
			else
				gothere2 = true
				rxTest:expectEqual( docValue, expectedDocValue2 )
			end
			
			if gothere1 and gothere2 then
				endTime = KSDate.timeNow()
			end
			
		end )
	
	-- Make sure the timer fires at a minimum of nSec and a maximum of nSec + 3.
	
	local observer = rxTest:observe( lowFidelityTimer )
	
	local maxDiff = nSec + 3
	local minDiff = nSec
	
	observer:expectDataWithinTime( maxDiff, ':ignore:' )
	
	rxTest:sleep( 0.2 )
	
	rxTest:expectEqual( true, gothere1 )
	rxTest:expectEqual( true, gothere2 )
	
	local diff = endTime - startTime
	
	rxTest:expectEqual( true, diff <= maxDiff and diff >= minDiff )

	rxTest:sleep( .1 ) -- let the unit test swap to the other coperative thread to update timer count
	rxTest:expectEqual( 0, WFRxLowFidelityTimer.getTimerCount() )

end

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

return WFRxTestCase.wrapSuite( 'WFRxLowFidelityTimerTest', WFRxLowFidelityTimerTest )
