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

LoupeRenditionStateMachineTest.lua

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

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

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

--==============================================================================
-- Skip all tests logic
--==============================================================================

-- This test uses ACR since it renders, if a platform doesn't have ACR the test
-- will fail to even load. We therefore bail in that case.
local WFPlatformInfo = require 'WFPlatformInfo'

if not WFPlatformInfo.hasACR then return {} end

if WFPlatformInfo.isAndroid then return {} end

--==============================================================================
-- normal test execution starts below
--==============================================================================

local Rx = require 'Rx'
local WFRxScheduler = require 'WFRxScheduler'

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

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

local WFCatRenderSize = require 'WFCatRenderSize'
local PixelPathProviderScheduler = require "PixelPathProviderScheduler"
local PixelPathProviderRequestTypes = require "PixelPathProviderRequestTypes"
local PixelProviderDownloadTask = require "PixelProviderDownloadTask"

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

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 testDataPath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ),
										"import-samples" )

local testDataFolder1 = KSPathUtils.child( testDataPath, "folder-1" )

local _validOrigPath = KSPathUtils.child( testDataFolder1, "IMG_7763.jpg" )

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

local function makePixStreamTuple( rxTest )
	
	return function( pixStreamId, assetId, success, pixelPathOrError,
							maybeDigest, maybeRenderSize, maybeCroppedLongSide )

		if success then

			rxTest:logf( "Pixstream '%s' asset %s saw rendition size %s with path %s",
							 pixStreamId, assetId, maybeRenderSize, pixelPathOrError )

		else

			rxTest:logf( "Pixstream '%s' asset %s saw error %s",
							 pixStreamId, assetId, pixelPathOrError )

		end

		return { pixStreamId, 
					assetId,
					success, 
					success and "pixelPath" or pixelPathOrError, 
					maybeDigest and "digest" or nil, 
					maybeRenderSize,
					maybeCroppedLongSide }

	end

end

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

local function firstMatches( v )

	return function( wut )

		return wut == v

	end

end

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

local function stripFirstNAndPack( n )

	return function( ... )

		local ret = {}

		for i = n + 1, select( "#", ... ) do

			ret[ #ret + 1 ] = select( i, ... )

		end

		return ret

	end

end

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

local function makeTestMessageStreamMatchingMessage( scheduler, wantedMsg )

	return scheduler._testData.testInfoStream
				:where( 
					function( msg ) 

						return msg == wantedMsg

					end )
				:map( stripFirstNAndPack( 1 ) )

end

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

local function reduceT( initialValue, operand, t, ... )
	
	local ret = initialValue

	local k, v

	repeat

		k, v = next( t, k )

		if k then

			ret = operand( ret, k, v, ... )

		end

	until not k

	return ret

end 

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

local function addURIUnlessRenditionBiggerThan( ret, uri, uriData,
												maxDownloadableRenditionSize )

	if type( uri ) ~= "string" then

		return ret

	end
	
	local maybeSize = uri:gsub( ".*/(.*)", "%1" )

	local pixelSize = maybeSize == "thumbnail2x" and 320 or tonumber( maybeSize )

	if maxDownloadableRenditionSize == "fullsize" or
		( pixelSize and pixelSize <= maxDownloadableRenditionSize ) then

		ret[ uri ] = uriData

	end

	return ret

end

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

local function fixupRevisionIds( rxTest, catalog, assetId )

	rxTest
		:expectSingleValue(
			catalog
				:docStore()
				:docValue( assetId )
				:take( 1 )
				:flatMapLatest(
					function( docValue )

						return catalog
								:docStore()
								:applyOverlay( 
									{
										_id = assetId, 
										_serverProvidedMetadata = {
											revision_ids = { docValue._revId }
										} 
									} )

					end ),
			':ignore:' )


end

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

local function addRecordToDocStore( rxTest, catalog, docStoreRecord )

	--[[
local WFTableArchiving = require 'WFTableArchiving'

	rxTest:logf( "Adding record to docStore: %s",
					WFTableArchiving.tableToString( docStoreRecord ) )

	--]]
	
	rxTest
		:expectSingleValue(
			catalog:docStore():setDocValue( docStoreRecord ),
			':ignore:' )

	fixupRevisionIds( rxTest, catalog, docStoreRecord._id )

end

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

local function addMaxDownloadableRenditionSizeToDocStore( 
				rxTest, catalog, docStoreRecord, maxDownloadableRenditionSize )
	
	local ozLinks =
			KSTableUtils.derefOpt( 
				docStoreRecord, "_serverProvidedMetadata", "links" )

	if type( ozLinks ) == "table" then

		docStoreRecord._serverProvidedMetadata.links = 
			reduceT( {}, 
				addURIUnlessRenditionBiggerThan, 
					ozLinks, maxDownloadableRenditionSize )

	end

	addRecordToDocStore( rxTest, catalog, docStoreRecord )

end

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

local function addLoupeRenditionRequest( rxTest, scheduler,
											loupeRenditionPixStreamId,
											localAssetId, requestTypeName )

	rxTest:logf( "Adding loupe rendition pixel provider request for %s in '%s' with type %s",
					localAssetId, loupeRenditionPixStreamId, requestTypeName )

	scheduler
		:createLoupeRenditionRequestOrUpdatePriority( 
			loupeRenditionPixStreamId, 
			{ { "create", 1 } }, { localAssetId },  { 2560 },
			requestTypeName )

end

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

local function addGenericPixelProviderRequest( rxTest, scheduler, pixStreamId,
												localAssetId, requestTypeName,
												renderSize )

	rxTest:logf( "Adding generic pixel provider request for %s in '%s' with type %s",
					localAssetId, pixStreamId, requestTypeName,
					renderSize )

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

end

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

local function makeDocStoreRecordOriginalAndRenditions( assetId, localAssetId, originalPath, croppedW, croppedH )

	return {

		_id = assetId,
		_localId = localAssetId,
		type = "asset",
		subtype = "image",
		develop = {
			processingModel = "lightroom",
			croppedHeight = croppedH,
			croppedWidth = croppedW,
			userOrientation = 1,
			crsVersion = "11.3.1",
			xmpCameraRaw = "<xmp />",
		},
		userUpdated = "2019-08-08T21:19:53.434Z",
		copy = {
			copy_id = "4bce0aa638584d51b46f5fd6401f78db",
		},
		captureDate = "2016-12-26T15:34:13.318",
		aesthetics = {
			harmony = 18,
			score = 61,
			lighting = -28,
			rot = 0,
			repetition = 3,
			symmetry = 2,
			balancing = 6,
			emphasis = -35,
			version = 1,
			application = "ias",
			created = "2019-08-08T21:20:06Z",
			dof = -7,
			content = 4,
			vivid = -41,
		},
		importSource = {
			originalHeight = croppedH,
			importedOnDevice = "bkruse-macbookpro2014",
			importTimestamp = "2019-08-08T21:19:52Z",
			contentType = "image/jpeg",
			fileName = "IMG_2261.JPG",
			fileSize = 1215097,
			originalWidth = croppedW,
			sha256 = "f9b430859b70d4d2673051172ac9bf8861ea398cbe7ee413d5a47fa49b7855c1",
			importedBy = "55fe3558cff715f3e3138579b5bcb1d9",
		},
		xmp = {
			tiff = {
				Orientation = "top left",
				Make = "Apple",
				Model = "iPhone 5",
			},
			exif = {
				ApertureValue = {
					2526069,
					1000000,
				},
				FNumber = {
					24,
					10,
				},
				FocalLength = {
					103,
					25,
				},
				DateTimeOriginal = "2016-12-26T15:34:13.318",
				FlashRedEyeMode = false,
				ExposureTime = {
					1,
					6800,
				},
				FlashFired = true,
				MeteringMode = "pattern",
				FocalLengthIn35mmFilm = 33,
				ExposureProgram = "normal program",
				FlashReturn = "no strobe return detection",
				ISOSpeedRatings = 64,
				ShutterSpeedValue = {
					12731319,
					1000000,
				},
				FlashMode = "compulsory flash firing",
				BrightnessValue = {
					9696,
					857,
				},
				FlashFunction = false,
				ExposureBiasValue = {
					0,
					1,
				},
			},
			aux = {
				Lens = "iPhone 5 back camera 4.12mm f/2.4",
			},
			xmp = {
				CreatorTool = "10.2",
				CreateDate = "2016-12-26T15:34:13.318",
				ModifyDate = "2016-12-26T15:34:13",
			},
			photoshop = {
				DateCreated = "2016-12-26T15:34:13.318",
			},
		},
		userCreated = "2019-08-08T21:19:53.434Z",
		_localOnly = {
			files = {
				proxy = {
					["2560"] = {
						relativePath = "Users/bkruse/Pictures/Lightroom Library.lrlibrary/ac1f6bf23e384320a753fb6e5934ee2f/proxies/2560/f7f/0cfb46e6a46368fb01407460a1430",
						volumeGuid = "DC5268AF-16FA-30B1-A355-111D5A326FF7",
						sha256 = "f9b430859b70d4d2673051172ac9bf8861ea398cbe7ee413d5a47fa49b7855c1",
						shouldExist = true,
						size = 396102,
					},
				},
				original = {
					master = {
						relativePath = 
							WFFileUtils.removeVolumeFromPath( originalPath ) or
							originalPath,
						volumeGuid = 
							WFFileUtils.getVolumeGuidForPath( originalPath ) or
							KSIdGenerator.uuidInLowercaseHex(),
						sha256 = "f9b430859b70d4d2673051172ac9bf8861ea398cbe7ee413d5a47fa49b7855c1",
						size = 1215097,
					},
				},
			},
			xmpSidecarExists = false,
			cameraProfileInfo = {
				profileDigest = "NA",
				isStandard = false,
				xmpCameraRawProfileDigest = "27ed687384abf82580f4fcee402a66f8",
			},
		},
		_serverProvidedMetadata = {
			updated = "2019-08-08T22:18:13.677478Z",
			revision_ids = {
				"7519d7b3f7af4ae38ba5a296d36373eb",
				"de3b6a99d4535668e3bf21a74203d8cb",
				"4de477bff8b54c9f97c06156be400fcb",
			},
			created = "2019-08-08T21:19:55.788502Z",
			base = "https://photos.adobe.io/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/",
			links = {
				["self"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430",
				},
				["/rels/master_create"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/{revision_id}/master",
					templated = true,
				},
				["/rels/rendition_type/fullsize"] = {
					href = "yuuuuge",
				},
				["/rels/rendition_type/8192"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/7519d7b3f7af4ae38ba5a296d36373eb/renditions/261bde29b42782f6242b2f900747875f",
				},
				["/rels/rendition_type/2048"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/7519d7b3f7af4ae38ba5a296d36373eb/renditions/261bde29b42782f6242b2f900747875f",
				},
				["/rels/rendition_type/thumbnail2x"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/7519d7b3f7af4ae38ba5a296d36373eb/renditions/01f3248a7212ca863bee8d16ea4bcadd",
				},
				["/rels/xmp_develop_create"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/{revision_id}/xmp/develop",
					templated = true,
				},
				["/rels/rendition_create"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/{revision_id}/renditions/{rendition_id}",
					templated = true,
				},
				["/rels/proxy_type/2560"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/7519d7b3f7af4ae38ba5a296d36373eb/proxies/7a48cfcc02608ddcc48a87d067b96b32",
				},
				["/rels/rendition_type/640"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/7519d7b3f7af4ae38ba5a296d36373eb/renditions/721d33ad21b31c7c3103f7d7f70747db",
				},
				["/rels/proxy_create"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/{revision_id}/proxies/{proxy_id}",
					templated = true,
				},
				["/rels/rendition_type/1280"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/7519d7b3f7af4ae38ba5a296d36373eb/renditions/03e26909aca892195981d4e1513adeb1",
				},
				["/rels/master"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/7519d7b3f7af4ae38ba5a296d36373eb/master",
					content_type = "image/jpeg",
				},
				["/rels/xmp"] = {
					href = "/v2/catalogs/ac1f6bf23e384320a753fb6e5934ee2f/assets/f7f0cfb46e6a46368fb01407460a1430/revisions/7519d7b3f7af4ae38ba5a296d36373eb/xmp",
				},
			},
		},
	}

end

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

local function makeRenditionByRendering( rxTest, scheduler, localAssetId, 
											renderSize, maybeCroppedLongSide )

	local renditionPixstreamId = "filmstrip"

	local savedPixelPath

	local function recordPixelPath( _, _, _, pixelPath )

		savedPixelPath = pixelPath

	end

	local pixelPathStreamFilteredToFilmstrip = 
			rxTest:observe( 
				scheduler
					:pixelPathStream()
					:where( firstMatches( renditionPixstreamId ) )
					:tap( recordPixelPath )
					:map( makePixStreamTuple( rxTest ) ) )

	addGenericPixelProviderRequest( 
		rxTest, scheduler, 
		renditionPixstreamId, 
		localAssetId,
		PixelPathProviderRequestTypeNames.grid_visible, 
		renderSize )

	rxTest:log( "waiting for a small rendition render" )

	pixelPathStreamFilteredToFilmstrip
		:expectData( 
			{ 
				renditionPixstreamId,
				localAssetId,
				true,
				"pixelPath",
				"digest",
				renderSize == maybeCroppedLongSide and "full" or renderSize, 
				maybeCroppedLongSide
			} )

	rxTest
		:expectEqual( 
			true, 
			WFFileUtils.exists( savedPixelPath ) == "file" )

	return savedPixelPath, renditionPixstreamId

end

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

local function sendDataAsync( observer, data )

	return 
		Rx.Observable.timer( 0.1, WFRxScheduler.sharedScheduler() )
			:take( 1 )
			:map(
				function()

					return data

				end )
			:subscribe( observer )

end

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

local function makeMockBinaryRequester()

	local mockDownloadStream = Rx.Subject()

	local binaryRequests, binaryRequestsCount

	local pendingObservers = {}

	local scheduledDispatch = Rx.CompositeDisposable()

	return {

		getBinaryRequestStream = 
			function() 

				return mockDownloadStream

			end,
		
		maybeRequestBinaryAtUrl = 
			function( _, ozAssetId, ozBinaryType, ozBinarySubtype, ozBinaryUrl, revisionId, priority, requestInfo ) 

				local sendToBinaryRequestsStream = not binaryRequests

				binaryRequests = binaryRequests or {}

				binaryRequestsCount = ( binaryRequestsCount or 0 ) + 1

				local thisBinaryRequestId = binaryRequestsCount

				local function cancelFn()

					binaryRequests[ thisBinaryRequestId ] = nil

					return true

				end

				binaryRequests[ thisBinaryRequestId ] =
					{
						ozAssetId = ozAssetId,
						ozBinaryType = ozBinaryType,
						ozBinarySize = ozBinarySubtype,
						ozBinaryUrl = ozBinaryUrl,
						ozRevisionId =  revisionId,
						requestPriority = priority,
						cancelFn = cancelFn,
						optInfo = requestInfo
					}
					
				if sendToBinaryRequestsStream then

					pendingObservers =
							reduceT( {},
								function( ret, pendingObserver )

									scheduledDispatch =
										Rx.CompositeDisposable( 
											scheduledDispatch, 
											sendDataAsync( pendingObserver, binaryRequests ) 
										)

									return ret

								end, 
								pendingObservers ) 

				end

				return cancelFn

		end,
			
		close = 
			function( self )

				self._closed = true

			end,

		getInFlightRequests =
			function()

				return binaryRequests
				
			end,

		getInFlightRequestsStream =
			function()

				return Rx.Observable.create(
				
					function( observer )

						if binaryRequests then

							local currBinaryRequests = binaryRequests

							binaryRequests = {}

							return sendDataAsync( observer, currBinaryRequests )

						end
						
						pendingObservers[ observer ] = observer

						return 
							Rx.CompositeDisposable(
								scheduledDispatch,
								Rx.Disposable.create(
									function()
						
										pendingObservers[ observer ] = nil

									end ) )

					end )

			end,

	}

end

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

local function setupMockBinaryRequester( scheduler, maxDownloadsCount )

	local mockBinaryRequester = makeMockBinaryRequester()

	if not scheduler._testData.runLoopTaskStates then

		local SchedulerTestAPI = scheduler:_expose_test_API()

		SchedulerTestAPI.runScheduledRequests( scheduler )

	end

	scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask =
		PixelProviderDownloadTask.privateUnitTestExport.injectMockBinaryRequester(
			scheduler._testData.runLoopTaskStates.PixelProviderDownloadTask,
			maxDownloadsCount, mockBinaryRequester )

	return mockBinaryRequester

end

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

local function getRequestInfo( request )

	if type( request ) ~= "table" then

		return

	end

	return request.ozAssetId, request.ozRevisionId, request.ozBinaryType,
			request.ozBinarySize

end

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

local function logRequest( request, rxTest )

	rxTest:logf( "request: " .. "%s" .. string.rep( ", %s", #request ),
					unpack( request ) )

end

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

local function waitUntilFulfillAllPendingFakeDownloadRequests( 
					rxTest, scheduler, pixStreamId, mockBinaryRequester,
					shouldFakeDownloadSucceed )

	local mockDownloadStream = mockBinaryRequester:getBinaryRequestStream()

	rxTest:log( "start waiting for need to fulfill requests" )

	rxTest:expectOnlyCompleted(

		mockBinaryRequester
			:getInFlightRequestsStream()
			:flatMapLatest(

				function( inflightRequests ) 

					for i, requested in ipairs( inflightRequests ) do

						rxTest:logf( "%s request state before sending fake blob",
										pixStreamId )
						scheduler
							:_mapAllRequestsWithPixStreamId( 
								pixStreamId, logRequest, rxTest )

						local ozAssetId, revId, ozBinaryType, ozBinarySubtype = 
								getRequestInfo( requested )

						if shouldFakeDownloadSucceed then

							rxTest:logf( "sending fake blob on mockDownloadStream %s %s",
											ozBinaryType, ozBinarySubtype )

							mockDownloadStream
								:onNext( 
									true, 
									KSData.make( "mockDownload" ),
									revId, ozAssetId, ozBinaryType,
									ozBinarySubtype )

						else

							rxTest:logf( "sending error mockDownloadStream %s %s",
											ozBinaryType, ozBinarySubtype )

							mockDownloadStream
								:onNext( 
									false, 
									string.format( "fake download fail %s", i ),
									revId, ozAssetId, ozBinaryType,
									ozBinarySubtype )

						end

					end

					return Rx.Observable.empty()

				end 
				
			) 

	) 

	rxTest:log( "done waiting for need to fulfill requests" )

end

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

local function reverse( t )

	local reversed = {}

	local n = #t

	for i = n, 1, -1 do

		reversed[ n - i + 1 ] = t[ i ]
	end

	return reversed

end

--------------------------------------------------------------------------------
-- This test covers the following permutations:
-- 
-- 1) have or do not have a current filmstrip request for our image id
-- 2) download succeeds or fails
-- 3) have maximum downloadable renditions sizes thumbnail2x - 8192

local function rxTestDownloadRenditionPermutations( rxTest )

	local scheduler, catalog = makeSchedulerForTest( rxTest )

	scheduler:setPreferRenderForNewRenditions( true )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.allowlistAllTasks( scheduler )

	rxTest:log( "Adding an asset id with downloadable orig and renditions" )

	local localAssetId

	local sizesToTest = reverse( WFCatRenderSize.defaultRenderSizes )

	table.insert( sizesToTest, 1, "fullsize" )

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

		local savedPixelPath, renditionPixstreamId
		
		for _, shouldFakeDownloadSucceed in ipairs( { true, false } ) do

			for _, maxDownloadableRenditionSize in ipairs( sizesToTest ) do

				local longSideInPixels =
					maxDownloadableRenditionSize == "fullsize" and 
						9000 or maxDownloadableRenditionSize

				local croppedW, croppedH = 
						longSideInPixels + 2,
						longSideInPixels + 1

				rxTest:logf( "==============================Running test with %sfilmstrip rendition, download will%s succeed for max download size of %s",
								shouldHaveFilmstripRendition and "" or "NO ",
								shouldFakeDownloadSucceed and "" or " NOT",
								maxDownloadableRenditionSize )

				local assetId = KSIdGenerator.uuidInLowercaseHex()

				localAssetId = ( localAssetId or 0 ) + 1

				local docStoreRecord =
					makeDocStoreRecordOriginalAndRenditions(
						assetId, localAssetId, _validOrigPath, croppedW, croppedH )

				addMaxDownloadableRenditionSizeToDocStore( 
					rxTest, catalog, docStoreRecord,
					maxDownloadableRenditionSize )

				if shouldHaveFilmstripRendition then

					savedPixelPath, renditionPixstreamId =
						makeRenditionByRendering( 
							rxTest, scheduler, localAssetId, 320, croppedW )

				end

				local mockBinaryRequester = setupMockBinaryRequester( scheduler, 1 )

				local loupeRenditionPixStreamId = "loupeRenditionPixStreamId"

				local pixelPathStreamFilteredToLoupeRendition = 
						rxTest:observe( 
							scheduler
								:pixelPathStream()
								:where( firstMatches( loupeRenditionPixStreamId ) )
								:map( makePixStreamTuple( rxTest ) ) )

				local maybeSendLoupeRenditionPathFromInMemoryCacheForRequestTestMessageStream = 
						rxTest:observe( 
							makeTestMessageStreamMatchingMessage( 
								scheduler, 
								"maybeSendLoupeRenditionPathFromInMemoryCacheForRequest" ) )

				local sendFinalLoupeRenditionPathForRequestTestMessageStream = 
						rxTest:observe( 
							makeTestMessageStreamMatchingMessage( 
									scheduler, 
									"sendFinalLoupeRenditionPathForRequest" )
								:map(
									function( packedResults )
										
										local pixStreamId, thelocalAssetId, transition, maybePath = unpack( packedResults )

										return { pixStreamId, thelocalAssetId, transition, not not maybePath }

									end ) )

				addLoupeRenditionRequest( 
					rxTest, scheduler, loupeRenditionPixStreamId, localAssetId,
					PixelPathProviderRequestTypeNames.grid_prefetch )

				if shouldHaveFilmstripRendition then

					rxTest:logf( "making sure we got initial path on %s", loupeRenditionPixStreamId )

					pixelPathStreamFilteredToLoupeRendition
						:expectData( 
							{ 
								loupeRenditionPixStreamId, 
								localAssetId,
								true,
								"pixelPath",
								"digest",
								320,
								croppedW
							} )

				end

				rxTest:log( "maybeSendLoupeRenditionPathFromInMemoryCacheForRequestTestMessageStream check" )

				maybeSendLoupeRenditionPathFromInMemoryCacheForRequestTestMessageStream
					:expectData( 
						{ 
							loupeRenditionPixStreamId, 
							localAssetId,
							shouldHaveFilmstripRendition and 
								( longSideInPixels > 320 and 
									"sent_path_need_further_check" or "sent_path" ) or
								"no_suitable_rendition_need_further_check"
						} )

				rxTest:logf( "%s request state before fulfillAllPendingFakeDownloadRequest",
								loupeRenditionPixStreamId )

				if not shouldHaveFilmstripRendition or
						longSideInPixels > 320 then

					scheduler
						:_mapAllRequestsWithPixStreamId( 
							loupeRenditionPixStreamId, logRequest, rxTest )

					rxTest:log( "waitUntilFulfillAllPendingFakeDownloadRequests" )

					waitUntilFulfillAllPendingFakeDownloadRequests( 
						rxTest, scheduler, loupeRenditionPixStreamId, mockBinaryRequester,
						shouldFakeDownloadSucceed )

					rxTest:logf( "%s request state after fulfillAllPendingFakeDownloadRequest",
									loupeRenditionPixStreamId )
					scheduler
						:_mapAllRequestsWithPixStreamId( 
							loupeRenditionPixStreamId, logRequest, rxTest )

					rxTest:log( "sendFinalLoupeRenditionPathForRequestTestMessageStream check" )

					if shouldFakeDownloadSucceed then

						sendFinalLoupeRenditionPathForRequestTestMessageStream
							:expectData( 
							  { 
								  loupeRenditionPixStreamId, 
								  localAssetId, 
								  'sent_path',
								  true 
							  } )

					else

						pixelPathStreamFilteredToLoupeRendition
							:expectData( 
								{ 
									loupeRenditionPixStreamId, 
									localAssetId,
									false,
									"SendPermanentDownloadError"
								} )

					end

				end

				rxTest:logf( "abandonAllPixStreamRequests: %s", loupeRenditionPixStreamId )

				scheduler:abandonAllPixStreamRequests( loupeRenditionPixStreamId )

			end

		end

		if savedPixelPath then

			WFFileUtils.delete( savedPixelPath )

			rxTest:logf( "deleted %s", savedPixelPath )

			rxTest:expectEqual( false, WFFileUtils.exists( savedPixelPath ) )

		end

		if renditionPixstreamId then

			rxTest:logf( "abandonAllPixStreamRequests: %s", renditionPixstreamId )
			
			scheduler:abandonAllPixStreamRequests( renditionPixstreamId )

		end

	end

end

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

local function rxTestFullSizeLocalRender( rxTest )

	local scheduler, catalog = makeSchedulerForTest( rxTest )

	scheduler:setPreferRenderForNewRenditions( true )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.allowlistAllTasks( scheduler )

	rxTest:log( "Adding an asset id with downloadable orig and renditions" )

	local localAssetId

	local savedPixelPath, renditionPixstreamId

	rxTest:log( "==============================Running test with fullsize rendition" )

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	localAssetId = ( localAssetId or 0 ) + 1

	local docStoreRecord =
			makeDocStoreRecordOriginalAndRenditions(
				assetId, localAssetId, _validOrigPath, 3264, 2448 )

	addMaxDownloadableRenditionSizeToDocStore( 
		rxTest, catalog, docStoreRecord,
		2048 )

	savedPixelPath, renditionPixstreamId =
		makeRenditionByRendering( 
			rxTest, scheduler, localAssetId, 3264, 3264 )

	local loupeRenditionPixStreamId = "loupeRenditionPixStreamId"

	local pixelPathStreamFilteredToLoupeRendition = 
			rxTest:observe( 
				scheduler
					:pixelPathStream()
					:where( firstMatches( loupeRenditionPixStreamId ) )
					:map( makePixStreamTuple( rxTest ) ) )

	local maybeSendLoupeRenditionPathFromInMemoryCacheForRequestTestMessageStream = 
			rxTest:observe( 
				makeTestMessageStreamMatchingMessage( 
					scheduler, 
					"maybeSendLoupeRenditionPathFromInMemoryCacheForRequest" ) )

	addLoupeRenditionRequest( 
		rxTest, scheduler, loupeRenditionPixStreamId, localAssetId,
		PixelPathProviderRequestTypeNames.grid_prefetch )

	rxTest:logf( "making sure we got initial path on %s", loupeRenditionPixStreamId )

	pixelPathStreamFilteredToLoupeRendition
		:expectData( 
			{ 
				loupeRenditionPixStreamId, 
				localAssetId,
				true,
				"pixelPath",
				"digest",
				"full",
				3264
			} )

	rxTest:log( "maybeSendLoupeRenditionPathFromInMemoryCacheForRequestTestMessageStream check" )

	maybeSendLoupeRenditionPathFromInMemoryCacheForRequestTestMessageStream
		:expectData( 
			{ 
				loupeRenditionPixStreamId, 
				localAssetId,
				"sent_path"
			} )

	rxTest:logf( "abandonAllPixStreamRequests: %s", loupeRenditionPixStreamId )

	scheduler:abandonAllPixStreamRequests( loupeRenditionPixStreamId )

	if savedPixelPath then

		WFFileUtils.delete( savedPixelPath )

		rxTest:logf( "deleted %s", savedPixelPath )

		rxTest:expectEqual( false, WFFileUtils.exists( savedPixelPath ) )

	end

	if renditionPixstreamId then

		rxTest:logf( "abandonAllPixStreamRequests: %s", renditionPixstreamId )
		
		scheduler:abandonAllPixStreamRequests( renditionPixstreamId )

	end

end

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

local function bumpTimeLimit( f, n )

	return function( rxTest )

		rxTest:setTimeLimit( n )

		return f( rxTest )

	end

end

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

return WFRxTestCase.wrapSuite( 'LoupeRenditionStateMachineTest', {

	rxTestDownloadRenditionPermutations = 	
			bumpTimeLimit( 
				rxTestDownloadRenditionPermutations, 32 ),

	rxTestFullSizeLocalRender = 	
			bumpTimeLimit( 
				rxTestFullSizeLocalRender, 10 ),


} )
