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

PixelProviderRenderSaveTaskTest.lua

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

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

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

	-- If ACR is not supported, disable test before we attempt to
	-- require WFAcr. (That will fail.)
if not require 'WFPlatformInfo'.hasACR then return {} end

	-- wf.core
local KSData = require 'KSData'
local KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require 'KSTableUtils'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFFileUtils = require 'WFFileUtils'
local WFFiniteStateMachine = require 'WFFiniteStateMachine'
local WFRxTestCase = require 'WFRxTestCase'
local WFCloseSupport = require 'WFCloseSupport'

	-- Rx
local Rx = require 'Rx'

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

local PixelPathProviderScheduler = require 'PixelPathProviderScheduler'
local PixelPathProviderRequestTypes = require 'PixelPathProviderRequestTypes'
local PixelPathProviderStateMachine = require 'PixelPathProviderStateMachine'
local PixelProviderRenderSaveTask = require 'PixelProviderRenderSaveTask'

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

local PixelPathProviderRequestTypeNames = PixelPathProviderRequestTypes.names

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

local function makeSchedulerForTest( rxTest )

	local catalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )

	local scheduler = PixelPathProviderScheduler( catalog )

	rxTest:finally( function() scheduler:close() end )

	return scheduler, catalog

end

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

local function localAssetId( i )

	return i

end

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

local function pixStreamId( i )

	return string.format( "pixStreamId%d", i )

end

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

local function makeSaveDataStr( i )

	return string.format( "PixelPathProviderStateMachineTest fake data %d", i )

end

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

local function stripTestMessageAndMakeTupleN( n )
	
	return function( _, ... )

		return { unpack( { ... }, 1, n ) }

	end

end

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

local function sortArray( arr, maybeSortFn )

	local sorted = KSTableUtils.deepCopy( arr )

	table.sort( sorted, maybeSortFn )

	return sorted

end

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

local function compareArrays( a1, a2 )

	if #a1 ~= #a2 then

		return #a1 > #a2

	end

	for i = 1, math.min( #a1, #a2 ) do

		if a1[ i ] ~= a2[ i ] then

			return a1[ i ] > a2 [i ]

		end

	end

	return false

end

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

local function sortTupleArray( tupleArray )

	return sortArray( tupleArray, compareArrays )

end

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

local function isTestMessage( msg )

	return function( testMsg, ... )

		return testMsg == msg

	end

end

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

local function getSortedNFromTestInfoStream( rxTest, 
												desc, 
												testStream, 
												n,
												testMessageFilterStr )

	local i

	return testStream
			:where( isTestMessage( testMessageFilterStr ) )
			:tap(
				function( ... )

					i = ( i or 0 ) + 1

					local argN = select( "#", ... )

					local argStr = argN == 0 and "<no args>" or "%s" .. string.rep( ", %s", argN - 1 )

					rxTest:logf( "getSortedNFromTestInfoStream( '%s' ) %s of %s: " .. argStr, 
									desc, i, n, ... )

				end )
			:map( stripTestMessageAndMakeTupleN( 2 ) )
			:take( n )
			:toArray()
			:map( sortTupleArray )

end

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

local runningSaveFileCount = 0

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

local function makeUniqueSavePath( i )

	runningSaveFileCount = runningSaveFileCount + 1

	local fname = string.format( "PixelPathProviderStateMachineTest%d_%d", 
									runningSaveFileCount, i )

	local realFileDir = KSPathUtils.getStandardFilePath( "temp" )

	return WFFileUtils.ensureFileNameIsUnique( 
						KSPathUtils.child( realFileDir, fname ) )

end

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

local function takeFirstN( n )

	return function( t )

		local ret = {}

		for i = 1, n do

			ret[ i ] = t[ i ]

		end

		return ret

	end

end

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

local randomUUID = require 'KSIdGenerator'.uuidInLowercaseHex

--------------------------------------------------------------------------------
-- rxTestRenderSave 
-- 
-- 		test a real save
--

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

local function appendT( ... )

	local appendedT = {}

	local ts = {}

	for i = 1, select( '#', ... ) do

		ts[ i ] = select( i, ... )

		assert( type( ts[ i ] ) == "table",
					string.format( "arg %s is not a table!", i ) )

	end

	local j = 0

	for _, t in ipairs( ts ) do

		for _, v in ipairs( t ) do
		
			j = j + 1

			appendedT[ j ] = v

		end

	end

	return appendedT

end

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

local needSaveRenderStates =
			{ WFFiniteStateMachine.makeRetryableStates(
						PixelPathProviderStateMachine.SaveRenderRetryCount, 

							PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender ) }

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

local needSaveRenditionStates =
			{ PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveLoupeRendition,
			  PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveExportFullRendition,
			  PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveExportBiggestPossibleRenditionDownload }

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

local needsSaveRenderBlobStates =
		appendT( needSaveRenderStates, needSaveRenditionStates )

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

local function rxTestRenderSave( rxTest )
	
	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderRenderSaveTask" )

	for i = 1, #needsSaveRenderBlobStates do

		scheduler:addRequest( pixStreamId( i ), localAssetId( i ),
									PixelPathProviderRequestTypeNames.grid_visible, 1 )

	end

	for _, testWithRenderBlob in ipairs( { true, false } ) do

		for _, testWithSavePath in ipairs( { true, false } ) do

			local expectedTestDataTuples = {}

			local expectedErrorStateTuples = {}

			local expectedHaveRenditionPathStateTuples = {}

			local strings = {}

			local ksDatas = {}

			local paths = {}

			local expectedTestMessage = 
					( testWithRenderBlob and testWithSavePath and
						"didPixelCacheSave" ) or 
					( not testWithSavePath and "skipSaveRenditionNoSettings" ) or
					"skipSaveRenditionNoBlob"

			local watchPixelCacheSaves = 
					rxTest
						:observe( 

							getSortedNFromTestInfoStream(
									rxTest,
									"watchPixelCacheSaves",
									scheduler._testData.testInfoStream,
									#needsSaveRenderBlobStates,
									expectedTestMessage )
						)

			for i, needSaveRenderState in ipairs( needsSaveRenderBlobStates ) do

				local digest = randomUUID()

				local renderSize = 100 + i

				if testWithRenderBlob then

					strings[ i ] = makeSaveDataStr( i )

					ksDatas[ i ] = KSData.make( strings[ i ] )

					scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:pushBlob(
						localAssetId( i ), ksDatas[ i ], 'rendition', 
						renderSize, digest )

				end

				if testWithSavePath then

					paths[ i ] = makeUniqueSavePath( i )

					rxTest:log( "Making sure no saved rendition exists at " .. tostring( paths[ i ] ) )

					rxTest:expectEqual( false, WFFileUtils.exists( paths[ i ] ) )

					scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
						pixStreamId( i ), localAssetId( i ),
						{ renditionPath = paths[ i ],
							digest = digest, 
							revId = 'wat', 
							renderSize = renderSize,
							croppedLongSide = 67890, 
							userOrientation = 1, 
							devSettings = "<xmp />" } )

				end

				rxTest:logf( "Added request of %s to %s with initial state %s", 
								localAssetId( i ), pixStreamId( i ), needSaveRenderState ) 

				SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
											needSaveRenderState )

				expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = { pixStreamId( i ), localAssetId( i ) }

				expectedErrorStateTuples[ #expectedErrorStateTuples + 1 ] = 
					not( testWithRenderBlob and testWithSavePath ) and
						{ pixStreamId( i ), localAssetId( i ) } or nil

				expectedHaveRenditionPathStateTuples[ #expectedHaveRenditionPathStateTuples + 1 ] = 
					testWithRenderBlob and testWithSavePath and 
							{ pixStreamId( i ), localAssetId( i ) } or nil

			end

			rxTest:log( "Scheduling a test expecting " .. expectedTestMessage )

			SchedulerTestAPI.runScheduledRequests( scheduler )

			watchPixelCacheSaves:expectData( sortTupleArray( expectedTestDataTuples ) )

			rxTest:log( "Finishing a test expecting " .. expectedTestMessage )

			rxTest:expectEqual( 
				sortTupleArray( expectedErrorStateTuples ),
				sortTupleArray( 
					scheduler:_mapAllRequestsWithStates( 
								{ PixelPathProviderStateMachine.SendDataStates.SendPermanentError,
									PixelPathProviderStateMachine.SendDataStates.SendPermanentDownloadError,
									PixelPathProviderStateMachine.SendDataStates.SendPermanentErrorForExportFullRenditionCreateFail,
									PixelPathProviderStateMachine.SendDataStates.SendPermanentErrorForExportFullRenditionDownloadFail,
									PixelPathProviderStateMachine.SendDataStates.SendPermanentErrorForExportBiggestPossibleRenditionDownloadFail },
								takeFirstN( 2 ) ) ) )

			rxTest:expectEqual( 
				sortTupleArray( expectedHaveRenditionPathStateTuples ),
				sortTupleArray( 
					scheduler:_mapAllRequestsWithStates( 
								{ PixelPathProviderStateMachine.SendDataStates.HaveRenditionPath,
									PixelPathProviderStateMachine.SendDataStates.SendFinalLoupeRenditionPath,
									PixelPathProviderStateMachine.SendDataStates.SendFinalExportFullRenditionPath,
									PixelPathProviderStateMachine.SendDataStates.SendFinalExportBiggestPossibleRenditionPath },
								takeFirstN( 2 ) ) ) )

			if testWithRenderBlob then

				-- check to make sure all the render blobs are cleaned up
				for i = 1, #needsSaveRenderBlobStates do

					local digest = randomUUID()

					local renderSize = 100 + i

					rxTest:expectEqual( nil,
						scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:peekBlob(
							localAssetId( i ), 'rendition', renderSize, digest ) )

					rxTest:expectEqual( true, WFCloseSupport.hasBeenOrWillBeClosed( ksDatas[ i ] ) )

					if testWithSavePath then

						rxTest:log( "Making sure saved rendition exists at " .. tostring( paths[ i ] ) )

						rxTest:expectEqual( "file", WFFileUtils.exists( paths[ i ] ) )

						local f = io.open( paths[ i ], 'r' )

						local contents = f:read( "*all" )

						f:close()

						WFFileUtils.delete( paths[ i ] )

						rxTest:expectEqual( strings[ i ], contents )

					end

				end

			end

			if testWithSavePath then

				for i = 1, #needsSaveRenderBlobStates do

					scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:destroyCacheItem(
						pixStreamId( i ), localAssetId( i ) )

				end

			end

		end

	end

end

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

local function makeFakeSaveThread( fakeSaveResultsStreams )

	local i

	return {

		savePixels = 

			function() 

				i = ( i or 0 ) + 1

				return fakeSaveResultsStreams[ i ] or Rx.Subject()
				
			end,

		close = function() end,

	}

end

--------------------------------------------------------------------------------
-- rxTestRenderSaveFail 
-- 
-- 	test a save failure (failures are faked by mocking the save thread)
--
--------------------------------------------------------------------------------

local function rxTestRenderSaveFail( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderRenderSaveTask" )

	local k 

	local fakeSaveResultsStreams = {}

	local expectedTestDataTuples = {}

	for i = 1, #needsSaveRenderBlobStates do

		scheduler:addRequest( pixStreamId( i ), localAssetId( i ),
									PixelPathProviderRequestTypeNames.grid_visible, 1 )

	end

	for i = 1, #needSaveRenderStates do
		for j = #needSaveRenderStates, i, -1 do

			k = ( k or 0 ) + 1

			rxTest:logf( "Adding failure stream %s ( %s total) for request of %s to %s with initial state %s", 
							#needSaveRenderStates - j + 1, k, 
							localAssetId( i ), pixStreamId( i ),
							needSaveRenderStates[ i ] ) 

			expectedTestDataTuples[ k ] = { pixStreamId( i ), localAssetId( i ) }

			fakeSaveResultsStreams[ k ] = Rx.Subject()

		end

	end

	local nTuples, nStreams = #expectedTestDataTuples, #fakeSaveResultsStreams

	for i = 1, #needSaveRenditionStates do

		rxTest:logf( "Adding failure stream %s ( %s total) for request of %s to %s with initial state %s", 
						i, nStreams + i,
						localAssetId( i ), pixStreamId( i ),
						needSaveRenditionStates[ i ] ) 

		expectedTestDataTuples[ nTuples + i ] = { pixStreamId( #needSaveRenderStates + i ), localAssetId( #needSaveRenderStates + i ) }

		fakeSaveResultsStreams[ nStreams + i ] = Rx.Subject()

	end

	scheduler._testData.runLoopTaskStates.PixelProviderRenderSaveTask.saveToCacheThread =
		makeFakeSaveThread( fakeSaveResultsStreams )

	local expectedSendPermanentErrorTuples = {}

	local strings = {}

	local ksDatas = {}

	local paths = {}

	local watchPixelCacheSaves = 
			rxTest
				:observe( 

					getSortedNFromTestInfoStream(
						rxTest,
						"watchPixelCacheSaves",
						scheduler._testData.testInfoStream,
						#fakeSaveResultsStreams,
						'saveFailed' )

				)

	for i, needSaveRenderState in ipairs( needsSaveRenderBlobStates ) do

		local digest = randomUUID()

		local renderSize = 100 + i

		strings[ i ] = makeSaveDataStr( i )

		ksDatas[ i ] = KSData.make( strings[ i ] )

		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:pushBlob(
			localAssetId( i ), ksDatas[ i ], 'rendition', 
			renderSize, digest )

		paths[ i ] = makeUniqueSavePath( i )

		rxTest:log( "Making sure no saved rendition exists at " .. tostring( paths[ i ] ) )

		rxTest:expectEqual( false, WFFileUtils.exists( paths[ i ] ) )

		scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
			pixStreamId( i ), localAssetId( i ),
			{ renditionPath = paths[ i ],
				digest = digest, 
				revId = 'wat', 
				renderSize = renderSize,
				croppedLongSide = 67890, 
				userOrientation = 1, 
				devSettings = "<xmp />" } )

		rxTest:logf( "Added request of %s to %s with initial state %s", 
						localAssetId( i ), pixStreamId( i ), needSaveRenderState ) 

		SchedulerTestAPI.setState( scheduler, pixStreamId( i ), localAssetId( i ), 
									needSaveRenderState )

		rxTest:logf( "Added expected data tuple of %s to %s with initial state %s", 
						localAssetId( i ), pixStreamId( i ), needSaveRenderState ) 

		expectedSendPermanentErrorTuples[ #expectedSendPermanentErrorTuples + 1 ] = 
					{ pixStreamId( i ), localAssetId( i ) }

	end

	rxTest:log( "Scheduling a test expecting saveFailed" )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	for i = 1, #fakeSaveResultsStreams do

		fakeSaveResultsStreams[ i ]:onError( string.format( "fale wail #%s!", i ) )

	end

	watchPixelCacheSaves:expectData( sortTupleArray( expectedTestDataTuples ) )

	rxTest:log( "Finishing a test expecting saveFailed" )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SendDataStates.HaveRenditionPath,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		sortTupleArray( expectedSendPermanentErrorTuples ),
		sortTupleArray( 
			scheduler:_mapAllRequestsWithStates( 
						{ PixelPathProviderStateMachine.SendDataStates.SendPermanentError,
							PixelPathProviderStateMachine.SendDataStates.SendPermanentDownloadError,
							PixelPathProviderStateMachine.SendDataStates.SendPermanentErrorForExportFullRenditionDownloadFail,
							PixelPathProviderStateMachine.SendDataStates.SendPermanentErrorForExportFullRenditionCreateFail,
							PixelPathProviderStateMachine.SendDataStates.SendPermanentErrorForExportBiggestPossibleRenditionDownloadFail },
						takeFirstN( 2 ) ) ) )

	-- check to make sure all the render blobs are cleaned up
	for i = 1, #needsSaveRenderBlobStates do

		local digest = randomUUID()

		local renderSize = 100 + i

		rxTest:expectEqual( nil,
			scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:peekBlob(
				localAssetId( i ), 'rendition', renderSize, digest ) )

		rxTest:expectEqual( true, WFCloseSupport.hasBeenOrWillBeClosed( ksDatas[ i ] ) )

	end

end

--------------------------------------------------------------------------------
-- rxTestQuitPendingSaveThread
--
-- 	test quitting the scheduler main loop before the save thread finished (save
-- 	thread is mocked)
--------------------------------------------------------------------------------

local function rxTestQuitPendingSaveThread( rxTest )

	-- quit the task before we've finished saving the blob

	local catalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )

	local scheduler = PixelPathProviderScheduler( catalog )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderRenderSaveTask" )

	scheduler:addRequest( pixStreamId( 1 ), localAssetId( 1 ),
								PixelPathProviderRequestTypeNames.grid_visible, 1 )


	local fakeSaveResultsStreams = { Rx.Subject() }

	scheduler._testData.runLoopTaskStates.PixelProviderRenderSaveTask.saveToCacheThread =
		makeFakeSaveThread( fakeSaveResultsStreams )

	local strings = {}

	local ksDatas = {}

	local paths = {}
	
	local digest = randomUUID()

	local renderSize = 100 + 1

	strings[ 1 ] = makeSaveDataStr( 1 )

	ksDatas[ 1 ] = KSData.make( strings[ 1 ] )

	scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:pushBlob(
		localAssetId( 1 ), ksDatas[ 1 ], 'rendition', 
		renderSize, digest )

	paths[ 1 ] = makeUniqueSavePath( 1 )

	rxTest:log( "Making sure no saved rendition exists at " .. tostring( paths[ 1 ] ) )

	rxTest:expectEqual( false, WFFileUtils.exists( paths[ 1 ] ) )

	scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
		pixStreamId( 1 ), localAssetId( 1 ),
		{ renditionPath = paths[ 1 ],
			digest = digest, 
			revId = 'wat', 
			renderSize = renderSize,
			croppedLongSide = 67890, 
			userOrientation = 1, 
			devSettings = "<xmp />" } )

	SchedulerTestAPI.setState( scheduler, pixStreamId( 1 ), localAssetId( 1 ), 
								PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender )

	rxTest:expectEqual( true,
		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:peekBlob(
			localAssetId( 1 ), 'rendition', renderSize, digest ) ~= nil )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	scheduler:close()

	-- check to make sure all the render blobs are cleaned up
	rxTest:expectEqual( nil,
		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:peekBlob(
			localAssetId( 1 ), 'rendition', renderSize, digest ) )

	fakeSaveResultsStreams[ 1 ]:onCompleted()

	rxTest:expectEqual( false, WFCloseSupport.hasBeenOrWillBeClosed( ksDatas[ 1 ] ) )

end

--------------------------------------------------------------------------------
-- rxTestPurgeAbandoned
--
-- test that abandoned blobs are purged
--------------------------------------------------------------------------------

local function rxTestPurgeAbandoned( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderRenderSaveTask" )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	local strings = {}

	local ksDatas = {}

	local paths = {}
	
	local digest = randomUUID()

	local renderSize = 100 + 1

	strings[ 1 ] = makeSaveDataStr( 1 )

	ksDatas[ 1 ] = KSData.make( strings[ 1 ] )

	scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:pushBlob(
		localAssetId( 1 ), ksDatas[ 1 ], 'rendition', 
		renderSize, digest )

	paths[ 1 ] = makeUniqueSavePath( 1 )

	rxTest:log( "Making sure no saved rendition exists at " .. tostring( paths[ 1 ] ) )

	rxTest:expectEqual( false, WFFileUtils.exists( paths[ 1 ] ) )

	scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
		pixStreamId( 1 ), localAssetId( 1 ),
		{ renditionPath = paths[ 1 ],
			digest = digest, 
			revId = 'wat', 
			renderSize = renderSize,
			croppedLongSide = 67890, 
			userOrientation = 1, 
			devSettings = "<xmp />" } )

	rxTest:expectEqual( true,
		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:peekBlob(
			localAssetId( 1 ), 'rendition', renderSize, digest ) ~= nil )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	-- check to make sure all the render blobs are cleaned up
	rxTest:expectEqual( nil,
		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:peekBlob(
			localAssetId( 1 ), 'rendition', renderSize, digest ) )

	rxTest:expectEqual( true, WFCloseSupport.hasBeenOrWillBeClosed( ksDatas[ 1 ] ) )

end

--------------------------------------------------------------------------------
-- rxTestJoinPendingSave 
-- 
-- Test that appropriate requests join a pending save and that all of them
-- then serve up send a rendition path after the save
--------------------------------------------------------------------------------

local function rxTestJoinPendingSave( rxTest )
	
	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderRenderSaveTask" )

	local strings = {}

	local ksDatas = {}

	local paths = {}
	
	local digest = randomUUID()

	local renderSize = 100 + 1

	local expectedTestDataTuples = {}

	paths[ 1 ] = makeUniqueSavePath( 1 )

	-- now add a bunch requests that should join this one
	for i, state in ipairs( PixelProviderRenderSaveTask.privateUnitTestExports.needsRenderBlobStates ) do

		scheduler:addRequest( pixStreamId( i + 1 ), localAssetId( 1 ),
									PixelPathProviderRequestTypeNames.grid_visible, 1 )

		scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
			pixStreamId( i + 1 ), localAssetId( 1 ),
			{ renditionPath = paths[ 1 ],
				digest = digest, 
				revId = 'wat', 
				renderSize = renderSize,
				croppedLongSide = 67890, 
				userOrientation = 1, 
				devSettings = "<xmp />" } )

		rxTest:logf( "Added request of %s to %s with initial state %s", 
						localAssetId( 1 ), pixStreamId( i + 1 ), state ) 

		SchedulerTestAPI.setState( scheduler, pixStreamId( i + 1 ), localAssetId( 1 ), state ) 

		rxTest:logf( "Added expected data tuple of %s to %s with initial state %s", 
						localAssetId( 1 ), pixStreamId( i + 1 ), state ) 

		expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = { pixStreamId( i + 1 ), localAssetId( 1 ) }

	end

	-- set up a pending save request
	scheduler:addRequest( pixStreamId( 1 ), localAssetId( 1 ),
								PixelPathProviderRequestTypeNames.grid_visible, 1 )

	strings[ 1 ] = makeSaveDataStr( 1 )

	ksDatas[ 1 ] = KSData.make( strings[ 1 ] )

	scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:pushBlob(
		localAssetId( 1 ), ksDatas[ 1 ], 'rendition', 
		renderSize, digest )

	rxTest:log( "Making sure no saved rendition exists at " .. tostring( paths[ 1 ] ) )
					
	rxTest:expectEqual( false, WFFileUtils.exists( paths[ 1 ] ) )

	scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
		pixStreamId( 1 ), localAssetId( 1 ),
		{ renditionPath = paths[ 1 ],
			digest = digest, 
			revId = 'wat', 
			renderSize = renderSize,
			croppedLongSide = 67890, 
			userOrientation = 1, 
			devSettings = "<xmp />" } )

	expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = { pixStreamId( 1 ), localAssetId( 1 ) }

	local watchPixelCacheSaves = 
			rxTest
				:observe( 

					getSortedNFromTestInfoStream(
						rxTest,
						"watchPixelCacheSaves",
						scheduler._testData.testInfoStream,
						#expectedTestDataTuples,
						'didPixelCacheSave' )
						
				)

	rxTest:log( "Begin join test" )

	SchedulerTestAPI.setState( scheduler, pixStreamId( 1 ), localAssetId( 1 ), 
								PixelPathProviderStateMachine.SaveRenderStates.NeedToSaveRender )

	rxTest:logf( "Added request of %s to %s with initial state %s", 
					localAssetId( 1 ), pixStreamId( 1 ), "NeedToSaveRender" ) 

	rxTest:logf( "Added expected data tuple of %s to %s with initial state %s", 
					localAssetId( 1 ), pixStreamId( 1 ), "NeedToSaveRender" ) 

	SchedulerTestAPI.runScheduledRequests( scheduler )

	watchPixelCacheSaves:expectData( sortTupleArray( expectedTestDataTuples ) )

	rxTest:log( "Finishing join test" )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SendDataStates.SendPermanentError,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		sortTupleArray( expectedTestDataTuples ),
		sortTupleArray( 
			scheduler:_mapAllRequestsWithStates( 
						{ PixelPathProviderStateMachine.SendDataStates.HaveRenditionPath,
							PixelPathProviderStateMachine.SendDataStates.SendFinalLoupeRenditionPath,
							PixelPathProviderStateMachine.SendDataStates.SendFinalExportFullRenditionPath,
							PixelPathProviderStateMachine.SendDataStates.SendFinalExportBiggestPossibleRenditionPath },
						takeFirstN( 2 ) ) ) )

	-- check to make sure all the render blobs are cleaned up
	rxTest:expectEqual( nil,
		scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:peekBlob(
			localAssetId( 1 ), 'rendition', renderSize, digest ) )

	rxTest:expectEqual( true, WFCloseSupport.hasBeenOrWillBeClosed( ksDatas[ 1 ] ) )

	rxTest:log( "Making sure saved rendition exists at " .. tostring( paths[ 1 ] ) )

	rxTest:expectEqual( "file", WFFileUtils.exists( paths[ 1 ] ) )

	local f = io.open( paths[ 1 ], 'r' )

	local contents = f:read( "*all" )

	f:close()

	WFFileUtils.delete( paths[ 1 ] )

	rxTest:expectEqual( strings[ 1 ], contents )

	rxTest:expectEqual( true, WFCloseSupport.hasBeenOrWillBeClosed( ksDatas[ 1 ] ) )

end

--------------------------------------------------------------------------------
-- rxTestJoinActiveSave
-- 
-- Test that all appropriate requests join an active save (save thread is
-- mocked)
--------------------------------------------------------------------------------

local function rxTestJoinActiveSave( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderRenderSaveTask" )

	local strings = {}

	local ksDatas = {}

	local paths = {}
	
	local digest = randomUUID()

	local renderSize = 100 + 1

	local expectedTestDataTuples = {}

	-- now add a bunch requests that should join this one
	for i, state in ipairs( PixelProviderRenderSaveTask.privateUnitTestExports.needsRenderBlobStates ) do

		scheduler:addRequest( pixStreamId( i + 1 ), localAssetId( 1 ),
									PixelPathProviderRequestTypeNames.grid_visible, 1 )

		scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
			pixStreamId( i + 1 ), localAssetId( 1 ),
			{ renditionPath = paths[ 1 ],
				digest = digest, 
				revId = 'wat', 
				renderSize = renderSize,
				croppedLongSide = 67890, 
				userOrientation = 1, 
				devSettings = "<xmp />" } )

		SchedulerTestAPI.setState( scheduler, pixStreamId( i + 1 ), localAssetId( 1 ), state ) 

		expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = { pixStreamId( i + 1 ), localAssetId( 1 ) }

	end

	-- set up a pending save request
	scheduler:addRequest( pixStreamId( 1 ), localAssetId( 1 ),
								PixelPathProviderRequestTypeNames.grid_visible, 1 )

	scheduler._testData.runLoopTaskStates.PixelProviderRenderSaveTask.saveToCacheThread =
		makeFakeSaveThread( {} )

	strings[ 1 ] = makeSaveDataStr( 1 )

	ksDatas[ 1 ] = KSData.make( strings[ 1 ] )

	scheduler._testData.runLoopCaches.sharedCaches.pendingSaveBlobCache:pushBlob(
		localAssetId( 1 ), ksDatas[ 1 ], 'rendition', 
		renderSize, digest )

	paths[ 1 ] = makeUniqueSavePath( 1 )

	rxTest:log( "Making sure no saved rendition exists at " .. tostring( paths[ 1 ] ) )

	rxTest:expectEqual( false, WFFileUtils.exists( paths[ 1 ] ) )

	scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache:pushCacheItem(
		pixStreamId( 1 ), localAssetId( 1 ),
		{ renditionPath = paths[ 1 ],
			digest = digest, 
			revId = 'wat', 
			renderSize = renderSize,
			croppedLongSide = 67890, 
			userOrientation = 1, 
			devSettings = "<xmp />" } )

	expectedTestDataTuples[ #expectedTestDataTuples + 1 ] = { pixStreamId( 1 ), localAssetId( 1 ) }

	SchedulerTestAPI.setState( scheduler, pixStreamId( 1 ), localAssetId( 1 ), 
								PixelPathProviderStateMachine.SaveRenderStates.SavingRender )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	rxTest:expectEqual( 
		{},
		sortTupleArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.SendDataStates.SendPermanentError,
						takeFirstN( 2 ) ) ) )

	rxTest:expectEqual( 
		sortTupleArray( expectedTestDataTuples ),
		sortTupleArray( 
			appendT(
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveRenderStates.SavingRender,
							takeFirstN( 2 ) ),
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveRenderStates.SavingLoupeRendition,
							takeFirstN( 2 ) ),
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveRenderStates.SavingExportFullRendition,
							takeFirstN( 2 ) ),
				scheduler:_mapAllRequestsWithState( 
							PixelPathProviderStateMachine.SaveRenderStates.SavingBiggestPossibleRenditionDownload,
							takeFirstN( 2 ) ) 
			) 
		)

	)

end

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

local function bumpTimeLimit( f, n )

	return function( rxTest )

		rxTest:setTimeLimit( n )

		return f( rxTest )

	end

end

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

return WFRxTestCase.wrapSuite( 'PixelProviderRenderSaveTaskTest', {

	rxTestRenderSave = bumpTimeLimit( rxTestRenderSave, 5 ),

	rxTestRenderSaveFail = rxTestRenderSaveFail,

	rxTestQuitPendingSaveThread = rxTestQuitPendingSaveThread,

	rxTestPurgeAbandoned = rxTestPurgeAbandoned,

	rxTestJoinPendingSave = bumpTimeLimit( rxTestJoinPendingSave, 5 ),

	rxTestJoinActiveSave = rxTestJoinActiveSave,

} )
