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

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'

	-- 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 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.userOrientation,
					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

	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 maybeChooseLocalRender( request, scheduler, baseRenderInfo,
										renderSize, renditionPathBaseDir, pixStreamId, 
										localAssetId, activeRenditionInfo, 
										baseRenderInfoCache, activeRenditionInfoCache )

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

	local checkNegativeTransition =
		( hasAtLeastOneLocalNegativeForRender( currOriginalPath, currProxyPath, 
												renderSize ) and 
		  'confirm_any_local_negative_for_render' ) or
		( hasLocalProxySuitableForRender( currProxyPath, renderSize ) and 
		  'confirm_local_proxy_for_render' )

	if not checkNegativeTransition then
		return
	end

	local maybeTempDigest, maybePath =
		maybeGetTempDigestAndRenditionPathForMissingProfile( 
			renditionPathBaseDir,
			pixStreamId, 
			localAssetId, 
			baseRenderInfo, 
			activeRenditionInfo )

	if maybeTempDigest then

		baseRenderInfo.digest = maybeTempDigest
			
		baseRenderInfoCache:pushCacheItem( localAssetId, baseRenderInfo )

		activeRenditionInfo.digest = maybeTempDigest
		activeRenditionInfo.renditionPath = maybePath
			
		activeRenditionInfoCache:pushCacheItem( pixStreamId, localAssetId,
										activeRenditionInfo )
	end

	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 =
			tonumber( PixelProviderSharedTaskUtils_ozRenditionNameToRenderSize(
						maybeDownloadableRenditionSizeName ) )

	if not bestRenditionSize 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 NmbRenditionStrategy_renderInfoHasDownloadableProxy =
		assert( NmbRenditionStrategy.renderInfoHasDownloadableProxy,
			"NmbRenditionStrategy.renderInfoHasDownloadableProxy is missing!" )

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

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

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

	local maybeNonRenderState = 
			maybeChooseNonRenderState( 
				request, scheduler, baseRenderInfoCache,
				activeRenditionInfoCache )

	if maybeNonRenderState then

		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

		if hasExactMatchDownloadableRendition then
		
			return scheduler:_applyTransitionToRequest( 
					request, 'try_download_rendition' )

		end

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

		if maybeTryDownloadBestMatchRenditionState then

			return maybeTryDownloadBestMatchRenditionState

		end

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

		if maybeTryDownloadInvalidRenditionState then

			return maybeTryDownloadInvalidRenditionState

		end

		-- this transition will put us in a 'wait' state that will be lifted
		-- if we get any new rendition links from Oz
		return scheduler:_applyTransitionToRequest( request, 'no_original' )

	end

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

	local hasLocallyInitiatedEdits = updatedDevelopSettingsAssetIds[ localAssetId ]

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

	local preferDownloadsOverRenders = 
			scheduler:shouldPreferDownloadsForNewRenditions()
	
	if preferDownloadsOverRenders and canDownloadInsteadOfRendering then
	
		return scheduler:_applyTransitionToRequest( 
				request, 'try_download_rendition' )

	end

	-- At this point, we either cannot download our rendition or we prefer
	-- to render instead of downloading. So now gather the data we need to make
	-- a render happen and fallback to downloading if we can download but
	-- cannot render

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

	if not xmpSettings then
		
		-- no xmp, so we can't render
		if canDownloadInsteadOfRendering then

			return scheduler:_applyTransitionToRequest( 
					request, 'try_download_rendition' )

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

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

	if maybeLocalRenderState then

		return maybeLocalRenderState

	end
	
	-- we can't render, probably because we don't have local proxy or orig. we
	-- fall back to downloading preferring rendition, proxy, original in that order
	
	-- TODO [matthewj 2016-03-31]: We need to be able to consider the PANO proxy case
	if canDownloadInsteadOfRendering then

		return scheduler:_applyTransitionToRequest( 
				request, 'try_download_rendition' )
	
	elseif NmbRenditionStrategy_renderInfoHasDownloadableProxy(
			baseRenderInfo ) and 
			canRenderWithProxy( renderSize ) then
		
		-- we download a proxy that will work for our render
		return scheduler:_applyTransitionToRequest( 
							request, 'try_download_proxy' )
	
	elseif NmbRenditionStrategy_renderInfoHasDownloadableOriginal(
			baseRenderInfo ) then
		
		-- we can download the master to do a render
		return scheduler:_applyTransitionToRequest( 
							request, 'try_download_original' )
											
	end

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

	if maybeTryDownloadBestMatchRenditionState then

		return maybeTryDownloadBestMatchRenditionState

	end

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

	if maybeTryDownloadInvalidRenditionState then

		return maybeTryDownloadInvalidRenditionState

	end

	-- we can't render and can't download anything. 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, 'no_original' )

end

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

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

	local maybeNonRenderState = 
			maybeChooseNonRenderState( 
				request, scheduler, baseRenderInfoCache,
				activeRenditionInfoCache )

	if maybeNonRenderState then

		return maybeNonRenderState

	end

	local pixStreamId, localAssetId = unpack( request )

	-- We first need to download/read any external XMP settings in order to render.

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

	if not xmpSettings then
		
		currRenderSettingsCache:popCacheItem( localAssetId )
			-- clear out any previous render settings while we try to fetch the new 
			-- external settings
		
		return scheduler:_applyTransitionToRequest( request, 'need_develop_xmp' )
	
	end

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

	local baseRenderInfo = baseRenderInfoCache:peekCacheItem( localAssetId )

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

	if maybeLocalRenderState then

		return maybeLocalRenderState

	end

	return scheduler:_applyTransitionToRequest( request, 'no_original' )

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,
									updatedDevelopSettingsAssetIds )

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

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

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

local function setupTask( renditionPathBaseDir )
	
	return { 

		renditionPathBaseDir = renditionPathBaseDir,
			
	}

end

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

return {

	setupTask = setupTask,
	processRequests = processRequests,

}
