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

PixelPathProviderSchedulerMainLoopTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law. Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

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

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

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

local PixelPathProviderScheduler = require "PixelPathProviderScheduler"
local PixelPathProviderRequestTypes = require "PixelPathProviderRequestTypes"
local PixelPathProviderStateMachine = require "PixelPathProviderStateMachine"
local NmbLocalRenditionDigest = require "NmbLocalRenditionDigest"

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

local PixelPathProviderRequestTypeNames = PixelPathProviderRequestTypes.names

local testPath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
local volumeGuid = WFFileUtils.getVolumeGuidForPath( testPath )

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

local function makeSchedulerForTest( rxTest )

	local catalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )

	local scheduler = PixelPathProviderScheduler( catalog )

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

	return scheduler, catalog

end

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

local function generateLocalAssetId( i )

	return i

end

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

local function generatePixStreamId( i )

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

end

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

local function extractLocalAssetIdFromRequest( request )

	return request[ 2 ]

end

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

local function addRecordToDocStore( rxTest, catalog, i )

	rxTest:expectSingleValue(

		catalog:docStore():bulkSetDocValue {
			{
				_id = KSIdGenerator.uuidInLowercaseHex(),
				_localId = generateLocalAssetId( i ),
				subtype = "image",
				_localOnly = {
					files = {
						original = {
							master = {
								relativePath = string.format( 'local path %s', i ),
								volumeGuid = volumeGuid
							}
						}
					}
				},
				develop = {
					croppedHeight = 100 * i,
					croppedWidth = 100 * i,
					xmpCameraRaw = '<xmp />',
					userOrientation = i % 4,
				},
				rating = i % 5,
			},
		},

		':ignore:' )

end

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

local function rxTestRunLoopShutdown( rxTest )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	rxTest:expectEqual( "suspended", coroutine.status( scheduler._runLoopCoroutineThread ) )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	rxTest:expectEqual( "suspended", coroutine.status( scheduler._runLoopCoroutineThread ) )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	rxTest:expectEqual( "suspended", coroutine.status( scheduler._runLoopCoroutineThread ) )

	SchedulerTestAPI.runScheduledRequests( scheduler )

	rxTest:expectEqual( "suspended", coroutine.status( scheduler._runLoopCoroutineThread ) )

	scheduler:close()

	rxTest:expectEqual( "dead", coroutine.status( scheduler._runLoopCoroutineThread ) )

end

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

local function isTestMessage( msg )

	return function( testMsg, ... )

		return testMsg == msg

	end

end

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

local function takeNth( n )
	
	return function( ... )

		return select( n, ... )

	end

end

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

local function sortArray( arr, maybeSortFn )

	local sorted = KSTableUtils.deepCopy( arr )

	table.sort( sorted, maybeSortFn )

	return sorted

end

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

local function compareArrays( a1, a2 )

	if #a1 ~= #a2 then

		return #a1 > #a2

	end

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

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

			return a1[ i ] > a2 [i ]

		end

	end

	return false

end

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

local function sortTupleArray( tupleArray )

	return sortArray( tupleArray, compareArrays )

end

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

local function justReturn( ... )

	return ...

end

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

local function getNFromTestInfoStream( testStream, n, filterFunc, 
										maybeTransformFunc )

	local transformTestData = maybeTransformFunc or justReturn

	return testStream
			:where( filterFunc )
			:map( transformTestData )
			:take( n )
			:toArray()

end

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

local function stripTestMessageAndMakeTuple( _, ... )

	return { ... }

end

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

local function rxTestPixelCacheCheckTask( rxTest )

	local scheduler, catalog = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderDocstoreInfoTask" )

	SchedulerTestAPI.addRunLoopTaskToAllowlist( scheduler, 
												"PixelProviderPixelCacheCheckTask" )

	local newRecordCount = 5

	for i = 1, newRecordCount do

		addRecordToDocStore( rxTest, catalog, i )

	end

	local pixStreamCount = 3

	local watchPixelCacheChecks = rxTest:observe( 
									getNFromTestInfoStream(
											scheduler._testData.testInfoStream,
											newRecordCount * pixStreamCount,
											isTestMessage( "didPixelCacheCheck" ),
											stripTestMessageAndMakeTuple )
										:map( sortTupleArray ) )

	local expectedNewRecords = {}

	local expectedPixelCacheCheckMessages = {}

	for i = 1, newRecordCount do

		for j = 1, pixStreamCount do

			expectedPixelCacheCheckMessages[ #expectedPixelCacheCheckMessages + 1 ] = 
				{ generatePixStreamId( j ), generateLocalAssetId( i ), 'no_local_rendition' }

			scheduler:addRequest( generatePixStreamId( j ), generateLocalAssetId( i ), 
									PixelPathProviderRequestTypeNames.grid_visible, 1 )
		
			expectedNewRecords[ #expectedNewRecords + 1 ] = generateLocalAssetId( i )

		end
	
	end

	rxTest:log( "before watchPixelCacheChecks" )

	-- make sure we got doc store data for all the records
	watchPixelCacheChecks:expectData( sortTupleArray( expectedPixelCacheCheckMessages ) )

	rxTest:log( "after watchPixelCacheChecks" )

	-- all requests are now on NeedCheckForPixelPathInCache
	rxTest:expectEqual( 
		sortArray( expectedNewRecords ),
		sortArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.PixelCacheCheckStates.ChooseRenditionStrategy,
						extractLocalAssetIdFromRequest ) ) )


end

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

local function rxTestChooseRenditionStrategyNeedCheckAnyLocalNegativeForRender( rxTest )

	local scheduler, catalog = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( 
						scheduler, "PixelProviderDocstoreInfoTask" )

	SchedulerTestAPI.addRunLoopTaskToAllowlist( 
						scheduler, "PixelProviderPixelCacheCheckTask" )

	SchedulerTestAPI.addRunLoopTaskToAllowlist( 
						scheduler, "PixelProviderChooseRenditionStrategyTask" )

	local newRecordCount = 5

	for i = 1, newRecordCount do

		addRecordToDocStore( rxTest, catalog, i )

	end

	local pixStreamCount = 3

	local watchChooseRenditionStrategy = rxTest:observe( 
									getNFromTestInfoStream(
											scheduler._testData.testInfoStream,
											newRecordCount * pixStreamCount,
											isTestMessage( "chooseRenditionStrategy" ),
											takeNth( 2 ) ) )

	local expectedNewRecords = {}

	local expectedMessages = {}

	for i = 1, newRecordCount do

		for j = 1, pixStreamCount do

			expectedMessages[ #expectedMessages + 1 ] = "NeedCheckAnyLocalNegativeForRender"

			scheduler:addRequest( generatePixStreamId( j ), generateLocalAssetId( i ), 
									PixelPathProviderRequestTypeNames.grid_visible, 1 )
		
			expectedNewRecords[ #expectedNewRecords + 1 ] = generateLocalAssetId( i )

		end
	
	end

	-- make sure we got doc store data for all the records
	watchChooseRenditionStrategy:expectData( expectedMessages )

	-- all requests are now on NeedCheckForPixelPathInCache
	rxTest:expectEqual( 
		sortArray( expectedNewRecords ),
		sortArray( 
			scheduler:_mapAllRequestsWithState( 
						PixelPathProviderStateMachine.LocalBinaryPathCheckStates.NeedCheckAnyLocalNegativeForRender,
						extractLocalAssetIdFromRequest ) ) )


end

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

local function rxTestChooseRenditionStrategyBestMatchCanMaybeRender( rxTest )

	local revId = KSIdGenerator.uuidInLowercaseHex()

	local digest = 'digest'

	local userOrientation = 1
	
	local pixStreamId = "tesssst"

	local localAssetId = 1

	local renderSize = 640

	local croppedLongSide = 4000

	local renditionPath = 
			WFFileUtils.ensureFileNameIsUnique( 
					KSPathUtils.child( 
						KSPathUtils.getStandardFilePath( "temp" ),
						"fake" ) )

	local scheduler = makeSchedulerForTest( rxTest )

	local SchedulerTestAPI = scheduler:_expose_test_API()

	SchedulerTestAPI.addRunLoopTaskToAllowlist( 
						scheduler, "PixelProviderChooseRenditionStrategyTask" )

	-- runScheduledRequests to bootstrap scheduler._testData.runLoopCaches
	SchedulerTestAPI.runScheduledRequests( scheduler )

	for _, downloadableRenditionState in ipairs( { "none", "valid", "invalid" } ) do

		for _, downloadableRenditionType in ipairs( { "image", "video" } ) do

			for _, haveNegatives in ipairs( { "none", "localAny", "localProxy", "localOrig", "serverBoth", "serverProxy", "serverOrig" } ) do

				for _, xmpCameraRawType in ipairs( { "sharedAssetViewOnly", "renderableInternal", "missingExternal" } ) do

					local renditions, invalid_renditions

					if downloadableRenditionState ~= "none" then

						-- video renditions on Oz are only in thumbnail2x or 2048
						local rawRenditionList = 
							( downloadableRenditionType == "image" and
								{
									{ size = "thumbnail2x", href = "fakeThumb" },
									{ size = "640", href = "fake640" },
									{ size = "1280", href = "fake1280" },
									{ size = "2048", href = "fake2048" },
								} ) or
							( downloadableRenditionType == "video" and
								{
									{ size = "thumbnail2x", href = "fakeThumb" },
									{ size = "2048", href = "fake2048" },
								} )

						if downloadableRenditionState == "valid" then

							renditions = rawRenditionList

						elseif downloadableRenditionState == "invalid" then
							
							invalid_renditions = rawRenditionList

						end

					end

					-- set up xmpCameraRaw for various states
					local xmpCameraRaw 

					if xmpCameraRawType ~= "missingExternal" then

						xmpCameraRaw =
							xmpCameraRawType == "sharedAssetViewOnly" and
								NmbLocalRenditionDigest.privateUnitTestExports.makeUniqueEmptySettingsForDownloadOnlyAssetImp( revId ) or
								"<xmp />" 

					end

					-- set up negative paths for various states
					local proxyPath, originalPath, proxies, master

					if haveNegatives == "localAny" or haveNegatives == "localProxy" then

						proxyPath = "proxyPath"

					end

					if haveNegatives == "localAny" or haveNegatives == "localOrig" then

						originalPath = "originalPath"

					end

					if haveNegatives == "serverBoth" or haveNegatives == "serverProxy" then

						proxies = { { href = "proxyHref", size = 2560 } }

					end

					if haveNegatives == "serverBoth" or haveNegatives == "serverOrig" then

						master = "masterHref"

					end

					scheduler._testData.runLoopCaches.sharedCaches.baseRenderInfoCache
						:pushCacheItem( 
							localAssetId,
							{ 
								ozAssetId = "ozAssetId",
								changeCounter = 1, 
								revId = revId,
								digest = digest,
								croppedLongSide = croppedLongSide,
								renditions = renditions,
								invalid_renditions = invalid_renditions,
								xmpCameraRaw = xmpCameraRaw,
								proxyPath = proxyPath,
								originalPath = originalPath,
								proxies = proxies,
								master = master

							} )

					scheduler._testData.runLoopCaches.sharedRenderCaches.activeRenditionInfoCache
						:pushCacheItem(
							pixStreamId, localAssetId,
							{ 
								renditionPath = renditionPath,
								digest = digest,
								revId = revId,
								renderSize = renderSize,
								croppedLongSide = croppedLongSide,
								userOrientation = userOrientation, 
								devSettings = xmpCameraRaw,
							} )

					scheduler._testData.runLoopCaches.sharedRenderCaches.currRenderSettingsCache
						:pushCacheItem( 
							localAssetId, 
							{ digest, revId, xmpCameraRaw, userOrientation } )

					local watchChooseRenditionStrategy = 
							rxTest
								:observe( 
									getNFromTestInfoStream(
										scheduler._testData.testInfoStream,
										1,
										isTestMessage( "chooseRenditionStrategy" ),
										takeNth( 2 ) ) )

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

					SchedulerTestAPI.runScheduledRequests( scheduler )

					local expectedMessages = { "unhandled permutation!" }

					if downloadableRenditionState == "none" then

						-- we can't download, test all the render scenarios
						expectedMessages = 
							-- no render, no download
							( haveNegatives == "none" and
								{ "SendNoDownloadableRenderOrBinary" } ) or
							-- can render, but missing external xmp
							( xmpCameraRawType == "missingExternal" and
								{ "NeedCheckForLocalXmpDevelopForRender"} ) or
							-- can render, but not allowed in album (should be impossible)
							( xmpCameraRawType == "sharedAssetViewOnly" and
								{ "SendNoDownloadableRenderOrBinary"} ) or
							-- can render using local negative
							( ( haveNegatives == "localAny" or 
									haveNegatives == "localProxy" or 
									haveNegatives == "localOrig" ) and
								{ "NeedCheckAnyLocalNegativeForRender" } ) or
							-- can render after downloading proxy negative
							( ( haveNegatives == "serverBoth" or 
									haveNegatives == "serverProxy" ) and
								{ "NeedProxyDownloadForRender" } ) or
								{ "NeedOriginalDownloadForRender" }

					-- we CAN download, test all rest of the scenarios
					elseif downloadableRenditionType ~= "video" and
								( xmpCameraRawType == "sharedAssetViewOnly" or 
									haveNegatives == "none" ) then

						-- we have an image which cannot be rendered
						expectedMessages = 
							downloadableRenditionState == "invalid" and
							{ "NeedInvalidRenderDownload" } or
							{ "NeedRenderDownload" }

					elseif downloadableRenditionType == "video" then

						-- Note: for video, we are requesting a 640 rendition
						-- but none are availaible online (only thumbnail2x and
						-- 2048 are available in those cases). If we can render
						-- locally we'll create a 640, otherwise we will
						-- download a 2048 which will result in a new long side
						-- for the request
						expectedMessages = 
							-- no negatives, must download a 2048 which will
							-- reset long side and restart request state machine
							( haveNegatives == "none" and 
								{ "NeedAssetInfoForRender" } ) or
							-- can't render due to album policy must download a
							-- 2048 which will reset long side and restart
							-- request state machine
							( xmpCameraRawType == "sharedAssetViewOnly" and
								{ "NeedAssetInfoForRender"} ) or
							-- can render, but missing external xmp
							( xmpCameraRawType == "missingExternal" and
								{ "NeedCheckForLocalXmpDevelopForRender"} ) or
							-- can render using local negative
							( ( haveNegatives == "localAny" or 
									haveNegatives == "localProxy" or 
									haveNegatives == "localOrig" ) and
								{ "NeedCheckAnyLocalNegativeForRender" } ) or
							-- can render after downloading proxy negative
							( ( haveNegatives == "serverBoth" or 
									haveNegatives == "serverProxy" ) and
								{ "NeedProxyDownloadForRender" } ) or
								{ "NeedOriginalDownloadForRender" }

					-- we can download, maybe we can render
					elseif downloadableRenditionState == "invalid" then

						expectedMessages = 
							-- cannot render, must download
							( haveNegatives == "none" and 
								{ "NeedInvalidRenderDownload" } ) or
							-- can render, but missing external xmp
							( xmpCameraRawType == "missingExternal" and
								{ "NeedCheckForLocalXmpDevelopForRender"} ) or
							-- can render using local negative
							( ( haveNegatives == "localAny" or 
									haveNegatives == "localProxy" or 
									haveNegatives == "localOrig" ) and
								{ "NeedCheckAnyLocalNegativeForRender" } ) or
							-- can render after downloading proxy negative
							( ( haveNegatives == "serverBoth" or 
									haveNegatives == "serverProxy" ) and
								{ "NeedProxyDownloadForRender" } ) or
								{ "NeedOriginalDownloadForRender" }

					elseif downloadableRenditionState == "valid"  then

						expectedMessages = { "NeedRenderDownload" }

					end
			
					rxTest:logf( "Expecting %s when downloadableRenditionState (%s), downloadableRenditionType (%s), haveNegatives (%s), xmpCameraRawType (%s)",
									expectedMessages[ 1 ],
									downloadableRenditionState,
									downloadableRenditionType,
									haveNegatives,
									xmpCameraRawType )

					watchChooseRenditionStrategy:expectData( expectedMessages )

					scheduler:abandonAllPixStreamRequests( pixStreamId )

				end
		
			end

		end

	end

end

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

return WFRxTestCase.wrapSuite {

	rxTestRunLoopShutdown = rxTestRunLoopShutdown,
	
	rxTestPixelCacheCheckTask = rxTestPixelCacheCheckTask,

	rxTestChooseRenditionStrategyNeedCheckAnyLocalNegativeForRender = 
		rxTestChooseRenditionStrategyNeedCheckAnyLocalNegativeForRender,

	rxTestChooseRenditionStrategyBestMatchCanMaybeRender = 
		rxTestChooseRenditionStrategyBestMatchCanMaybeRender,


}
