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

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

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

	-- wf.core
local WFFiniteStateMachine = require 'WFFiniteStateMachine'
local KSTableUtils = require 'KSTableUtils'

	-- pixel provider
local PixelPathProviderStateMachine = require 'PixelPathProviderStateMachine'
local PixelProviderSharedTaskUtils = require 'PixelProviderSharedTaskUtils'
local PixelProviderSharedBlobCache = require 'PixelProviderSharedBlobCache'
local WFCatRenderSize = require 'WFCatRenderSize'
local NmbLocalRenditionDigest = require 'NmbLocalRenditionDigest'
local NmbRenditionStrategy = require 'NmbRenditionStrategy'
local WFFileUtils = require 'WFFileUtils'

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

local logger = require 'WFLogger'( 'PixelProviderChooseRenditionStrategyTask' )

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

local PixelCacheCheckStates = 
	PixelPathProviderStateMachine.PixelCacheCheckStates

--==============================================================================
-- choose pixel strategy 
--==============================================================================

local SaveRenderStates = PixelPathProviderStateMachine.SaveRenderStates

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

local needSaveRenderStates = 
			{ WFFiniteStateMachine.makeRetryableStates(
					PixelPathProviderStateMachine.SaveRenderRetryCount, 
					SaveRenderStates.NeedToSaveRender ) }

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

local PixelProviderSharedBlobCache_getEquivalentRenditionRequests =
		PixelProviderSharedBlobCache.getEquivalentRenditionRequests

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

local function canJoinPendingSave( scheduler, activeRenditionInfoCache,
									localAssetId, renderSize, digest )

	local currentPendingSaveRequests = 
			PixelProviderSharedBlobCache_getEquivalentRenditionRequests( 
				scheduler, activeRenditionInfoCache, localAssetId,
				renderSize, digest, unpack( needSaveRenderStates ) )

	return #currentPendingSaveRequests > 0

end

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

local isSavingStates =
			{ WFFiniteStateMachine.makeRetryableStates(
					PixelPathProviderStateMachine.SaveRenderRetryCount, 
					SaveRenderStates.SavingRender ) }

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

local function canJoinInProgressSave( scheduler, activeRenditionInfoCache,
										localAssetId, renderSize, digest )

	local currentInProgressSaveRequests = 
			PixelProviderSharedBlobCache_getEquivalentRenditionRequests( 
				scheduler, activeRenditionInfoCache, localAssetId,
				renderSize, digest, unpack( isSavingStates ) )

	return #currentInProgressSaveRequests > 0

end

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

local canRenderWithProxy = 
		assert( WFCatRenderSize.canRenderWithProxy,
				"WFCatRenderSize.canRenderWithProxy no defined" )

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

local function hasAtLeastOneLocalNegativeForRender( currOriginalPath, currProxyPath,
											neededLongSide )

	return currOriginalPath ~= nil or
			currProxyPath and canRenderWithProxy( neededLongSide )

end

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

local function hasLocalProxySuitableForRender( currProxyPath, neededLongSide )

	return currProxyPath and canRenderWithProxy( neededLongSide )

end

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

local function maybeGetTempDigestAndRenditionPathForMissingProfile( 
	renditionPathBaseDir, pixStreamId, 
	localAssetId, baseRenderInfo, activeRenditionInfo )

	if baseRenderInfo.hasStandardProfile and baseRenderInfo.cameraProfileMissing then

		logger:warnf( "Render with digest for missing profile %s. %s: no profile",
						pixStreamId, localAssetId )

		logger:warnf( "Standard profile unavailable, include missing info %s", localAssetId )
		
		local temporaryDigest = 
				NmbLocalRenditionDigest.computeTemporaryDefaultForOrientation( 
					baseRenderInfo.ozAssetId or localAssetId, 
					activeRenditionInfo.devSettings, activeRenditionInfo.videoDevSettings, 
					activeRenditionInfo.userOrientation, activeRenditionInfo.temporal,
					baseRenderInfo.cameraProfileDigest, baseRenderInfo.cameraProfileMissing,
					true )
		
		logger:warnf( "Actual digest:%s Temp Digest:%s", activeRenditionInfo.digest, temporaryDigest )
		
		local tempPath = require "WFCatPixelPathGenerator".pathForPreviewDigestAndLongEdge( 
			renditionPathBaseDir, baseRenderInfo.ozAssetId, 
			temporaryDigest, activeRenditionInfo.renderSize )
		
		logger:warnf( "Actual path:%s Temp path:%s", activeRenditionInfo.renditionPath, tempPath )
		
		return temporaryDigest, tempPath, activeRenditionInfo.renderSize

	end

end

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

local function maybeChooseNonRenderState( request, scheduler,
											baseRenderInfoCache,
											activeRenditionInfoCache )

	local pixStreamId, localAssetId = unpack( request )

	local activeRenditionInfo = activeRenditionInfoCache:peekCacheItem( 
										pixStreamId, localAssetId )
	
	if not activeRenditionInfo then

		logger:warnf( "Can't choose pixel strategy for %s.%s: no renderInfo",
						pixStreamId, localAssetId )

		-- force it to retrieve again
		return scheduler:_applyTransitionToRequest( 
							request, 'new_docstore_render_info' )

	end
	
	local wantedDigest = activeRenditionInfo.digest
	
	local renderSize = activeRenditionInfo.renderSize

	if canJoinPendingSave( scheduler, activeRenditionInfoCache,
								localAssetId, renderSize, wantedDigest ) then

		return scheduler:_applyTransitionToRequest( 
							request, 'has_pending_rendition_save' )

	end
	
	if canJoinInProgressSave( scheduler, activeRenditionInfoCache,
								localAssetId, renderSize, wantedDigest ) then

		return scheduler:_applyTransitionToRequest( 
							request, 'has_active_rendition_save' )

	end

	local baseRenderInfo = baseRenderInfoCache:peekCacheItem( localAssetId )
	
	if not baseRenderInfo then

		logger:warnf( "Can't choose pixel strategy for %s.%s: no renderInfo",
						pixStreamId, localAssetId )

		-- force it to retrieve again
		return scheduler:_applyTransitionToRequest( 
							request, 'new_docstore_render_info' )

	end

end

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

local function maybeGetConfirmLocalPathTransition( 
					baseRenderInfo, renderSize )

	local currOriginalPath, currProxyPath, originalCannotBeRendered = 
			baseRenderInfo.originalPath, baseRenderInfo.proxyPath, baseRenderInfo.originalCannotBeRendered
			
	if originalCannotBeRendered then
		currOriginalPath = nil
	end

	return
		( hasAtLeastOneLocalNegativeForRender( currOriginalPath, currProxyPath, 
												renderSize ) and 
		  'confirm_any_local_negative_for_render' ) or
		( hasLocalProxySuitableForRender( currProxyPath, renderSize ) and 
		  'confirm_local_proxy_for_render' )

end

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

local function maybeChooseLocalRender( request, scheduler, baseRenderInfo,
										renderSize, renditionPathBaseDir, pixStreamId, 
										localAssetId, activeRenditionInfo, 
										baseRenderInfoCache, activeRenditionInfoCache,
										renditionDiskStatusCache )

	local checkNegativeTransition =
			maybeGetConfirmLocalPathTransition( baseRenderInfo, renderSize )

	if not checkNegativeTransition then
		-- we couldn't find a proxy/negative, give up
		return
	end

	-- we are probably going to render, check to make sure we have any
	-- needed camera/lens profile files. If we do not, we will be rendering
	-- without them and will be using the missingProfileDigest as the
	-- new digest
	local missingProfileRenderDigest, missingProfileRenditionPath, 
				missingProfileRenditionRenderSize =
			maybeGetTempDigestAndRenditionPathForMissingProfile( 
				renditionPathBaseDir,
				pixStreamId, 
				localAssetId, 
				baseRenderInfo, 
				activeRenditionInfo )

	if missingProfileRenderDigest then

		-- we're going to be serving up a missing profile rendition path
		--
		-- First update the caches with the new digest and path
		baseRenderInfo.digest = missingProfileRenderDigest
			
		baseRenderInfoCache:pushCacheItem( localAssetId, baseRenderInfo )

		activeRenditionInfo.digest = missingProfileRenderDigest
		activeRenditionInfo.renditionPath = missingProfileRenditionPath
			
		activeRenditionInfoCache:pushCacheItem( pixStreamId, localAssetId,
										activeRenditionInfo )

		-- now check if the missing profile rendition already exists (Normally
		-- the asset rendition path check is done in the
		-- PixelProviderPixelCacheCheckTask, but that task only uses the "real"
		-- digests.)
		local missingProfileRenditionStatus = 
				WFFileUtils.exists( missingProfileRenditionPath )

		if missingProfileRenditionStatus then

			if missingProfileRenditionStatus == "file" then
				-- Since we are using a temporary digest based path and
				-- checking the path here, we stash missing profile rendition
				-- path to the cache that the scheduler expects paths to be
				-- found in.
				renditionDiskStatusCache
					:addRendition( 
						localAssetId, missingProfileRenditionRenderSize,
						missingProfileRenderDigest, missingProfileRenditionPath )

			end

			-- Now transition to a send path or send error state.
			local missingProfileRenditionStatusTransition =
				missingProfileRenditionStatus == "file" and
					'have_missing_profile_local_rendition' or
					-- bad_missing_profile_local_rendition_state is
					-- extremely unlikely- requires a rendition path
					-- entry to exist as a folder
					'bad_missing_profile_local_rendition_state'

			return scheduler
					:_applyTransitionToRequest( 
						request, missingProfileRenditionStatusTransition ) 

		end

	end

	-- otherwise start the local proxy/orig path check that is required before
	-- we attempt the render
	return scheduler:_applyTransitionToRequest( 
								request, checkNegativeTransition )

end

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

local NmbRenditionStrategy_renditionDownloadsProhibited =
			assert( NmbRenditionStrategy.renditionDownloadsProhibited,
					"missing NmbRenditionStrategy.renditionDownloadsProhibited" )

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

local PixelProviderSharedBlobCache_maybeGetStandardSizedDownloadableRenditionName =
			assert( PixelProviderSharedBlobCache.maybeGetStandardSizedDownloadableRenditionName,
					"missing PixelProviderSharedBlobCache.maybeGetStandardSizedDownloadableRenditionName" )

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

local function haveExactMatchDownloadableRendition( baseRenderInfoCache, localAssetId, renderSize )

	if NmbRenditionStrategy_renditionDownloadsProhibited() then

		return false

	end

	local maybeDownloadableRenditionSizeName = 
			PixelProviderSharedBlobCache_maybeGetStandardSizedDownloadableRenditionName(
				baseRenderInfoCache, localAssetId, renderSize )
			
	return maybeDownloadableRenditionSizeName ~= nil
	
end

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

local PixelProviderSharedBlobCache_getBestMatchRenditionForRenderInfo =
			assert( PixelProviderSharedBlobCache.getBestMatchRenditionForRenderInfo,
					"missing PixelProviderSharedBlobCache.getBestMatchRenditionForRenderInfo" )

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

local PixelProviderSharedTaskUtils_ozRenditionNameToRenderSize =
		assert( PixelProviderSharedTaskUtils.ozRenditionNameToRenderSize,
				"PixelProviderSharedTaskUtils.ozRenditionNameToRenderSize is missing" )

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

local function maybeTryDownloadBestMatchRenditionImp( 
				scheduler, request, maybeGetBestMatchDownloadableRenditionFunc, 
				tryDownloadTransition, renderInfo, renderSize )

	if NmbRenditionStrategy_renditionDownloadsProhibited() then

		return false

	end

	local pixStreamId, localAssetId = unpack( request )

	local maybeDownloadableRenditionSizeName = 
		maybeGetBestMatchDownloadableRenditionFunc(
				renderInfo, localAssetId, renderSize )

	if not maybeDownloadableRenditionSizeName then

		return false

	end

	local bestRenditionSize = PixelProviderSharedTaskUtils_ozRenditionNameToRenderSize(
						maybeDownloadableRenditionSizeName )
	
	-- if the size is a numeric string, convert it to a number, otherwise keep the string.
	bestRenditionSize = tonumber( bestRenditionSize ) or bestRenditionSize
	
	-- we expect either a numeric rendition size or a video size string here	
	local bestRenditionSizeIsUsable =
			type( bestRenditionSize ) == "number" or
			( type( bestRenditionSize ) == "string" and bestRenditionSize ~= "full" )

	if not bestRenditionSizeIsUsable then
		return false
	end

	if bestRenditionSize ~= renderSize then

		local newLongSideState =
			scheduler
				:maybeUpdateLongSide( pixStreamId, localAssetId, bestRenditionSize )

		if newLongSideState then

			-- we have a downloadable rendition, but it's not the right size
			-- and we updated the request long side which has has reset the
			-- state machine the state machine. It will eventually return to
			-- ChooseRenditionStrategy but the requested size will match a
			-- downloadable size..
			return newLongSideState

		end

	end

	-- we have a rendition of the right size, transition to downloading it
	return scheduler
			:_applyTransitionToRequest( 
				request, tryDownloadTransition )

end

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

local function maybeTryDownloadBestMatchRendition( 
				scheduler, request, renderInfo, renderSize )

	return maybeTryDownloadBestMatchRenditionImp( 
			scheduler, request, 
			PixelProviderSharedBlobCache_getBestMatchRenditionForRenderInfo,
			'try_download_rendition', renderInfo, renderSize )

end

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

local PixelProviderSharedBlobCache_getBestMatchInvalidRenditionForRenderInfo =
			assert( PixelProviderSharedBlobCache.getBestMatchInvalidRenditionForRenderInfo,
					"missing PixelProviderSharedBlobCache.getBestMatchInvalidRenditionForRenderInfo" )

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

local function maybeTryDownloadInvalidRendition( 
				scheduler, request, renderInfo, renderSize )

	return maybeTryDownloadBestMatchRenditionImp( 
			scheduler, request, 
			PixelProviderSharedBlobCache_getBestMatchInvalidRenditionForRenderInfo,
			'try_download_invalid_rendition', renderInfo, renderSize )

end

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

local PixelProviderSharedTaskUtils_mustUseOzForRendersForCachedBaseRenderInfo =
		assert( require "PixelProviderSharedTaskUtils".mustUseOzForRendersForCachedBaseRenderInfo,
				"PixelProviderSharedTaskUtils.mustUseOzForRendersForCachedBaseRenderInfo is missing!" )

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

local PixelProviderSharedTaskUtils_renderInfoHasDownloadableProxy =
		assert( PixelProviderSharedTaskUtils.renderInfoHasDownloadableProxy,
			"PixelProviderSharedTaskUtils.renderInfoHasDownloadableProxy is missing!" )

--------------------------------------------------------------------------------
	
local NmbRenditionStrategy_renderInfoHasDownloadableOriginal =
		assert( NmbRenditionStrategy.renderInfoHasDownloadableOriginal,
			"NmbRenditionStrategy.renderInfoHasDownloadableOriginal is missing!" )

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

local function mustReadXmpDevelopSettingsIntoMemory( 
					baseRenderInfo, currRenderSettingsCache, localAssetId )

	if baseRenderInfo.isVideo then

		-- as of 20220830, video files do not use image xmp develop settings
		return false

	end

	local _, _, xmpSettings = 
			unpack( currRenderSettingsCache:peekCacheItem( localAssetId ) or {} )

	if xmpSettings then

		-- we have xmp settings, we don't need to sync external xmp
		return false

	end

	return true

end 

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

local function applyNeedsXmpDevelopTransition( 
					scheduler, currRenderSettingsCache, request )

	local _, localAssetId = unpack( request )

	currRenderSettingsCache:popCacheItem( localAssetId )
		-- clear out any previous render settings while we try to fetch the new 
		-- external settings
	
	-- start fetching the external develop settings
	return scheduler
			:_applyTransitionToRequest( request, 'need_develop_xmp' )

end

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

-- 20230721 bkruse 
--
-- See NMB-25460 for details, but the gist is that some rendition URIs are 
-- rendered on the fly by Oz. If we can render locally, it's assumed it
-- will be faster than requesting Oz to do the render and then download it.
local _renderSizeMayTriggerOzRenderSet = KSTableUtils.mapArrayToSet( { 1280 } )

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

local function transitionRequestToWaitForRenderDataState( scheduler, request )

	-- we can't render or download, this transition will put us in a 'wait'
	-- state that will be lifted if we get any new download links from Oz
	return scheduler:_applyTransitionToRequest( request, 'wait_for_render_data_updates' )

end

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

local function shortPixstreamId( pixStreamId )
	
	pixStreamId = tostring( pixStreamId )

	if string.find( pixStreamId, "[Gg]rid" ) then

		return "grid"

	end

	if string.find( pixStreamId, "[Ff]ilmst" ) then

		return "filmstrip"

	end

	return "non-ui"

end

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

shortPixstreamId = require "KSFunctionUtils".memoizeFn( shortPixstreamId )

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

local function logfRequestRenditionChoice( request, logStr, ... )

	logStr = type( logStr ) == 'string' and logStr or "bad logStr"

	local pixStreamId, localAssetId, renderSize = unpack( request )

	logStr = select( '#', ... ) > 0 and string.format( logStr, ... ) or logStr

	logger:infof( "%s %s rendition choice for id %s: %s", 
					shortPixstreamId( pixStreamId ), renderSize, localAssetId, 
					logStr )

end

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

local maybeLogfRequestRenditionChoice = logger.will_info and 
		logfRequestRenditionChoice or function() end

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

local function chooseRenditionStrategy( request, scheduler, renditionPathBaseDir, baseRenderInfoCache,
											activeRenditionInfoCache,
											currRenderSettingsCache, renditionDiskStatusCache,
											updatedDevelopSettingsAssetIds )

	-- maybe we _shouldn't_ render?
	local maybeNonRenderState = 
			maybeChooseNonRenderState( 
				request, scheduler, baseRenderInfoCache,
				activeRenditionInfoCache )

	if maybeNonRenderState then

		-- we are not going to render
		maybeLogfRequestRenditionChoice( 
			request, "not rendering (%s)", maybeNonRenderState )

		return maybeNonRenderState

	end

	local pixStreamId, localAssetId = unpack( request )

	local activeRenditionInfo = 
			activeRenditionInfoCache
				:peekCacheItem( pixStreamId, localAssetId ) or {}
	
	local renderSize = activeRenditionInfo.renderSize
	
	local baseRenderInfo = 
			baseRenderInfoCache
				:peekCacheItem( localAssetId ) or {}

	-- We forbid local renders for assets in shared albums
	local mustNotRenderLocally =
			PixelProviderSharedTaskUtils_mustUseOzForRendersForCachedBaseRenderInfo( 
				baseRenderInfo )

	local hasExactMatchDownloadableRendition =
			haveExactMatchDownloadableRendition( 
				baseRenderInfoCache, localAssetId, renderSize )

	if mustNotRenderLocally then

		-- turns out we must download our rendition
		if hasExactMatchDownloadableRendition then
		
			-- we will try to download an exact match rendition
			maybeLogfRequestRenditionChoice( 
				request, "download exact match (must not render)" )

			return scheduler:_applyTransitionToRequest( 
					request, 'try_download_rendition' )

		end

		local maybeTryDownloadBestMatchRenditionState =
				maybeTryDownloadBestMatchRendition( 
					scheduler, request, baseRenderInfo, renderSize )

		if maybeTryDownloadBestMatchRenditionState then

			-- we will try to download a best size with current settings
			-- rendition
			maybeLogfRequestRenditionChoice( 
				request, "download best match (must not render)" )

			return maybeTryDownloadBestMatchRenditionState

		end

		local maybeTryDownloadInvalidRenditionState =
				maybeTryDownloadInvalidRendition( 
					scheduler, request, baseRenderInfo, renderSize )

		if maybeTryDownloadInvalidRenditionState then

			-- we will try to download a stale settings rendition
			maybeLogfRequestRenditionChoice( 
				request, "download invalid rendition (must not render)" )

			return maybeTryDownloadInvalidRenditionState

		end

		-- we must download rendition but couldn't find anything to download,
		-- transition to a wait state
		maybeLogfRequestRenditionChoice( 
			request, "wait for Oz update (render not allowed and no download)" )

		return transitionRequestToWaitForRenderDataState( scheduler, request )

	end

	local hasCustomProfileLocally = 
			baseRenderInfo.cameraProfileDigest and 
			not baseRenderInfo.cameraProfileMissing and 
			not baseRenderInfo.hasStandardProfile

	local hasLocallyInitiatedEdits = updatedDevelopSettingsAssetIds[ localAssetId ]

	local okToDownloadInsteadOfRendering = 
			not( hasCustomProfileLocally or hasLocallyInitiatedEdits )
	
	local canDownloadExactMatchRendition =
			okToDownloadInsteadOfRendering and hasExactMatchDownloadableRendition

	local localRenderPreferredAllRenditions =
			not scheduler:shouldPreferDownloadsForNewRenditions()

	local localRenderProbablyRequiresNegativeDownload = 
			not maybeGetConfirmLocalPathTransition( 
				baseRenderInfo, renderSize ) 

	local localRenderPreferredThisRendition =
			not localRenderProbablyRequiresNegativeDownload and
					_renderSizeMayTriggerOzRenderSet[ renderSize ] or 
					false

	local localRenderPreferred =
			localRenderPreferredAllRenditions or
			localRenderPreferredThisRendition
	
	if not localRenderPreferred and canDownloadExactMatchRendition then
	
		-- try download render
		maybeLogfRequestRenditionChoice( 
			request, "download exact match (prefer not render)" )

		return scheduler:_applyTransitionToRequest( 
				request, 'try_download_rendition' )

	end

	-- At this point, we either
	--
	-- 	* cannot download our rendition or 
	-- 	* prefer to render instead of downloading. 
	--
	-- So now gather the data we need to make a render happen and default to
	-- downloading only if we cannot render locally.
	
	-- Start with external dev settings in memory check
	local mustReadXmpDevelopSettings =
			mustReadXmpDevelopSettingsIntoMemory( 
				baseRenderInfo, currRenderSettingsCache, localAssetId )

	-- external dev settings are usually synced at the same time as orig or
	-- proxy. so if we have to download a proxy or orig, there's a good chance
	-- will need to download external dev settings too
	local xmpSettingsAndNegativeProbablyRequireDownload =
			mustReadXmpDevelopSettings and
			localRenderProbablyRequiresNegativeDownload

	if xmpSettingsAndNegativeProbablyRequireDownload and 
			canDownloadExactMatchRendition then

		-- if we have to sync external dev settings, it will take a long time
		-- to download all the pieces needed to do a local render.
		--
		-- In that case, we ignore localRenderPreferred and attempt a rendition
		-- download instead.
		maybeLogfRequestRenditionChoice( 
			request, "download exact match (xmp download needed)" )

		return scheduler
					:_applyTransitionToRequest( 
						request, 'try_download_rendition' )

	end

	if mustReadXmpDevelopSettings then

		-- kick off state machine that reads local xmp develop settings into
		-- memory
		maybeLogfRequestRenditionChoice( 
			request, "start local render read external xmp" )

		return applyNeedsXmpDevelopTransition( 
				scheduler, currRenderSettingsCache, request )
	
	end

	-- At this point we know we have the settings in memory, now do some
	-- additional checks to see if we have a suitable local negative.
	
	local maybeLocalRenderState = 
			maybeChooseLocalRender( request, scheduler, baseRenderInfo,
										renderSize, renditionPathBaseDir, pixStreamId, 
										localAssetId, activeRenditionInfo, 
										baseRenderInfoCache, activeRenditionInfoCache,
										renditionDiskStatusCache )

	if maybeLocalRenderState then

		-- we can render locally
		maybeLogfRequestRenditionChoice( 
			request, "start local render %s", maybeLocalRenderState )

		return maybeLocalRenderState

	end
	
	-- At this point, local render isn't possible without a download.
	--
	-- In priority order, attempt download available rendition, available
	-- proxy, or available original.
	
	if canDownloadExactMatchRendition then

		-- 1) download rendition if exact match
		maybeLogfRequestRenditionChoice( 
			request, "download exact match (negative download needed)" )

		return scheduler:_applyTransitionToRequest( 
				request, 'try_download_rendition' )

	end
	
	if PixelProviderSharedTaskUtils_renderInfoHasDownloadableProxy(
				baseRenderInfo ) and 
			canRenderWithProxy( renderSize ) then
		
		-- 2) download proxy if avail and big enough for render
		maybeLogfRequestRenditionChoice( request, "download proxy for render" )

		return scheduler:_applyTransitionToRequest( 
							request, 'try_download_proxy' )

	end
	
	if NmbRenditionStrategy_renderInfoHasDownloadableOriginal(
			baseRenderInfo ) then
		
		-- 3) download original if avail
		maybeLogfRequestRenditionChoice( request, "download orig for render" )

		return scheduler:_applyTransitionToRequest( 
							request, 'try_download_original' )
											
	end

	-- At this point there's no hope of an exact match current settings
	-- rendition, fall back to "best match" rendition download in order
	-- of preference.
	--
	-- In priority order, attempt download available best match rendition or
	-- available invalid rendition.
	
	local maybeTryDownloadBestMatchRenditionState =
			maybeTryDownloadBestMatchRendition( 
				scheduler, request, baseRenderInfo, renderSize )

	if maybeTryDownloadBestMatchRenditionState then

		-- 1) download closest match rendition
		maybeLogfRequestRenditionChoice( 
			request, "download best match (cannot render or download)" )

		return maybeTryDownloadBestMatchRenditionState

	end

	local maybeTryDownloadInvalidRenditionState =
			maybeTryDownloadInvalidRendition( 
				scheduler, request, baseRenderInfo, renderSize )

	if maybeTryDownloadInvalidRenditionState then

		-- 2) download invalid rendition
		maybeLogfRequestRenditionChoice( 
			request, "download invalid rendition (cannot render or download)" )

		return maybeTryDownloadInvalidRenditionState

	end

	-- wait for render data (we can't render or download)
	maybeLogfRequestRenditionChoice( 
		request, "wait for Oz update (cannot render or download)" )

	return transitionRequestToWaitForRenderDataState( scheduler, request )

end

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

local function chooseRenderOnlyRenditionStrategy( 
					request, scheduler, renditionPathBaseDir, baseRenderInfoCache,
					activeRenditionInfoCache, currRenderSettingsCache,
					renditionDiskStatusCache )

	-- maybe we _shouldn't_ render?
	local maybeNonRenderState = 
			maybeChooseNonRenderState( 
				request, scheduler, baseRenderInfoCache,
				activeRenditionInfoCache )

	if maybeNonRenderState then

		-- we are not going to render
		maybeLogfRequestRenditionChoice( 
			request, "not rendering (%s)", maybeNonRenderState )

		return maybeNonRenderState

	end

	local pixStreamId, localAssetId = unpack( request )

	local baseRenderInfo = baseRenderInfoCache:peekCacheItem( localAssetId )

	-- We first need to download/read any external XMP settings in order to render.
	
	local mustReadXmpDevelopSettings =
			mustReadXmpDevelopSettingsIntoMemory( 
				baseRenderInfo, currRenderSettingsCache, localAssetId )

	if mustReadXmpDevelopSettings then
		
		-- kick off state machine that reads local xmp develop settings into
		-- memory
		maybeLogfRequestRenditionChoice( 
			request, "start local render read external xmp" )

		return applyNeedsXmpDevelopTransition( 
				scheduler, currRenderSettingsCache, request )
	
	end

	local activeRenditionInfo = activeRenditionInfoCache:peekCacheItem( 
										pixStreamId, localAssetId )
	
	local renderSize = activeRenditionInfo.renderSize

	local maybeLocalRenderState = 
			maybeChooseLocalRender( request, scheduler, baseRenderInfo,
										renderSize, renditionPathBaseDir, pixStreamId, 
										localAssetId, activeRenditionInfo, 
										baseRenderInfoCache, activeRenditionInfoCache,
										renditionDiskStatusCache )

	if maybeLocalRenderState then

		-- we can render locally
		maybeLogfRequestRenditionChoice( 
			request, "start local render %s", maybeLocalRenderState )

		return maybeLocalRenderState

	end

	-- we can't render locally, transition to wait state
	maybeLogfRequestRenditionChoice( 
		request, "wait for Oz update (cannot local render and download not allowed)" )

	return transitionRequestToWaitForRenderDataState( scheduler, request )

end

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

local function wrapInSendTestData( f )

	return function( _, scheduler, ... )

		local ret = f( _, scheduler, ... )

		scheduler:_sendTestData( "chooseRenditionStrategy", ret )

		return ret

	end

end

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

chooseRenditionStrategy = wrapInSendTestData( chooseRenditionStrategy )

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

local function processRequests( scheduler, taskState, baseRenderInfoCache, 
									activeRenditionInfoCache,
									currRenderSettingsCache,
									renditionDiskStatusCache,
									updatedDevelopSettingsAssetIds )

	-- update all of the ChooseRenditionStrategy requests
	scheduler:_mapAllRequestsWithState( 
		PixelCacheCheckStates.ChooseRenditionStrategy,
			chooseRenditionStrategy, 
				scheduler, taskState.renditionPathBaseDir, 
				baseRenderInfoCache, activeRenditionInfoCache,
				currRenderSettingsCache, 
				renditionDiskStatusCache, updatedDevelopSettingsAssetIds )

	-- update all of the ChooseRenderOnlyRenditionStrategy requests
	scheduler:_mapAllRequestsWithState( 
		PixelCacheCheckStates.ChooseRenderOnlyRenditionStrategy,
			chooseRenderOnlyRenditionStrategy, 
				scheduler, taskState.renditionPathBaseDir, 
				baseRenderInfoCache, activeRenditionInfoCache,
				currRenderSettingsCache, renditionDiskStatusCache )

end
--==============================================================================
-- setupTask
--==============================================================================

local function setupTask( renditionPathBaseDir )
	
	return { 

		renditionPathBaseDir = renditionPathBaseDir,
			
	}

end

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

return {

	setupTask = setupTask,
	processRequests = processRequests,

}
