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

WFCatReorderAssetsTaskTest.lua

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

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

NOTICE: All information contained herein is, and remains 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 are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

	-- WF.core.self_test
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.oz_client.managed_catalog
local WFCatMockCatalog = require 'WFCatMockCatalog'

local WFCatReorderAssetsTask = require 'WFCatReorderAssetsTask'
local WFCatTaskManager = require 'WFCatTaskManager'
local WFCatAlbums = require 'WFCatAlbums'

local WFCatUndo = require 'WFCatUndo'

local WFArguments = require 'WFArguments'

local WFOzTestUtils = require 'WFOzTestUtils'

local KSDate = require "KSDate"
--------------------------------------------------------------------------------

local createFakeWFCatalog = WFCatMockCatalog.createFakeWFCatalog
local check = assert( WFArguments.check )

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

local WFCatReorderAssetsTaskTest = {}

local kDefaultDescription = "WFCatTask"

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

local function makeAdderOfAlbum( rxTest, wfCatalog )

	local counter = 9

	return function( parentId, hidden, importSource, albumId )

		local docId
		local albumName = string.format( "Album %d", counter )

		rxTest:expectOnlyCompleted(

			WFCatAlbums.createAlbum( wfCatalog, albumName, parentId, hidden, importSource, albumId )
				:tap( function( localAlbumId, globalAlbumId )
					check( 'number', 'uuid' )
					docId = globalAlbumId
				end )
				:takeLast( 0 )
		)

		counter = counter - 1

		return docId

	end

end

--------------------------------------------------------------------------------
function WFCatReorderAssetsTaskTest.rxTestCreateReorderingTask( rxTest )
	
	local timeOut = 60
	
	local undoTokenOffset = WFCatUndo.getUndoStateCount()
	
	rxTest:setTimeLimit( timeOut )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	
	
	local addAlbumFn = makeAdderOfAlbum( rxTest, wfCatalog )
	local nextHeight = 1
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore,
		function( baseDocValue, date)
			-- set capture date to what we want
			baseDocValue.captureDate = date
			
			-- set the height for the query ordering
			baseDocValue.importSource.originalHeight = nextHeight
			nextHeight = nextHeight + 1
			
			return baseDocValue
		end )

	-- Begin with 5 album assets.
	-- Expect none of them to have 'order' field in 'payload'.

	local albumId = addAlbumFn()
	
	local assetId1 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId2 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId3 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId4 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId5 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )

	rxTest:expectOnlyCompleted(
		WFCatAlbums.addAssetsToAlbum( wfCatalog, albumId, { assetId1, assetId2, assetId3, assetId4, assetId5 } )
	)
	
	
	------------------------------------------------------------------------------------------
	-- assetId1, assetId2, ..., assetId5 hold strings each
	-- Fetch the asset docs and get the local doc ids 
	-- and store the same in assetId1, assetId2, ..., assetId5.
	-- Thus, we end up in numbers rather than strings since 
	-- they are the ones we work with for reordering assets.
	local assetIdToLocalId = {}
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT * FROM assets]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( assets )
			
			local asset_id_list = {
				assets[ 1 ][ 1 ],
				assets[ 2 ][ 1 ],
				assets[ 3 ][ 1 ],
				assets[ 4 ][ 1 ],
				assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( asset_id_list )
				:tap( function( docValues )
					for _, docValue in ipairs( docValues ) do
						if docValue._id == assetId1 then
							assetId1 = docValue._localId
						elseif docValue._id == assetId2 then
							assetId2 = docValue._localId
						elseif docValue._id == assetId3 then
							assetId3 = docValue._localId
						elseif docValue._id == assetId4 then
							assetId4 = docValue._localId
						elseif docValue._id == assetId5 then
							assetId5 = docValue._localId
						end
						
						assetIdToLocalId[docValue._id] = docValue._localId
					end
					
				end )
		end )
	, ':ignore:' )
	
	------------------------------------------------------------------------------------------
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT docId FROM album_asset_v2]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 5 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					
					return not sortOrderOfAsset1 and
							not sortOrderOfAsset2 and
							not sortOrderOfAsset3 and
							not sortOrderOfAsset4 and
							not sortOrderOfAsset5
				end)
		end )
	, true )
	
	
	---------------------------------------------------------------------------------
	--
	-- TEST: Full Ordering
	--			Start with no order on album assets
	--			End up with order on every album asset.
	--
	-- Initial ordering:
	-- 	[(1, ""), (2, ""), (3, ""), (4, ""), (5, "")]
	--
	--	1st field	=>	asset id (local doc id)
	--	2nd field	=>	order (sortOrder field of album_assets_v2 in index store)
	-- 
	-- Action:
	-- 	Move asset ids 2 and 5 and insert them before asset id 4
	-- 
	-- New ordering:
	--	[(1, "V--0"), (3, "V--1"), (2, "V--2"), (5, "V--3"), (4, "V--4")]
	--  
	
	local anchorAssetId = assetId4
	local assetIdsToReorder = { assetId2, assetId5 }
	local insertWhere = "before"
	local maybeAllAssetIds = {assetId1, assetId2, assetId3, assetId4, assetId5}
	local optUndoStateToken = nil
	
	local fullOrderingTask = WFCatReorderAssetsTask.createReorderingTask( wfCatalog, albumId, anchorAssetId, assetIdsToReorder, insertWhere, maybeAllAssetIds, optUndoStateToken )
	
	local manager = WFCatTaskManager.getCatTaskManager( wfCatalog )
	
	manager:addToQueue( fullOrderingTask )
	
	local fullOrderingTaskObs = rxTest:observe( fullOrderingTask:taskObservable()
		:map( function( data )
			return data
		end )
	)
	
	-- Full ordering should impact all the 5 album asset documents
	-- Hence expect total count to be 5.
	fullOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 5,
	})
	
	fullOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 1,
		processedCount = 5,
		totalCount = 5,
	})
	
	fullOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		extraInfo = {
			totalCount = 5,
			undoToken = undoTokenOffset + 1,
		},
		percentComplete = 1,
		processedCount = 5,
		totalCount = 5,
	})
	
	fullOrderingTaskObs:expectCompleted()
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT s.docId, a.assetId FROM assets a   
					INNER JOIN album_asset_v2 s ON a.assetId == s.assetId 
					ORDER BY s.sortOrder ASC, 
								a.captureDate ASC, 
								a.filenameLC COLLATE i18n_collate ASC]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 5 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					
					return sortOrderOfAsset1 == "V--0" and
							sortOrderOfAsset2 == "V--1" and
							sortOrderOfAsset3 == "V--2" and
							sortOrderOfAsset4 == "V--3" and
							sortOrderOfAsset5 == "V--4" and
							assetIdToLocalId[docValues[1].asset.id] == assetId1 and
							assetIdToLocalId[docValues[2].asset.id] == assetId3 and
							assetIdToLocalId[docValues[3].asset.id] == assetId2 and
							assetIdToLocalId[docValues[4].asset.id] == assetId5 and
							assetIdToLocalId[docValues[5].asset.id] == assetId4
				end)
		end )
	, true )
	
	
	
	---------------------------------------------------------------------------------
	-- TEST: Reordering in fully ordered list
	-- 			Start with fully ordered list
	--			Rearrange assets within this list
	--
	--------------------		Insert in middle          --------------------
	--	
	-- Initial ordering:
	--	[(1, "V--0"), (3, "V--1"), (2, "V--2"), (5, "V--3"), (4, "V--4")]
	-- 
	-- Action:
	--	Move assets ids 1 and 5 and place them before asset id 2
	--
	-- New ordering:
	--	[(3, "V--1"), (1, "V--1---0"), (5, "V--1---1"), (2, "V--2"), (4, "V--4")]
	
	anchorAssetId = assetId2
	assetIdsToReorder = { assetId1, assetId5 }
	insertWhere = "before"
	maybeAllAssetIds = {assetId1, assetId3, assetId2, assetId5, assetId4}
	
	undoTokenOffset = undoTokenOffset + 1
	
	local insertInteriorTask = WFCatReorderAssetsTask.createReorderingTask( wfCatalog, albumId, anchorAssetId, assetIdsToReorder, insertWhere, maybeAllAssetIds, optUndoStateToken )
	
	manager:addToQueue( insertInteriorTask )
	
	local insertInteriorTaskObs = rxTest:observe( insertInteriorTask:taskObservable()
		:map( function( data )
			return data
		end )
	)
	
	-- This reordering should impact 2 album asset documents
	-- since 2 assets are being reordered.
	-- Hence expect total count to be 2.
	insertInteriorTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 2,
	})
	
	insertInteriorTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 1,
		processedCount = 2,
		totalCount = 2,
	})
	
	insertInteriorTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		extraInfo = {
			totalCount = 2,
			undoToken = undoTokenOffset + 1,
		},
		percentComplete = 1,
		processedCount = 2,
		totalCount = 2,
	})
	
	insertInteriorTaskObs:expectCompleted()
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT s.docId, a.assetId FROM assets a   
					INNER JOIN album_asset_v2 s ON a.assetId == s.assetId 
					ORDER BY s.sortOrder ASC, 
								a.captureDate ASC, 
								a.filenameLC COLLATE i18n_collate ASC]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 5 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					
					return sortOrderOfAsset1 == "V--1" and
							sortOrderOfAsset2 == "V--1---0" and
							sortOrderOfAsset3 == "V--1---1" and
							sortOrderOfAsset4 == "V--2" and
							sortOrderOfAsset5 == "V--4" and
							assetIdToLocalId[docValues[1].asset.id] == assetId3 and
							assetIdToLocalId[docValues[2].asset.id] == assetId1 and
							assetIdToLocalId[docValues[3].asset.id] == assetId5 and
							assetIdToLocalId[docValues[4].asset.id] == assetId2 and
							assetIdToLocalId[docValues[5].asset.id] == assetId4
				end)
		end )
	, true )
	
	
	
	---------------------------------------------------------------------------------
	-- TEST: Reordering in fully ordered list
	--------------------		Insert in the beginning          --------------------
	--	
	-- Initial ordering:
	--	[(3, "V--1"), (1, "V--1---0"), (5, "V--1---1"), (2, "V--2"), (4, "V--4")]
	--
	-- Action:
	--	Move asset id 4 and place it before asset id 3
	--
	-- New ordering:
	--	[(4, "F"), (3, "V--1"), (1, "V--1---0"), (5, "V--1---1"), (2, "V--2")]
	anchorAssetId = assetId3
	assetIdsToReorder = { assetId4 }
	insertWhere = "before"
	maybeAllAssetIds = {assetId3, assetId1, assetId5, assetId2, assetId4}
	
	undoTokenOffset = undoTokenOffset + 1
	
	local insertBeginningTask = WFCatReorderAssetsTask.createReorderingTask( wfCatalog, albumId, anchorAssetId, assetIdsToReorder, insertWhere, maybeAllAssetIds, optUndoStateToken )
	
	manager:addToQueue( insertBeginningTask )
	
	local insertBeginningTaskObs = rxTest:observe( insertBeginningTask:taskObservable()
		:map( function( data )
			return data
		end )
	)
	
	-- This reordering should impact 1 album asset documents
	-- since 1 asset is being reordered.
	-- Hence expect total count to be 1.
	insertBeginningTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 1,
	})
	
	insertBeginningTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 1,
		processedCount = 1,
		totalCount = 1,
	})
	
	insertBeginningTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		extraInfo = {
			totalCount = 1,
			undoToken = undoTokenOffset + 1,
		},
		percentComplete = 1,
		processedCount = 1,
		totalCount = 1,
	})
	
	insertBeginningTaskObs:expectCompleted()
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT s.docId, a.assetId FROM assets a   
					INNER JOIN album_asset_v2 s ON a.assetId == s.assetId 
					ORDER BY s.sortOrder ASC, 
								a.captureDate ASC, 
								a.filenameLC COLLATE i18n_collate ASC]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 5 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					
					return sortOrderOfAsset1 == "F" and
							sortOrderOfAsset2 == "V--1" and
							sortOrderOfAsset3 == "V--1---0" and
							sortOrderOfAsset4 == "V--1---1" and
							sortOrderOfAsset5 == "V--2" and
							assetIdToLocalId[docValues[1].asset.id] == assetId4 and
							assetIdToLocalId[docValues[2].asset.id] == assetId3 and
							assetIdToLocalId[docValues[3].asset.id] == assetId1 and
							assetIdToLocalId[docValues[4].asset.id] == assetId5 and
							assetIdToLocalId[docValues[5].asset.id] == assetId2
				end)
		end )
	, true )


	---------------------------------------------------------------------------------
	-- TEST: Reordering in fully ordered list
	--------------------		Insert at the end          --------------------
	-- Initial ordering:
	--	[(4, "F"), (3, "V--1"), (1, "V--1---0"), (5, "V--1---1"), (2, "V--2")]
	--
	-- Action:
	--	Move asset id 3 and place it after asset id 2
	--
	-- New ordering:
	-- [(4, "F"), (1, "V--1---0"), (5, "V--1---1"), (2, "V--2"), (3, "k")]
	
	anchorAssetId = assetId2
	assetIdsToReorder = { assetId3 }
	insertWhere = "last"
	maybeAllAssetIds = {assetId4, assetId3, assetId1, assetId5, assetId2}
	
	
	undoTokenOffset = undoTokenOffset + 1
	
	local insertEndTask = WFCatReorderAssetsTask.createReorderingTask( wfCatalog, albumId, anchorAssetId, assetIdsToReorder, insertWhere, maybeAllAssetIds, optUndoStateToken )
	
	manager:addToQueue( insertEndTask )
	
	local insertEndTaskObs = rxTest:observe( insertEndTask:taskObservable()
		:map( function( data )
			return data
		end )
	)
	
	-- This reordering should impact 1 album asset documents
	-- since 1 asset is being reordered.
	-- Hence expect total count to be 1.
	insertEndTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 1,
	})
	
	insertEndTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 1,
		processedCount = 1,
		totalCount = 1,
	})
	
	insertEndTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		extraInfo = {
			totalCount = 1,
			undoToken = undoTokenOffset + 1,
		},
		percentComplete = 1,
		processedCount = 1,
		totalCount = 1,
	})
	
	insertEndTaskObs:expectCompleted()
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT s.docId, a.assetId FROM assets a   
					INNER JOIN album_asset_v2 s ON a.assetId == s.assetId 
					ORDER BY s.sortOrder ASC, 
								a.captureDate ASC, 
								a.filenameLC COLLATE i18n_collate ASC]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 5 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					
					return sortOrderOfAsset1 == "F" and
							sortOrderOfAsset2 == "V--1---0" and
							sortOrderOfAsset3 == "V--1---1" and
							sortOrderOfAsset4 == "V--2" and
							sortOrderOfAsset5 == "k" and
							assetIdToLocalId[docValues[1].asset.id] == assetId4 and
							assetIdToLocalId[docValues[2].asset.id] == assetId1 and
							assetIdToLocalId[docValues[3].asset.id] == assetId5 and
							assetIdToLocalId[docValues[4].asset.id] == assetId2 and
							assetIdToLocalId[docValues[5].asset.id] == assetId3
				end)
		end )
	, true )

	
	---------------------------------------------------------------------------------
	-- TEST: Reorder with an initial partial ordering applied
	-- Add some more new assets to an ordered album asset list and 
	-- initiate reordering.
	
	-- Add some assets here
	local assetId6 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId7 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId8 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )

	rxTest:expectOnlyCompleted(
		WFCatAlbums.addAssetsToAlbum( wfCatalog, albumId, { assetId6, assetId7, assetId8 } )
	)

	-- Store the new asset ids and ensure that they don't have sort order
	-- [4, 1, 5, 2, 3] => result of previous rearrangement; all have sort order
	-- [6, 7, 8] => should not have sort order
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT * FROM assets]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( assets )
			
			local asset_id_list = {
				assets[ 6 ][ 1 ],
				assets[ 7 ][ 1 ],
				assets[ 8 ][ 1 ],
			}
			
			return docStore:bulkDocValue( asset_id_list )
				:tap( function( docValues )
					for _, docValue in ipairs( docValues ) do
						
						if docValue._id == assetId6 then
							assetId6 = docValue._localId
						elseif docValue._id == assetId7 then
							assetId7 = docValue._localId
						elseif docValue._id == assetId8 then
							assetId8 = docValue._localId
						end
						
						assetIdToLocalId[docValue._id] = docValue._localId

					end
					
				end )
		end )
	, ':ignore:' )
	
	------------------------------------------------------------------------------------------
	
	-- Test if newly added assets 6, 7, 8 have order or not.
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT docId FROM album_asset_v2]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 8 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
				album_assets[ 6 ][ 1 ],
				album_assets[ 7 ][ 1 ],
				album_assets[ 8 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					local sortOrderOfAsset6 = docValues[ 6 ].order
					local sortOrderOfAsset7 = docValues[ 7 ].order
					local sortOrderOfAsset8 = docValues[ 8 ].order
					
					return sortOrderOfAsset1 and
							sortOrderOfAsset2 and
							sortOrderOfAsset3 and
							sortOrderOfAsset4 and
							sortOrderOfAsset5 and
							not sortOrderOfAsset6 and
							not sortOrderOfAsset7 and
							not sortOrderOfAsset8
				end)
		end )
	, true )
	
	----------------------------------------------------------------------------
	-- Initial ordering:
	--	[(4, "F"), (1, "V--1---0"), (5, "V--1---1"), (2, "V--2"), (3, "k"),
	--		(6, nil), (7, nil), (8, nil)]
	--
	-- Action:
	--	Move asset id 5 and place it before asset id 7
	--
	-- New ordering:
	-- [(4, "F"), (1, "V--1---0"), (2, "V--2"), (3, "k"),
	--		(6, "k--0"), (5, "k--1"), (7, "k--2"), (8, "k--3")]
	
	anchorAssetId = assetId7
	assetIdsToReorder = { assetId5 }
	insertWhere = "before"
	maybeAllAssetIds = {assetId4, assetId1, assetId5, assetId2, assetId3, assetId6, assetId7, assetId8}
	
	
	undoTokenOffset = undoTokenOffset + 1
	
	local reorderFromPartialOrderingTask = WFCatReorderAssetsTask.createReorderingTask( wfCatalog, albumId, anchorAssetId, assetIdsToReorder, insertWhere, maybeAllAssetIds, optUndoStateToken )
	
	manager:addToQueue( reorderFromPartialOrderingTask )
	
	local reorderFromPartialOrderingTaskObs = rxTest:observe( reorderFromPartialOrderingTask:taskObservable()
		:map( function( data )
			return data
		end )
	)
	
	-- This reordering should impact 4 album asset documents (6, 5, 7, 8)
	-- since 8 asset are being assigned new orders after reordering.
	-- Hence expect total count to be 4.
	reorderFromPartialOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 4,
	})
	
	reorderFromPartialOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 1,
		processedCount = 4,
		totalCount = 4,
	})
	
	reorderFromPartialOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		extraInfo = {
			totalCount = 4,
			undoToken = undoTokenOffset + 1,
		},
		percentComplete = 1,
		processedCount = 4,
		totalCount = 4,
	})
	
	reorderFromPartialOrderingTaskObs:expectCompleted()
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT s.docId, a.assetId FROM assets a   
					INNER JOIN album_asset_v2 s ON a.assetId == s.assetId 
					ORDER BY s.sortOrder ASC, 
								a.captureDate ASC, 
								a.filenameLC COLLATE i18n_collate ASC]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 8 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
				album_assets[ 6 ][ 1 ],
				album_assets[ 7 ][ 1 ],
				album_assets[ 8 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					local sortOrderOfAsset6 = docValues[ 6 ].order
					local sortOrderOfAsset7 = docValues[ 7 ].order
					local sortOrderOfAsset8 = docValues[ 8 ].order
					
					return sortOrderOfAsset1 == "F" and
							sortOrderOfAsset2 == "V--1---0" and
							sortOrderOfAsset3 == "V--2" and
							sortOrderOfAsset4 == "k" and
							sortOrderOfAsset5 == "k--0" and
							sortOrderOfAsset6 == "k--1" and
							sortOrderOfAsset7 == "k--2" and
							sortOrderOfAsset8 == "k--3" and
							assetIdToLocalId[docValues[1].asset.id] == assetId4 and
							assetIdToLocalId[docValues[2].asset.id] == assetId1 and
							assetIdToLocalId[docValues[3].asset.id] == assetId2 and
							assetIdToLocalId[docValues[4].asset.id] == assetId3 and
							assetIdToLocalId[docValues[5].asset.id] == assetId6 and
							assetIdToLocalId[docValues[6].asset.id] == assetId5 and
							assetIdToLocalId[docValues[7].asset.id] == assetId7 and
							assetIdToLocalId[docValues[8].asset.id] == assetId8
				end)
		end )
	, true )
	
end

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

function WFCatReorderAssetsTaskTest.rxTestCreateUndoReorderingTask( rxTest )
	
	local timeOut = 60
	local undoTokenOffset = WFCatUndo.getUndoStateCount()
	
	rxTest:setTimeLimit( timeOut )
	
	local wfCatalog = createFakeWFCatalog( rxTest )
	
	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	
	
	local addAlbumFn = makeAdderOfAlbum( rxTest, wfCatalog )
	local nextHeight = 1
	local addAssetFn = WFOzTestUtils.v2.makeAdderOfAsset( rxTest, docStore,
		function( baseDocValue, date)
			-- set capture date to what we want
			baseDocValue.captureDate = date
			
			-- set the height for the query ordering
			baseDocValue.importSource.originalHeight = nextHeight
			nextHeight = nextHeight + 1
			
			return baseDocValue
		end )

	-- Begin with 5 album assets.
	-- Expect none of them to have 'order' field in 'payload'.

	local albumId = addAlbumFn()
	
	local assetId1 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId2 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId3 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId4 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )
	local assetId5 = addAssetFn( KSDate.timeInIso8601Format( nil, false ) )

	rxTest:expectOnlyCompleted(
		WFCatAlbums.addAssetsToAlbum( wfCatalog, albumId, { assetId1, assetId2, assetId3, assetId4, assetId5 } )
	)
	
	
	------------------------------------------------------------------------------------------
	-- assetId1, assetId2, ..., assetId5 hold strings each
	-- Fetch the asset docs and get the local doc ids 
	-- and store the same in assetId1, assetId2, ..., assetId5.
	-- Thus, we end up in numbers rather than strings since 
	-- they are the ones we work with for reordering assets.
	local assetIdToLocalId = {}
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT * FROM assets]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( assets )
			
			local asset_id_list = {
				assets[ 1 ][ 1 ],
				assets[ 2 ][ 1 ],
				assets[ 3 ][ 1 ],
				assets[ 4 ][ 1 ],
				assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( asset_id_list )
				:tap( function( docValues )
					for _, docValue in ipairs( docValues ) do
						if docValue._id == assetId1 then
							assetId1 = docValue._localId
						elseif docValue._id == assetId2 then
							assetId2 = docValue._localId
						elseif docValue._id == assetId3 then
							assetId3 = docValue._localId
						elseif docValue._id == assetId4 then
							assetId4 = docValue._localId
						elseif docValue._id == assetId5 then
							assetId5 = docValue._localId
						end
						
						assetIdToLocalId[docValue._id] = docValue._localId
					end
					
				end )
		end )
	, ':ignore:' )
	
	------------------------------------------------------------------------------------------
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT docId FROM album_asset_v2]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 5 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					
					return not sortOrderOfAsset1 and
							not sortOrderOfAsset2 and
							not sortOrderOfAsset3 and
							not sortOrderOfAsset4 and
							not sortOrderOfAsset5
				end)
		end )
	, true )
	
	------------------------------------------------------------------------------------------
	-- 1) Create the initial ordering
	------------------------------------------------------------------------------------------
	--
	-- TEST: Full Ordering
	--			Start with no order on album assets
	--			End up with order on every album asset.
	--
	-- Initial ordering:
	-- 	[(1, ""), (2, ""), (3, ""), (4, ""), (5, "")]
	--
	--	1st field	=>	asset id (local doc id)
	--	2nd field	=>	order (sortOrder field of album_assets_v2 in index store)
	-- 
	-- Action:
	-- 	Move asset ids 2 and 5 and insert them before asset id 4
	-- 
	-- New ordering:
	--	[(1, "V--0"), (3, "V--1"), (2, "V--2"), (5, "V--3"), (4, "V--4")]
	--  
	
	local anchorAssetId = assetId4
	local assetIdsToReorder = { assetId2, assetId5 }
	local insertWhere = "before"
	local maybeAllAssetIds = {assetId1, assetId2, assetId3, assetId4, assetId5}
	local optUndoStateToken = nil
	
	local fullOrderingTask = WFCatReorderAssetsTask.createReorderingTask( wfCatalog, albumId, anchorAssetId, assetIdsToReorder, insertWhere, maybeAllAssetIds, optUndoStateToken )
	
	local manager = WFCatTaskManager.getCatTaskManager( wfCatalog )
	
	manager:addToQueue( fullOrderingTask )
	
	local fullOrderingTaskObs = rxTest:observe( fullOrderingTask:taskObservable()
		:map( function( data )
			return data
		end )
	)
	
	-- Full ordering should impact all the 5 album asset documents
	-- Hence expect total count to be 5.
	fullOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 5,
	})
	
	fullOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 1,
		processedCount = 5,
		totalCount = 5,
	})
	
	fullOrderingTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		extraInfo = {
			totalCount = 5,
			undoToken = undoTokenOffset + 1,
		},
		percentComplete = 1,
		processedCount = 5,
		totalCount = 5,
	})
	
	fullOrderingTaskObs:expectCompleted()
	
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT s.docId, a.assetId FROM assets a   
					INNER JOIN album_asset_v2 s ON a.assetId == s.assetId 
					ORDER BY s.sortOrder ASC, 
								a.captureDate ASC, 
								a.filenameLC COLLATE i18n_collate ASC]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 5 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					
					return sortOrderOfAsset1 == "V--0" and
							sortOrderOfAsset2 == "V--1" and
							sortOrderOfAsset3 == "V--2" and
							sortOrderOfAsset4 == "V--3" and
							sortOrderOfAsset5 == "V--4" and
							assetIdToLocalId[docValues[1].asset.id] == assetId1 and
							assetIdToLocalId[docValues[2].asset.id] == assetId3 and
							assetIdToLocalId[docValues[3].asset.id] == assetId2 and
							assetIdToLocalId[docValues[4].asset.id] == assetId5 and
							assetIdToLocalId[docValues[5].asset.id] == assetId4
				end)
		end )
	, true )
	
	
	------------------------------------------------------------------------------------------
	-- 2) Undo the 'reorder assets' operation
	------------------------------------------------------------------------------------------
	optUndoStateToken = undoTokenOffset + 1
	
	local undoTask = WFCatReorderAssetsTask.createReorderingTask( wfCatalog, nil, nil, nil, nil, nil, optUndoStateToken )

	manager:addToQueue( undoTask )
	
	local undoTaskObs = rxTest:observe( undoTask:taskObservable()
		:map( function( data )
			return data
		end )
	)
	
	undoTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 0,
		processedCount = 0,
		totalCount = 5,
	})
	
	undoTaskObs:expectDataWithinTime( timeOut, {
		description = kDefaultDescription,
		errorCount = 0,
		percentComplete = 1,
		processedCount = 5,
		totalCount = 5,
	})
	
	undoTaskObs:expectCompleted()
	
	------------------------------------------------------------------------------------------
	-- 3) Check the result data
	------------------------------------------------------------------------------------------
	-- Expect none of the album assets to have "order" field set in their payloads.
	rxTest:expectSingleValue(
		indexStore:query( {
			sql = [[SELECT docId FROM album_asset_v2]],
			quiesceUntilUpToDate = true,
		} )
		:take( 1 )
		:flatMap( function( album_assets )
			assert( #album_assets == 5 )
			
			local album_asset_id_list = {
				album_assets[ 1 ][ 1 ],
				album_assets[ 2 ][ 1 ],
				album_assets[ 3 ][ 1 ],
				album_assets[ 4 ][ 1 ],
				album_assets[ 5 ][ 1 ],
			}
			
			return docStore:bulkDocValue( album_asset_id_list )
				:map( function (docValues)
					
					local sortOrderOfAsset1 = docValues[ 1 ].order
					local sortOrderOfAsset2 = docValues[ 2 ].order
					local sortOrderOfAsset3 = docValues[ 3 ].order
					local sortOrderOfAsset4 = docValues[ 4 ].order
					local sortOrderOfAsset5 = docValues[ 5 ].order
					
					return not sortOrderOfAsset1 and
							not sortOrderOfAsset2 and
							not sortOrderOfAsset3 and
							not sortOrderOfAsset4 and
							not sortOrderOfAsset5
				end)
		end )
	, true )

end

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

return WFRxTestCase.wrapSuite( WFCatReorderAssetsTaskTest )


