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

PixelProviderSendDataTask.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 KSTableUtils = require 'KSTableUtils'

	-- pixel provider
local WFCatRenderSize = require "WFCatRenderSize"
local PixelProviderSharedTaskUtils = require 'PixelProviderSharedTaskUtils'
local PixelPathProviderStateMachine = require 'PixelPathProviderStateMachine'
local PixelPathProviderSchedulerPrivateMethods =
		require "PixelPathProviderSchedulerPrivateMethods"

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

local PixelProviderSharedTaskUtils_maybeUpdateRequestLongSideForMaximumDownloadableSize =
		assert( PixelProviderSharedTaskUtils.maybeUpdateRequestLongSideForMaximumDownloadableSize,
				"PixelProviderSharedTaskUtils.maybeUpdateRequestLongSideForMaximumDownloadableSize does not exist" )

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

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

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

local KSTableUtils_derefOpt = assert( KSTableUtils.derefOpt, 
										"KSTableUtils.derefOpt not defined" )

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

local WFCatRenderSize_getBoundedRenderSize =
		assert( WFCatRenderSize.getBoundedRenderSize,
				"WFCatRenderSize.getBoundedRenderSize not defined" )

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

local PixelProviderSharedTaskUtils_makeNonSparseStringArray =
		assert( PixelProviderSharedTaskUtils.makeNonSparseStringArray,
				"PixelProviderSharedTaskUtils.makeNonSparseStringArray not defined" )

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

local SendDataStates = PixelPathProviderStateMachine.SendDataStates

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

local function maybeGetCurrRenderData( baseRenderInfoCache, localAssetId )

	local maybeBaseRenderInfo =
			baseRenderInfoCache:peekCacheItem( localAssetId )

	return KSTableUtils_derefOpt( maybeBaseRenderInfo, 'croppedLongSide' ),
			KSTableUtils_derefOpt( maybeBaseRenderInfo, 'digest' ),
			KSTableUtils_derefOpt( maybeBaseRenderInfo, 'subtype' )

end

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

local PixelPathProviderSchedulerPrivateMethods_sendRenditionPathToPixelStream =
		PixelPathProviderSchedulerPrivateMethods.sendRenditionPathToPixelStream

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

local function sendRenditionPathForRequest( request, scheduler, 
											renditionDiskStatusCache,
											baseRenderInfoCache )

	local pixStreamId, localAssetId, requestedRenderSize = unpack( request )

	local croppedLongSide, currDigest, subtype =
			maybeGetCurrRenderData( baseRenderInfoCache, localAssetId ) 

	if not croppedLongSide then

		logger:warnf( "Can't send pixel path for %s.%s: missing expected croppedLongSide",
						pixStreamId, localAssetId )

		scheduler:_applyTransitionToRequest( request, 'missing_data' )

		return

	end

	local neededRenderSize = WFCatRenderSize_getBoundedRenderSize( 
								croppedLongSide, requestedRenderSize, subtype )

	local path, existingRenderSize, renditionDigest = 
			renditionDiskStatusCache
				:maybeGetPath( localAssetId, neededRenderSize )

	if not path and neededRenderSize == "full" then

		-- in certain edge cases (such as video) it's possible we downloaded a
		-- non full size rendition to fulfill the need for a "full" size
		-- rendition. In that case, we instead return the highest res current
		-- path.
		path, existingRenderSize, renditionDigest = 
			renditionDiskStatusCache
				:maybeGetHighestResCurrentRendition( localAssetId, neededRenderSize )

	end

	if not path then

		logger:warnf( "Missing expected pixel path for %s.%s. requested size: %s, needed size: %s",
						pixStreamId, localAssetId, requestedRenderSize,
						neededRenderSize )

		scheduler:_applyTransitionToRequest( request, 'missing_expected_path_computed_in_previous_step' )

		return

	end
	
	PixelPathProviderSchedulerPrivateMethods_sendRenditionPathToPixelStream( 
		scheduler,  pixStreamId, localAssetId, path, renditionDigest,
		existingRenderSize, croppedLongSide )

	local transition = renditionDigest == currDigest and 
							'sent' or 'stale_data_sent'

	scheduler:_applyTransitionToRequest( request, transition )

end

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

local function maybeSendLoupeRenditionPathFromInMemoryCacheForRequest( 
				request, scheduler, renditionDiskStatusCache,
				baseRenderInfoCache )

	local pixStreamId, localAssetId, _, _, requestState = unpack( request )

	local path, localRenditionSize, renditionDigest = 
			renditionDiskStatusCache
				:maybeGetHighestResCurrentRendition( 
					localAssetId )

	local maybeBaseRenderInfo, maxDownloadableRenderSize =
			PixelProviderSharedTaskUtils_maybeUpdateRequestLongSideForMaximumDownloadableSize( 
				scheduler, pixStreamId, localAssetId, requestState,
				baseRenderInfoCache )

	local localRendtionIsBestPossible =
			type( path ) == "string" and
			( not maxDownloadableRenderSize or
				localRenditionSize == "full" or 
				( type( localRenditionSize ) == "number" and
					type( maxDownloadableRenderSize ) == "number" and
					localRenditionSize >= maxDownloadableRenderSize ) ) 

	local transition = 
			( not path and 'no_suitable_rendition_need_further_check' ) or
			( localRendtionIsBestPossible and 'sent_path' ) or 
			'sent_path_need_further_check'

	if path then

		local maybeCroppedLongSide =
				KSTableUtils_derefOpt( maybeBaseRenderInfo, 'croppedLongSide' )
		
		PixelPathProviderSchedulerPrivateMethods_sendRenditionPathToPixelStream( 
			scheduler,  pixStreamId, localAssetId, path, renditionDigest,
			localRenditionSize, maybeCroppedLongSide )

	end

	logger:infof( "maybeSendLoupeRenditionPathFromInMemoryCacheForRequest for %s.%s",
						pixStreamId, localAssetId )

	scheduler:_sendTestData( 'maybeSendLoupeRenditionPathFromInMemoryCacheForRequest',
								pixStreamId, localAssetId, transition )

	scheduler:_applyTransitionToRequest( request, transition )

end

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

local function sendFinalLoupeRenditionPathForRequest( 
				request, scheduler, renditionDiskStatusCache, 
				baseRenderInfoCache )

	local pixStreamId, localAssetId = unpack( request )

	local path, localRenditionSize, renditionDigest = 
			renditionDiskStatusCache
				:maybeGetHighestResCurrentRendition( 
					localAssetId )

	local transition = 
			path and 'sent_path' or 'missing_expected_rendition_path'

	if path then

		local maybeBaseRenderInfo =
				baseRenderInfoCache:peekCacheItem( localAssetId )

		local maybeCroppedLongSide =
				KSTableUtils_derefOpt( maybeBaseRenderInfo, 'croppedLongSide' )
		
		PixelPathProviderSchedulerPrivateMethods_sendRenditionPathToPixelStream( 
			scheduler,  pixStreamId, localAssetId, path, renditionDigest,
			localRenditionSize, maybeCroppedLongSide )

	end

	logger:infof( "sendFinalLoupeRenditionPathForRequest for %s.%s path %s", pixStreamId, localAssetId, path )

	scheduler:_sendTestData( 'sendFinalLoupeRenditionPathForRequest',
								pixStreamId, localAssetId, transition, path )

	scheduler:_applyTransitionToRequest( request, transition )

end

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

local function sendFinalExportFullRenditionPathForRequest( 
				request, scheduler, renditionDiskStatusCache )

	local pixStreamId, localAssetId = unpack( request )

	local path, localRenditionSize, renditionDigest = 
			renditionDiskStatusCache
				:maybeGetHighestResCurrentRendition( 
					localAssetId )

	local haveFullSizePath = path and localRenditionSize == "full"

	local transition = 
			haveFullSizePath and
				'sent_path' or 'missing_expected_rendition_path'

	if haveFullSizePath then
		
		PixelPathProviderSchedulerPrivateMethods_sendRenditionPathToPixelStream( 
			scheduler,  pixStreamId, localAssetId, path, renditionDigest,
			localRenditionSize, "full" )

	end

	logger:infof( "sendFinalExportFullRenditionPathForRequest for %s.%s path %s", pixStreamId, localAssetId, path )

	scheduler:_sendTestData( 'sendFinalExportFullRenditionPathForRequest',
								pixStreamId, localAssetId, transition, path )

	scheduler:_applyTransitionToRequest( request, transition )

end

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

local function sendFinalExportBiggestPossibleRenditionPathForRequest( 
				request, scheduler, renditionDiskStatusCache )

	local pixStreamId, localAssetId = unpack( request )

	local path, localRenditionSize, renditionDigest = 
			renditionDiskStatusCache
				:maybeGetHighestResCurrentRendition( 
					localAssetId )

	local transition = 
			path and
				'sent_path' or 'missing_expected_rendition_path'

	if path then
		
		PixelPathProviderSchedulerPrivateMethods_sendRenditionPathToPixelStream( 
			scheduler,  pixStreamId, localAssetId, path, renditionDigest,
			localRenditionSize, "full" )

	end

	logger:infof( "sendFinalExportFullRenditionPathForRequest for %s.%s path %s", pixStreamId, localAssetId, path )

	scheduler:_sendTestData( 'sendFinalExportBiggestPossibleRenditionPathForRequest',
								pixStreamId, localAssetId, transition, path )

	scheduler:_applyTransitionToRequest( request, transition )

end

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

local PixelPathProviderSchedulerPrivateMethods_sendLocalBinaryPathToPixelStream =
	PixelPathProviderSchedulerPrivateMethods.sendLocalBinaryPathToPixelStream

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

local function sendLocalBinaryPathForRequest( request, scheduler, 
												localBinaryPathCache )

	local pixStreamId, localAssetId, _, _, state = unpack( request )

	local maybeNegativePathInfo = 
			localBinaryPathCache:popCacheItem( pixStreamId, localAssetId ) 

	local path, ozBinaryType = unpack( maybeNegativePathInfo or {} )

	if not path then

		logger:warnf( "Can't send local negative path for %s.%s: missing expected data for state %s",
						pixStreamId, localAssetId, state )

		scheduler:_applyTransitionToRequest( request, 'missing_expected_path_computed_in_previous_step' )

		return

	end
	
	logger:infof( "sendLocalBinaryPathForRequest for %s.%s path %s", pixStreamId, localAssetId, path )
	
	PixelPathProviderSchedulerPrivateMethods_sendLocalBinaryPathToPixelStream( 
		scheduler,  pixStreamId, localAssetId, path, ozBinaryType )

	scheduler:_applyTransitionToRequest( request, 'sent' )

end

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

local function sendXmpDevelopForRequest( request, scheduler,
												localBinaryPathCache )

	local pixStreamId, localAssetId, _, _, state = unpack( request )

	local xmpDevelopPath, ozBinaryType =
			unpack( localBinaryPathCache
						:popCacheItem( pixStreamId, localAssetId ) or {} )

	if not xmpDevelopPath then

		logger:warnf( "Can't send local xmp develop path for %s.%s: missing expected path for state %s",
						pixStreamId, localAssetId, state )

		scheduler:_applyTransitionToRequest( request, 'missing_expected_path_computed_in_previous_step' )

		return

	end

	if ozBinaryType ~= "xmp_develop" then

		logger:warnf( "Can't send local xmp develop path for %s.%s: wrong ozBinaryType %s for state %s",
						pixStreamId, localAssetId, ozBinaryType, state )

		scheduler:_applyTransitionToRequest( request, 'missing_expected_path_computed_in_previous_step' )

		return

	end
	
	PixelPathProviderSchedulerPrivateMethods_sendLocalBinaryPathToPixelStream( 
		scheduler,  pixStreamId, localAssetId, xmpDevelopPath, 'xmp_develop' )

	scheduler:_applyTransitionToRequest( request, 'sent' )

end

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

local function sendNotXmpSidecarForRequest( request, scheduler,
												localBinaryPathCache )

	local pixStreamId, localAssetId = unpack( request )

	local maybeNegativePathInfo =
			localBinaryPathCache:popCacheItem( pixStreamId, localAssetId )

	local path, ozBinaryType = unpack( maybeNegativePathInfo or {} )

	if path ~= nil then
		logger:warnf( "Expecting nil path for %s.%s.", pixStreamId, localAssetId )
	end

	PixelPathProviderSchedulerPrivateMethods_sendLocalBinaryPathToPixelStream(
		scheduler,  pixStreamId, localAssetId, nil, ozBinaryType )

	scheduler:_applyTransitionToRequest( request, 'sent' )

end

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

local PixelProviderSharedTaskUtils_getValidOriginalDownloadInfoFromRenderInfo =
		assert( require "PixelProviderSharedTaskUtils".getValidOriginalDownloadInfoFromRenderInfo,
			"PixelProviderSharedTaskUtils.getValidOriginalDownloadInfoFromRenderInfo is missing!" )

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

local function sendProxyForBestPossibleLocalNegativePath( 
					request, scheduler, localBinaryPathCache,
					baseRenderInfoCache )

	local pixStreamId, localAssetId = unpack( request )

	local maybeNegativePathInfo = 
			localBinaryPathCache:popCacheItem( pixStreamId, localAssetId ) 

	local path, ozBinaryType = unpack( maybeNegativePathInfo or {} )

	if not path then

		logger:warnf( "Can't send local negative path for %s.%s: missing expected data",
						pixStreamId, localAssetId )

		scheduler:_applyTransitionToRequest( request, 'missing_expected_path_computed_in_previous_step' )

		return

	end
	
	PixelPathProviderSchedulerPrivateMethods_sendLocalBinaryPathToPixelStream( 
		scheduler,  pixStreamId, localAssetId, path, ozBinaryType )

	local baseRenderInfo = baseRenderInfoCache:peekCacheItem( localAssetId )

	logger:infof( "sendProxyForBestPossibleLocalNegativePath for %s.%s path %s", pixStreamId, localAssetId, path )
	

	-- we've sent a proxy path, now let's see if we can send an original path
	
	if type( baseRenderInfo ) ~= "table" then

		-- can't figure out if we have original, enter the wait state which will
		-- be updated when docstore is updated
		return scheduler:_applyTransitionToRequest( request, 
											'sent_cannot_download_original' )

	end
	
	if baseRenderInfo.originalPath then

		-- we have a local path to the original, too. transition to a state machine loop that
		-- will check the local original path and send it
		return scheduler:_applyTransitionToRequest( request, 
											'sent_have_local_original' )

	end

	if PixelProviderSharedTaskUtils_getValidOriginalDownloadInfoFromRenderInfo( 
			baseRenderInfo ) then

		-- we have a downloadable original, too. transition to a state machine loop that
		-- will check the download the original 
		return scheduler:_applyTransitionToRequest( request, 
											'sent_can_download_original' )

	end

	-- No original available, enter the wait state which will be updated when
	-- docstore is updated. So if Oz gets the original at some point, we
	-- we get a docstore update which will add a original download URL that will
	-- in turn trigger the download
	return scheduler:_applyTransitionToRequest( request, 
										'sent_cannot_download_original' )

end

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

local function sendErrorForRequest( request, scheduler,
										sendErrorTransition, 
										activeRenditionInfoCache,
										localBinaryPathCache )

	local pixStreamId, localAssetId, _, _, errorState = unpack( request )

	localBinaryPathCache:destroyCacheItem( pixStreamId, localAssetId )

	activeRenditionInfoCache:destroyCacheItem( pixStreamId, localAssetId )

	logger:infof( "Sending error for %s.%s: %s", pixStreamId, localAssetId, 
													errorState )
	
	PixelPathProviderSchedulerPrivateMethods.sendErrorToPixelStream( 
		scheduler, pixStreamId, localAssetId, errorState )

	scheduler:_applyTransitionToRequest( request, sendErrorTransition )

end


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

local _haveLocalBinaryStates = 
	PixelProviderSharedTaskUtils_makeNonSparseStringArray(
		SendDataStates.HaveLocalNegativePath,
		SendDataStates.HaveLocalXmpSidecarPath,
		SendDataStates.HaveOriginalForBestPossibleLocalNegativePath
	)

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

local _errorStatesRepondingTo_sent_transition =
	PixelProviderSharedTaskUtils_makeNonSparseStringArray(
		SendDataStates.SendNoDownloadableRenderOrBinary,
		SendDataStates.SendNoDownloadableOriginal,
		SendDataStates.SendNoDownloadableBinary,
		SendDataStates.SendNoDownloadableBinaryForRender,
		SendDataStates.SendNoDownloadableXmpDevelop,
		SendDataStates.SendMissingExpectedXmpDevelopUrl,
		SendDataStates.SendMissingExpectedXmpDevelopUrlForRender,
		SendDataStates.HaveEmbeddedXmpDevelopNotExternal,
		SendDataStates.SendBadExternalDevelopAuxFileSequence,
		SendDataStates.SendNoDownloadableXmpSidecar,
		SendDataStates.SendPermanentDownloadError,
		SendDataStates.SendPermanentError,
		SendDataStates.SendPermanentMissingProfileRenditionPathError,
		SendDataStates.SendPermanentBadAssetIdError,
		SendDataStates.SendPermanentErrorForExportFullRenditionCreateFail,
		SendDataStates.SendPermanentErrorForExportFullRenditionDownloadFail,
		SendDataStates.SendPermanentErrorForExportFullRenditionDownloadNoLinks,
		SendDataStates.SendPermanentErrorForExportBiggestPossibleRenditionDownloadFail,
		SendDataStates.SendPermanentErrorForExportBiggestPossibleRenditionDownloadNoLinks,
		SendDataStates.SendPermanentDownloadErrorForBestPossibleLocalNegativePath,
		SendDataStates.SendPermanentErrorForBestPossibleLocalNegativePath )

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

local _errorStatesRespondingTo_send_error_transition -- calculated when needed

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

local function getSendErrorTransitionSourceStates( scheduler )

	_errorStatesRespondingTo_send_error_transition =
		_errorStatesRespondingTo_send_error_transition or
		-- build the list from the state machine
		PixelPathProviderSchedulerPrivateMethods.getSourceStatesForTransition(
			scheduler, 'send_error' )

	return _errorStatesRespondingTo_send_error_transition

end

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

local function addOrphanedStates( orphanedSendDataStateCounts, request )

	local _, _, _, _, state = unpack( request )

	orphanedSendDataStateCounts[ state ] =
		( orphanedSendDataStateCounts[ state ] or 0 ) + 1

	return orphanedSendDataStateCounts

end

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

local function processRequests( scheduler, 
									renditionDiskStatusCache,
									activeRenditionInfoCache,
									baseRenderInfoCache,
									localBinaryPathCache )

	-- send rendition paths
	scheduler:_mapAllRequestsWithState( SendDataStates.HaveRenditionPath,
			sendRenditionPathForRequest,
				scheduler, renditionDiskStatusCache,
				baseRenderInfoCache )

	scheduler:_mapAllRequestsWithState( SendDataStates.MaybeSendLoupeRenditionPathFromInMemoryCache,
			maybeSendLoupeRenditionPathFromInMemoryCacheForRequest,
				scheduler, renditionDiskStatusCache,
				baseRenderInfoCache )

	scheduler:_mapAllRequestsWithState( SendDataStates.SendFinalLoupeRenditionPath,
			sendFinalLoupeRenditionPathForRequest,
				scheduler, renditionDiskStatusCache,
				baseRenderInfoCache )

	scheduler:_mapAllRequestsWithState( SendDataStates.SendFinalExportFullRenditionPath,
			sendFinalExportFullRenditionPathForRequest,
				scheduler, renditionDiskStatusCache )

	scheduler:_mapAllRequestsWithState( SendDataStates.SendFinalExportBiggestPossibleRenditionPath,
			sendFinalExportBiggestPossibleRenditionPathForRequest,
				scheduler, renditionDiskStatusCache )

	-- send local negative paths
	scheduler:_mapAllRequestsWithStates( _haveLocalBinaryStates,
			sendLocalBinaryPathForRequest,
				scheduler, localBinaryPathCache )

	scheduler:_mapAllRequestsWithState( SendDataStates.HaveLocalXmpDevelopPath,
			sendXmpDevelopForRequest,
				scheduler, localBinaryPathCache )

	scheduler:_mapAllRequestsWithState( SendDataStates.NotXmpSidecar,
			sendNotXmpSidecarForRequest,
				scheduler, localBinaryPathCache )

	scheduler:_mapAllRequestsWithState( SendDataStates.HaveProxyForBestPossibleLocalNegativePath,
			sendProxyForBestPossibleLocalNegativePath,
				scheduler, localBinaryPathCache, baseRenderInfoCache )
				
	-- send errors
	--
	-- first send errors that respond to 'sent' transitions
	scheduler:_mapAllRequestsWithStates( _errorStatesRepondingTo_sent_transition,
			sendErrorForRequest,
				scheduler, 'sent', activeRenditionInfoCache,
				localBinaryPathCache )

	-- then send errors that respond to 'send_error' transitions
	scheduler
		:_mapAllRequestsWithStates( 
			getSendErrorTransitionSourceStates( scheduler ),
			sendErrorForRequest,
				scheduler, 'send_error', activeRenditionInfoCache,
				localBinaryPathCache )

	-- At this point, any requests that still have a send data state are
	-- permanently stuck. If we have a logger level at warn, we'll report them.
	if logger.will_warn then

		local sendDataStateArray =
				KSTableUtils.extractKeys( SendDataStates )

		local orphanedSendDataStateCounts = 
				PixelPathProviderSchedulerPrivateMethods.reduceAllRequestsWithStates( 
							scheduler, {}, sendDataStateArray, addOrphanedStates )

		for state, count in pairs( orphanedSendDataStateCounts ) do

			-- This warning fires if we have a state in the SendDataStates
			-- state group that does not send data in the above code.
			--
			-- This means if code is waiting for a request to send data, it
			-- will hang forever.
			logger:warnf( "There are %s orphaned %s requests, data will never be sent!",
							count, state )

		end

	end

end

--==============================================================================
-- private unit test exports
--==============================================================================

local function getSendErrorStates( scheduler )

	return KSTableUtils.appendArray( 
			KSTableUtils.deepCopy( 
				_errorStatesRepondingTo_sent_transition ),
			KSTableUtils.deepCopy( 
				getSendErrorTransitionSourceStates( scheduler ) ) )

end

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

return {

	processRequests = processRequests,

	privateUnitTestExports = {

		getAllSendErrorStates = getSendErrorStates

	}

}
